Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
82ad9295
Unverified
Commit
82ad9295
authored
Dec 19, 2017
by
Cliff Dyer
Committed by
GitHub
Dec 19, 2017
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #16569 from open-craft/cliff/video-completion
Define custom completion for VideoModule
parents
2c4a5207
1fc43bf6
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
407 additions
and
95 deletions
+407
-95
cms/envs/aws.py
+9
-0
cms/envs/common.py
+7
-0
common/lib/xmodule/xmodule/js/spec/video/completion_spec.js
+60
-0
common/lib/xmodule/xmodule/js/spec/video/html5_video_spec.js
+1
-1
common/lib/xmodule/xmodule/js/src/video/09_completion.js
+178
-0
common/lib/xmodule/xmodule/js/src/video/10_main.js
+14
-11
common/lib/xmodule/xmodule/video_module/bumper_utils.py
+5
-2
common/lib/xmodule/xmodule/video_module/video_handlers.py
+30
-2
common/lib/xmodule/xmodule/video_module/video_module.py
+30
-16
lms/djangoapps/courseware/tests/test_video_handlers.py
+8
-0
lms/djangoapps/courseware/tests/test_video_mongo.py
+50
-63
lms/envs/aws.py
+9
-0
lms/envs/common.py
+6
-0
No files found.
cms/envs/aws.py
View file @
82ad9295
...
@@ -552,6 +552,15 @@ PARENTAL_CONSENT_AGE_LIMIT = ENV_TOKENS.get(
...
@@ -552,6 +552,15 @@ PARENTAL_CONSENT_AGE_LIMIT = ENV_TOKENS.get(
# Allow extra middleware classes to be added to the app through configuration.
# Allow extra middleware classes to be added to the app through configuration.
MIDDLEWARE_CLASSES
.
extend
(
ENV_TOKENS
.
get
(
'EXTRA_MIDDLEWARE_CLASSES'
,
[]))
MIDDLEWARE_CLASSES
.
extend
(
ENV_TOKENS
.
get
(
'EXTRA_MIDDLEWARE_CLASSES'
,
[]))
########################## Settings for Completion API #####################
# Once a user has watched this percentage of a video, mark it as complete:
# (0.0 = 0%, 1.0 = 100%)
COMPLETION_VIDEO_COMPLETE_PERCENTAGE
=
ENV_TOKENS
.
get
(
'COMPLETION_VIDEO_COMPLETE_PERCENTAGE'
,
COMPLETION_VIDEO_COMPLETE_PERCENTAGE
,
)
########################## Derive Any Derived Settings #######################
########################## Derive Any Derived Settings #######################
derive_settings
(
__name__
)
derive_settings
(
__name__
)
cms/envs/common.py
View file @
82ad9295
...
@@ -1493,3 +1493,10 @@ ZENDESK_USER = None
...
@@ -1493,3 +1493,10 @@ ZENDESK_USER = None
ZENDESK_API_KEY
=
None
ZENDESK_API_KEY
=
None
ZENDESK_OAUTH_ACCESS_TOKEN
=
None
ZENDESK_OAUTH_ACCESS_TOKEN
=
None
ZENDESK_CUSTOM_FIELDS
=
{}
ZENDESK_CUSTOM_FIELDS
=
{}
############## Settings for Completion API #########################
# Once a user has watched this percentage of a video, mark it as complete:
# (0.0 = 0%, 1.0 = 100%)
COMPLETION_VIDEO_COMPLETE_PERCENTAGE
=
0.95
common/lib/xmodule/xmodule/js/spec/video/completion_spec.js
0 → 100644
View file @
82ad9295
(
function
()
{
'use strict'
;
describe
(
'VideoPlayer completion'
,
function
()
{
var
state
,
oldOTBD
;
beforeEach
(
function
()
{
oldOTBD
=
window
.
onTouchBasedDevice
;
window
.
onTouchBasedDevice
=
jasmine
.
createSpy
(
'onTouchBasedDevice'
)
.
and
.
returnValue
(
null
);
state
=
jasmine
.
initializePlayer
({
recordedYoutubeIsAvailable
:
true
,
completionEnabled
:
true
,
publishCompletionUrl
:
'https://example.com/publish_completion_url'
});
state
.
completionHandler
.
completeAfterTime
=
20
;
});
afterEach
(
function
()
{
$
(
'source'
).
remove
();
window
.
onTouchBasedDevice
=
oldOTBD
;
state
.
storage
.
clear
();
if
(
state
.
videoPlayer
)
{
state
.
videoPlayer
.
destroy
();
}
});
it
(
'calls the completion api when marking an object complete'
,
function
()
{
state
.
completionHandler
.
markCompletion
(
Date
.
now
());
expect
(
$
.
ajax
).
toHaveBeenCalledWith
({
url
:
state
.
config
.
publishCompletionUrl
,
type
:
'POST'
,
contentType
:
'application/json'
,
dataType
:
'json'
,
data
:
JSON
.
stringify
({
completion
:
1.0
}),
success
:
jasmine
.
any
(
Function
),
error
:
jasmine
.
any
(
Function
)
});
expect
(
state
.
completionHandler
.
isComplete
).
toEqual
(
true
);
});
it
(
'calls the completion api on the LMS when the time updates'
,
function
()
{
spyOn
(
state
.
completionHandler
,
'markCompletion'
).
and
.
callThrough
();
state
.
el
.
trigger
(
'timeupdate'
,
24.0
);
expect
(
state
.
completionHandler
.
markCompletion
).
toHaveBeenCalled
();
state
.
completionHandler
.
markCompletion
.
calls
.
reset
();
// But the handler is not called again after the block is completed.
state
.
el
.
trigger
(
'timeupdate'
,
30.0
);
expect
(
state
.
completionHandler
.
markCompletion
).
not
.
toHaveBeenCalled
();
});
it
(
'calls the completion api on the LMS when the video ends'
,
function
()
{
spyOn
(
state
.
completionHandler
,
'markCompletion'
).
and
.
callThrough
();
state
.
el
.
trigger
(
'ended'
);
expect
(
state
.
completionHandler
.
markCompletion
).
toHaveBeenCalled
();
});
});
}).
call
(
this
);
common/lib/xmodule/xmodule/js/spec/video/html5_video_spec.js
View file @
82ad9295
...
@@ -219,7 +219,7 @@
...
@@ -219,7 +219,7 @@
}).
done
(
done
);
}).
done
(
done
);
});
});
it
(
'set new inc
c
orrect values'
,
function
()
{
it
(
'set new incorrect values'
,
function
()
{
var
seek
=
state
.
videoPlayer
.
player
.
video
.
currentTime
;
var
seek
=
state
.
videoPlayer
.
player
.
video
.
currentTime
;
state
.
videoPlayer
.
player
.
seekTo
(
-
50
);
state
.
videoPlayer
.
player
.
seekTo
(
-
50
);
expect
(
state
.
videoPlayer
.
player
.
getCurrentTime
()).
toBe
(
seek
);
expect
(
state
.
videoPlayer
.
player
.
getCurrentTime
()).
toBe
(
seek
);
...
...
common/lib/xmodule/xmodule/js/src/video/09_completion.js
0 → 100644
View file @
82ad9295
(
function
(
define
)
{
'use strict'
;
/**
* Completion handler
* @exports video/09_completion.js
* @constructor
* @param {Object} state The object containing the state of the video
* @return {jquery Promise}
*/
define
(
'video/09_completion.js'
,
[],
function
()
{
var
VideoCompletionHandler
=
function
(
state
)
{
if
(
!
(
this
instanceof
VideoCompletionHandler
))
{
return
new
VideoCompletionHandler
(
state
);
}
this
.
state
=
state
;
this
.
state
.
completionHandler
=
this
;
this
.
initialize
();
return
$
.
Deferred
().
resolve
().
promise
();
};
VideoCompletionHandler
.
prototype
=
{
/** Tears down the VideoCompletionHandler.
*
* * Removes backreferences from this.state to this.
* * Turns off signal handlers.
*/
destroy
:
function
()
{
this
.
el
.
remove
();
this
.
el
.
off
(
'timeupdate.completion'
);
this
.
el
.
off
(
'ended.completion'
);
delete
this
.
state
.
completionHandler
;
},
/** Initializes the VideoCompletionHandler.
*
* This sets all the instance variables needed to perform
* completion calculations.
*/
initialize
:
function
()
{
// Attributes with "Time" in the name refer to the number of seconds since
// the beginning of the video, except for lastSentTime, which refers to a
// timestamp in seconds since the Unix epoch.
this
.
lastSentTime
=
undefined
;
this
.
isComplete
=
false
;
this
.
completionPercentage
=
this
.
state
.
config
.
completionPercentage
;
this
.
startTime
=
this
.
state
.
config
.
startTime
;
this
.
endTime
=
this
.
state
.
config
.
endTime
;
this
.
isEnabled
=
this
.
state
.
config
.
completionEnabled
;
if
(
this
.
endTime
)
{
this
.
completeAfterTime
=
this
.
calculateCompleteAfterTime
(
this
.
startTime
,
this
.
endTime
);
}
if
(
this
.
isEnabled
)
{
this
.
bindHandlers
();
}
},
/** Bind event handler callbacks.
*
* When ended is triggered, mark the video complete
* unconditionally.
*
* When timeupdate is triggered, check to see if the user has
* passed the completeAfterTime in the video, and if so, mark the
* video complete.
*
* When destroy is triggered, clean up outstanding resources.
*/
bindHandlers
:
function
()
{
var
self
=
this
;
/** Event handler to check if the video is complete, and submit
* a completion if it is.
*
* If the timeupdate handler doesn't fire after the required
* percentage, this will catch any fully complete videos.
*/
this
.
state
.
el
.
on
(
'ended.completion'
,
function
()
{
self
.
handleEnded
();
});
/** Event handler to check video progress, and mark complete if
* greater than completionPercentage
*/
this
.
state
.
el
.
on
(
'timeupdate.completion'
,
function
(
ev
,
currentTime
)
{
self
.
handleTimeUpdate
(
currentTime
);
});
/** Event handler to clean up resources when the video player
* is destroyed.
*/
this
.
state
.
el
.
off
(
'destroy'
,
this
.
destroy
);
},
/** Handler to call when the ended event is triggered */
handleEnded
:
function
()
{
if
(
this
.
isComplete
)
{
return
;
}
this
.
markCompletion
();
},
/** Handler to call when a timeupdate event is triggered */
handleTimeUpdate
:
function
(
currentTime
)
{
var
duration
;
if
(
this
.
isComplete
)
{
return
;
}
if
(
this
.
lastSentTime
!==
undefined
&&
currentTime
-
this
.
lastSentTime
<
this
.
repostDelaySeconds
())
{
// Throttle attempts to submit in case of network issues
return
;
}
if
(
this
.
completeAfterTime
===
undefined
)
{
// Duration is not available at initialization time
duration
=
this
.
state
.
videoPlayer
.
duration
();
if
(
!
duration
)
{
// duration is not yet set. Wait for another event,
// or fall back to 'ended' handler.
return
;
}
this
.
completeAfterTime
=
this
.
calculateCompleteAfterTime
(
this
.
startTime
,
duration
);
}
if
(
currentTime
>
this
.
completeAfterTime
)
{
this
.
markCompletion
(
currentTime
);
}
},
/** Submit completion to the LMS */
markCompletion
:
function
(
currentTime
)
{
var
self
=
this
;
var
errmsg
;
this
.
isComplete
=
true
;
this
.
lastSentTime
=
currentTime
;
if
(
this
.
state
.
config
.
publishCompletionUrl
)
{
$
.
ajax
({
type
:
'POST'
,
url
:
this
.
state
.
config
.
publishCompletionUrl
,
contentType
:
'application/json'
,
dataType
:
'json'
,
data
:
JSON
.
stringify
({
completion
:
1.0
}),
success
:
function
()
{
self
.
state
.
el
.
off
(
'timeupdate.completion'
);
self
.
state
.
el
.
off
(
'ended.completion'
);
},
error
:
function
(
xhr
)
{
/* eslint-disable no-console */
self
.
complete
=
false
;
errmsg
=
'Failed to submit completion'
;
if
(
xhr
.
responseJSON
!==
undefined
)
{
errmsg
+=
': '
+
xhr
.
responseJSON
.
error
;
}
console
.
warn
(
errmsg
);
/* eslint-enable no-console */
}
});
}
else
{
/* eslint-disable no-console */
console
.
warn
(
'publishCompletionUrl not defined'
);
/* eslint-enable no-console */
}
},
/** Determine what point in the video (in seconds from the
* beginning) counts as complete.
*/
calculateCompleteAfterTime
:
function
(
startTime
,
endTime
)
{
return
startTime
+
(
endTime
-
startTime
)
*
this
.
completionPercentage
;
},
/** How many seconds to wait after a POST fails to try again. */
repostDelaySeconds
:
function
()
{
return
3.0
;
}
};
return
VideoCompletionHandler
;
});
}(
RequireJS
.
define
));
common/lib/xmodule/xmodule/js/src/video/10_main.js
View file @
82ad9295
/* globals _ */
(
function
(
require
,
$
)
{
(
function
(
require
,
$
)
{
'use strict'
;
'use strict'
;
// In the case when the Video constructor will be called before RequireJS finishes loading all of the Video
// In the case when the Video constructor will be called before RequireJS finishes loading all of the Video
...
@@ -15,9 +16,9 @@
...
@@ -15,9 +16,9 @@
// If mock function was called with second parameter set to truthy value, we invoke the real `window.Video`
// If mock function was called with second parameter set to truthy value, we invoke the real `window.Video`
// on all the stored elements so far.
// on all the stored elements so far.
if
(
processTempCallStack
)
{
if
(
processTempCallStack
)
{
$
.
each
(
tempCallStack
,
function
(
index
,
el
ement
)
{
$
.
each
(
tempCallStack
,
function
(
index
,
el
)
{
// By now, `window.Video` is the real constructor.
// By now, `window.Video` is the real constructor.
window
.
Video
(
el
ement
);
window
.
Video
(
el
);
});
});
return
;
return
;
...
@@ -54,6 +55,7 @@
...
@@ -54,6 +55,7 @@
'video/09_events_plugin.js'
,
'video/09_events_plugin.js'
,
'video/09_events_bumper_plugin.js'
,
'video/09_events_bumper_plugin.js'
,
'video/09_poster.js'
,
'video/09_poster.js'
,
'video/09_completion.js'
,
'video/10_commands.js'
,
'video/10_commands.js'
,
'video/095_video_context_menu.js'
'video/095_video_context_menu.js'
],
],
...
@@ -61,8 +63,8 @@
...
@@ -61,8 +63,8 @@
VideoStorage
,
initialize
,
FocusGrabber
,
VideoAccessibleMenu
,
VideoControl
,
VideoFullScreen
,
VideoStorage
,
initialize
,
FocusGrabber
,
VideoAccessibleMenu
,
VideoControl
,
VideoFullScreen
,
VideoQualityControl
,
VideoProgressSlider
,
VideoVolumeControl
,
VideoSpeedControl
,
VideoCaption
,
VideoQualityControl
,
VideoProgressSlider
,
VideoVolumeControl
,
VideoSpeedControl
,
VideoCaption
,
VideoPlayPlaceholder
,
VideoPlayPauseControl
,
VideoPlaySkipControl
,
VideoSkipControl
,
VideoBumper
,
VideoPlayPlaceholder
,
VideoPlayPauseControl
,
VideoPlaySkipControl
,
VideoSkipControl
,
VideoBumper
,
VideoSaveStatePlugin
,
VideoEventsPlugin
,
VideoEventsBumperPlugin
,
VideoPoster
,
VideoCommands
,
VideoSaveStatePlugin
,
VideoEventsPlugin
,
VideoEventsBumperPlugin
,
VideoPoster
,
VideoContextMenu
VideoCo
mpletionHandler
,
VideoCommands
,
VideoCo
ntextMenu
)
{
)
{
var
youtubeXhr
=
null
,
var
youtubeXhr
=
null
,
oldVideo
=
window
.
Video
;
oldVideo
=
window
.
Video
;
...
@@ -75,9 +77,10 @@
...
@@ -75,9 +77,10 @@
mainVideoModules
=
[
FocusGrabber
,
VideoControl
,
VideoPlayPlaceholder
,
mainVideoModules
=
[
FocusGrabber
,
VideoControl
,
VideoPlayPlaceholder
,
VideoPlayPauseControl
,
VideoProgressSlider
,
VideoSpeedControl
,
VideoVolumeControl
,
VideoPlayPauseControl
,
VideoProgressSlider
,
VideoSpeedControl
,
VideoVolumeControl
,
VideoQualityControl
,
VideoFullScreen
,
VideoCaption
,
VideoCommands
,
VideoContextMenu
,
VideoQualityControl
,
VideoFullScreen
,
VideoCaption
,
VideoCommands
,
VideoContextMenu
,
VideoSaveStatePlugin
,
VideoEventsPlugin
],
VideoSaveStatePlugin
,
VideoEventsPlugin
,
VideoCompletionHandler
],
bumperVideoModules
=
[
VideoControl
,
VideoPlaySkipControl
,
VideoSkipControl
,
bumperVideoModules
=
[
VideoControl
,
VideoPlaySkipControl
,
VideoSkipControl
,
VideoVolumeControl
,
VideoCaption
,
VideoCommands
,
VideoSaveStatePlugin
,
VideoEventsBumperPlugin
],
VideoVolumeControl
,
VideoCaption
,
VideoCommands
,
VideoSaveStatePlugin
,
VideoEventsBumperPlugin
,
VideoCompletionHandler
],
state
=
{
state
=
{
el
:
el
,
el
:
el
,
id
:
id
,
id
:
id
,
...
@@ -104,10 +107,10 @@
...
@@ -104,10 +107,10 @@
return
bumperState
;
return
bumperState
;
};
};
var
player
=
function
(
s
tate
)
{
var
player
=
function
(
innerS
tate
)
{
return
function
()
{
return
function
()
{
_
.
extend
(
s
tate
.
metadata
,
{
autoplay
:
true
,
focusFirstControl
:
true
});
_
.
extend
(
innerS
tate
.
metadata
,
{
autoplay
:
true
,
focusFirstControl
:
true
});
initialize
(
s
tate
,
element
);
initialize
(
innerS
tate
,
element
);
};
};
};
};
...
@@ -120,8 +123,8 @@
...
@@ -120,8 +123,8 @@
new
VideoPoster
(
el
,
{
new
VideoPoster
(
el
,
{
poster
:
el
.
data
(
'poster'
),
poster
:
el
.
data
(
'poster'
),
onClick
:
_
.
once
(
function
()
{
onClick
:
_
.
once
(
function
()
{
var
mainVideoPlayer
=
player
(
state
)
,
var
mainVideoPlayer
=
player
(
state
)
;
bumper
,
bumperState
;
var
bumper
,
bumperState
;
if
(
storage
.
getItem
(
'isBumperShown'
))
{
if
(
storage
.
getItem
(
'isBumperShown'
))
{
mainVideoPlayer
();
mainVideoPlayer
();
}
else
{
}
else
{
...
...
common/lib/xmodule/xmodule/video_module/bumper_utils.py
View file @
82ad9295
"""
"""
Utils for video bumper
Utils for video bumper
"""
"""
from
collections
import
OrderedDict
import
copy
import
copy
import
json
import
json
import
pytz
import
logging
import
logging
from
collections
import
OrderedDict
from
datetime
import
datetime
,
timedelta
from
datetime
import
datetime
,
timedelta
from
django.conf
import
settings
from
django.conf
import
settings
import
pytz
from
.video_utils
import
set_query_parameter
from
.video_utils
import
set_query_parameter
...
@@ -137,6 +137,9 @@ def bumper_metadata(video, sources):
...
@@ -137,6 +137,9 @@ def bumper_metadata(video, sources):
'transcriptAvailableTranslationsUrl'
:
set_query_parameter
(
'transcriptAvailableTranslationsUrl'
:
set_query_parameter
(
video
.
runtime
.
handler_url
(
video
,
'transcript'
,
'available_translations'
)
.
rstrip
(
'/?'
),
'is_bumper'
,
1
video
.
runtime
.
handler_url
(
video
,
'transcript'
,
'available_translations'
)
.
rstrip
(
'/?'
),
'is_bumper'
,
1
),
),
'publishCompletionUrl'
:
set_query_parameter
(
video
.
runtime
.
handler_url
(
video
,
'publish_completion'
,
''
)
.
rstrip
(
'?'
),
'is_bumper'
,
1
),
})
})
return
metadata
return
metadata
common/lib/xmodule/xmodule/video_module/video_handlers.py
View file @
82ad9295
...
@@ -13,6 +13,7 @@ from datetime import datetime
...
@@ -13,6 +13,7 @@ from datetime import datetime
from
webob
import
Response
from
webob
import
Response
from
xblock.core
import
XBlock
from
xblock.core
import
XBlock
from
xblock.exceptions
import
JsonHandlerError
from
xmodule.exceptions
import
NotFoundError
from
xmodule.exceptions
import
NotFoundError
from
xmodule.fields
import
RelativeTime
from
xmodule.fields
import
RelativeTime
...
@@ -202,6 +203,33 @@ class VideoStudentViewHandlers(object):
...
@@ -202,6 +203,33 @@ class VideoStudentViewHandlers(object):
)
)
return
response
return
response
@XBlock.json_handler
def
publish_completion
(
self
,
data
,
dispatch
):
# pylint: disable=unused-argument
"""
Entry point for completion for student_view.
Parameters:
data: JSON dict:
key: "completion"
value: float in range [0.0, 1.0]
dispatch: Ignored.
Return value: JSON response (200 on success, 400 for malformed data)
"""
completion_service
=
self
.
runtime
.
service
(
self
,
'completion'
)
if
completion_service
is
None
:
raise
JsonHandlerError
(
500
,
u"No completion service found"
)
elif
not
completion_service
.
completion_tracking_enabled
():
raise
JsonHandlerError
(
404
,
u"Completion tracking is not enabled and API calls are unexpected"
)
if
not
isinstance
(
data
[
'completion'
],
(
int
,
float
)):
message
=
u"Invalid completion value {}. Must be a float in range [0.0, 1.0]"
raise
JsonHandlerError
(
400
,
message
.
format
(
data
[
'completion'
]))
elif
not
0.0
<=
data
[
'completion'
]
<=
1.0
:
message
=
u"Invalid completion value {}. Must be in range [0.0, 1.0]"
raise
JsonHandlerError
(
400
,
message
.
format
(
data
[
'completion'
]))
self
.
runtime
.
publish
(
self
,
"completion"
,
data
)
return
{
"result"
:
"ok"
}
@XBlock.handler
@XBlock.handler
def
transcript
(
self
,
request
,
dispatch
):
def
transcript
(
self
,
request
,
dispatch
):
"""
"""
...
@@ -282,6 +310,8 @@ class VideoStudentViewHandlers(object):
...
@@ -282,6 +310,8 @@ class VideoStudentViewHandlers(object):
transcript_content
,
transcript_filename
,
transcript_mime_type
=
self
.
get_transcript
(
transcript_content
,
transcript_filename
,
transcript_mime_type
=
self
.
get_transcript
(
transcripts
,
transcript_format
=
self
.
transcript_download_format
,
lang
=
lang
transcripts
,
transcript_format
=
self
.
transcript_download_format
,
lang
=
lang
)
)
except
(
KeyError
,
UnicodeDecodeError
):
return
Response
(
status
=
404
)
except
(
ValueError
,
NotFoundError
):
except
(
ValueError
,
NotFoundError
):
response
=
Response
(
status
=
404
)
response
=
Response
(
status
=
404
)
# Check for transcripts in edx-val as a last resort if corresponding feature is enabled.
# Check for transcripts in edx-val as a last resort if corresponding feature is enabled.
...
@@ -319,8 +349,6 @@ class VideoStudentViewHandlers(object):
...
@@ -319,8 +349,6 @@ class VideoStudentViewHandlers(object):
response
.
content_type
=
Transcript
.
mime_types
[
self
.
transcript_download_format
]
response
.
content_type
=
Transcript
.
mime_types
[
self
.
transcript_download_format
]
return
response
return
response
except
(
KeyError
,
UnicodeDecodeError
):
return
Response
(
status
=
404
)
else
:
else
:
response
=
Response
(
response
=
Response
(
transcript_content
,
transcript_content
,
...
...
common/lib/xmodule/xmodule/video_module/video_module.py
View file @
82ad9295
...
@@ -27,6 +27,7 @@ from opaque_keys.edx.locator import AssetLocator
...
@@ -27,6 +27,7 @@ from opaque_keys.edx.locator import AssetLocator
from
openedx.core.djangoapps.video_config.models
import
HLSPlaybackEnabledFlag
from
openedx.core.djangoapps.video_config.models
import
HLSPlaybackEnabledFlag
from
openedx.core.lib.cache_utils
import
memoize_in_request_cache
from
openedx.core.lib.cache_utils
import
memoize_in_request_cache
from
openedx.core.lib.license
import
LicenseMixin
from
openedx.core.lib.license
import
LicenseMixin
from
xblock.completable
import
XBlockCompletionMode
from
xblock.core
import
XBlock
from
xblock.core
import
XBlock
from
xblock.fields
import
ScopeIds
from
xblock.fields
import
ScopeIds
from
xblock.runtime
import
KvsFieldData
from
xblock.runtime
import
KvsFieldData
...
@@ -97,7 +98,7 @@ log = logging.getLogger(__name__)
...
@@ -97,7 +98,7 @@ log = logging.getLogger(__name__)
_
=
lambda
text
:
text
_
=
lambda
text
:
text
@XBlock.wants
(
'settings'
)
@XBlock.wants
(
'settings'
,
'completion'
)
class
VideoModule
(
VideoFields
,
VideoTranscriptsMixin
,
VideoStudentViewHandlers
,
XModule
,
LicenseMixin
):
class
VideoModule
(
VideoFields
,
VideoTranscriptsMixin
,
VideoStudentViewHandlers
,
XModule
,
LicenseMixin
):
"""
"""
XML source example:
XML source example:
...
@@ -110,6 +111,9 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
...
@@ -110,6 +111,9 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
<source src=".../mit-3091x/M-3091X-FA12-L21-3_100.ogv"/>
<source src=".../mit-3091x/M-3091X-FA12-L21-3_100.ogv"/>
</video>
</video>
"""
"""
has_custom_completion
=
True
completion_mode
=
XBlockCompletionMode
.
COMPLETABLE
video_time
=
0
video_time
=
0
icon_class
=
'video'
icon_class
=
'video'
...
@@ -150,9 +154,10 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
...
@@ -150,9 +154,10 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
resource_string
(
module
,
'js/src/video/09_events_plugin.js'
),
resource_string
(
module
,
'js/src/video/09_events_plugin.js'
),
resource_string
(
module
,
'js/src/video/09_events_bumper_plugin.js'
),
resource_string
(
module
,
'js/src/video/09_events_bumper_plugin.js'
),
resource_string
(
module
,
'js/src/video/09_poster.js'
),
resource_string
(
module
,
'js/src/video/09_poster.js'
),
resource_string
(
module
,
'js/src/video/09_completion.js'
),
resource_string
(
module
,
'js/src/video/095_video_context_menu.js'
),
resource_string
(
module
,
'js/src/video/095_video_context_menu.js'
),
resource_string
(
module
,
'js/src/video/10_commands.js'
),
resource_string
(
module
,
'js/src/video/10_commands.js'
),
resource_string
(
module
,
'js/src/video/10_main.js'
)
resource_string
(
module
,
'js/src/video/10_main.js'
)
,
]
]
}
}
css
=
{
'scss'
:
[
css
=
{
'scss'
:
[
...
@@ -327,6 +332,12 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
...
@@ -327,6 +332,12 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
edx_video_id
=
self
.
edx_video_id
.
strip
()
edx_video_id
=
self
.
edx_video_id
.
strip
()
)
)
completion_service
=
self
.
runtime
.
service
(
self
,
'completion'
)
if
completion_service
:
completion_enabled
=
completion_service
.
completion_tracking_enabled
()
else
:
completion_enabled
=
False
metadata
=
{
metadata
=
{
'saveStateUrl'
:
self
.
system
.
ajax_url
+
'/save_user_state'
,
'saveStateUrl'
:
self
.
system
.
ajax_url
+
'/save_user_state'
,
'autoplay'
:
settings
.
FEATURES
.
get
(
'AUTOPLAY_VIDEOS'
,
False
),
'autoplay'
:
settings
.
FEATURES
.
get
(
'AUTOPLAY_VIDEOS'
,
False
),
...
@@ -345,6 +356,8 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
...
@@ -345,6 +356,8 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
'savedVideoPosition'
:
self
.
saved_video_position
.
total_seconds
(),
'savedVideoPosition'
:
self
.
saved_video_position
.
total_seconds
(),
'start'
:
self
.
start_time
.
total_seconds
(),
'start'
:
self
.
start_time
.
total_seconds
(),
'end'
:
self
.
end_time
.
total_seconds
(),
'end'
:
self
.
end_time
.
total_seconds
(),
'completionEnabled'
:
completion_enabled
,
'completionPercentage'
:
settings
.
COMPLETION_VIDEO_COMPLETE_PERCENTAGE
,
'transcriptLanguage'
:
transcript_language
,
'transcriptLanguage'
:
transcript_language
,
'transcriptLanguages'
:
sorted_languages
,
'transcriptLanguages'
:
sorted_languages
,
'ytTestTimeout'
:
settings
.
YOUTUBE
[
'TEST_TIMEOUT'
],
'ytTestTimeout'
:
settings
.
YOUTUBE
[
'TEST_TIMEOUT'
],
...
@@ -358,18 +371,19 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
...
@@ -358,18 +371,19 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
'transcriptAvailableTranslationsUrl'
:
self
.
runtime
.
handler_url
(
'transcriptAvailableTranslationsUrl'
:
self
.
runtime
.
handler_url
(
self
,
'transcript'
,
'available_translations'
self
,
'transcript'
,
'available_translations'
)
.
rstrip
(
'/?'
),
)
.
rstrip
(
'/?'
),
'publishCompletionUrl'
:
self
.
runtime
.
handler_url
(
self
,
'publish_completion'
,
''
)
.
rstrip
(
'?'
),
## For now, the option "data-autohide-html5" is hard coded. This option
## either enables or disables autohiding of controls and captions on mouse
# For now, the option "data-autohide-html5" is hard coded. This option
## inactivity. If set to true, controls and captions will autohide for
# either enables or disables autohiding of controls and captions on mouse
## HTML5 sources (non-YouTube) after a period of mouse inactivity over the
# inactivity. If set to true, controls and captions will autohide for
## whole video. When the mouse moves (or a key is pressed while any part of
# HTML5 sources (non-YouTube) after a period of mouse inactivity over the
## the video player is focused), the captions and controls will be shown
# whole video. When the mouse moves (or a key is pressed while any part of
## once again.
# the video player is focused), the captions and controls will be shown
##
# once again.
## There is no option in the "Advanced Editor" to set this option. However,
#
## this option will have an effect if changed to "True". The code on
# There is no option in the "Advanced Editor" to set this option. However,
## front-end exists.
# this option will have an effect if changed to "True". The code on
# front-end exists.
'autohideHtml5'
:
False
,
'autohideHtml5'
:
False
,
# This is the server's guess at whether youtube is available for
# This is the server's guess at whether youtube is available for
...
@@ -399,8 +413,7 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
...
@@ -399,8 +413,7 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
return
self
.
system
.
render_template
(
'video.html'
,
context
)
return
self
.
system
.
render_template
(
'video.html'
,
context
)
@XBlock.wants
(
"request_cache"
)
@XBlock.wants
(
"request_cache"
,
"settings"
,
"completion"
)
@XBlock.wants
(
"settings"
)
class
VideoDescriptor
(
VideoFields
,
VideoTranscriptsMixin
,
VideoStudioViewHandlers
,
class
VideoDescriptor
(
VideoFields
,
VideoTranscriptsMixin
,
VideoStudioViewHandlers
,
TabsEditingDescriptor
,
EmptyDataRawDescriptor
,
LicenseMixin
):
TabsEditingDescriptor
,
EmptyDataRawDescriptor
,
LicenseMixin
):
"""
"""
...
@@ -408,6 +421,7 @@ class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandler
...
@@ -408,6 +421,7 @@ class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandler
"""
"""
module_class
=
VideoModule
module_class
=
VideoModule
transcript
=
module_attr
(
'transcript'
)
transcript
=
module_attr
(
'transcript'
)
publish_completion
=
module_attr
(
'publish_completion'
)
show_in_read_only_mode
=
True
show_in_read_only_mode
=
True
...
...
lms/djangoapps/courseware/tests/test_video_handlers.py
View file @
82ad9295
...
@@ -183,6 +183,14 @@ class TestVideo(BaseTestXmodule):
...
@@ -183,6 +183,14 @@ class TestVideo(BaseTestXmodule):
response
=
self
.
item_descriptor
.
handle_ajax
(
'save_user_state'
,
{
u'demoo�'
:
"sample"
})
response
=
self
.
item_descriptor
.
handle_ajax
(
'save_user_state'
,
{
u'demoo�'
:
"sample"
})
self
.
assertEqual
(
json
.
loads
(
response
)[
'success'
],
True
)
self
.
assertEqual
(
json
.
loads
(
response
)[
'success'
],
True
)
def
get_handler_url
(
self
,
handler
,
suffix
):
"""
Return the URL for the specified handler on self.item_descriptor.
"""
return
self
.
item_descriptor
.
xmodule_runtime
.
handler_url
(
self
.
item_descriptor
,
handler
,
suffix
)
.
rstrip
(
'/?'
)
def
tearDown
(
self
):
def
tearDown
(
self
):
_clear_assets
(
self
.
item_descriptor
.
location
)
_clear_assets
(
self
.
item_descriptor
.
location
)
super
(
TestVideo
,
self
)
.
tearDown
()
super
(
TestVideo
,
self
)
.
tearDown
()
...
...
lms/djangoapps/courseware/tests/test_video_mongo.py
View file @
82ad9295
...
@@ -84,14 +84,13 @@ class TestVideoYouTube(TestVideo):
...
@@ -84,14 +84,13 @@ class TestVideoYouTube(TestVideo):
'ytApiUrl'
:
'https://www.youtube.com/iframe_api'
,
'ytApiUrl'
:
'https://www.youtube.com/iframe_api'
,
'ytMetadataUrl'
:
'https://www.googleapis.com/youtube/v3/videos/'
,
'ytMetadataUrl'
:
'https://www.googleapis.com/youtube/v3/videos/'
,
'ytKey'
:
None
,
'ytKey'
:
None
,
'transcriptTranslationUrl'
:
self
.
item_descriptor
.
xmodule_runtime
.
handler_url
(
'transcriptTranslationUrl'
:
self
.
get_handler_url
(
'transcript'
,
'translation/__lang__'
),
self
.
item_descriptor
,
'transcript'
,
'translation/__lang__'
'transcriptAvailableTranslationsUrl'
:
self
.
get_handler_url
(
'transcript'
,
'available_translations'
),
)
.
rstrip
(
'/?'
),
'transcriptAvailableTranslationsUrl'
:
self
.
item_descriptor
.
xmodule_runtime
.
handler_url
(
self
.
item_descriptor
,
'transcript'
,
'available_translations'
)
.
rstrip
(
'/?'
),
'autohideHtml5'
:
False
,
'autohideHtml5'
:
False
,
'recordedYoutubeIsAvailable'
:
True
,
'recordedYoutubeIsAvailable'
:
True
,
'completionEnabled'
:
False
,
'completionPercentage'
:
0.95
,
'publishCompletionUrl'
:
self
.
get_handler_url
(
'publish_completion'
,
''
),
})),
})),
'track'
:
None
,
'track'
:
None
,
'transcript_download_format'
:
u'srt'
,
'transcript_download_format'
:
u'srt'
,
...
@@ -165,14 +164,13 @@ class TestVideoNonYouTube(TestVideo):
...
@@ -165,14 +164,13 @@ class TestVideoNonYouTube(TestVideo):
'ytApiUrl'
:
'https://www.youtube.com/iframe_api'
,
'ytApiUrl'
:
'https://www.youtube.com/iframe_api'
,
'ytMetadataUrl'
:
'https://www.googleapis.com/youtube/v3/videos/'
,
'ytMetadataUrl'
:
'https://www.googleapis.com/youtube/v3/videos/'
,
'ytKey'
:
None
,
'ytKey'
:
None
,
'transcriptTranslationUrl'
:
self
.
item_descriptor
.
xmodule_runtime
.
handler_url
(
'transcriptTranslationUrl'
:
self
.
get_handler_url
(
'transcript'
,
'translation/__lang__'
),
self
.
item_descriptor
,
'transcript'
,
'translation/__lang__'
'transcriptAvailableTranslationsUrl'
:
self
.
get_handler_url
(
'transcript'
,
'available_translations'
),
)
.
rstrip
(
'/?'
),
'transcriptAvailableTranslationsUrl'
:
self
.
item_descriptor
.
xmodule_runtime
.
handler_url
(
self
.
item_descriptor
,
'transcript'
,
'available_translations'
)
.
rstrip
(
'/?'
),
'autohideHtml5'
:
False
,
'autohideHtml5'
:
False
,
'recordedYoutubeIsAvailable'
:
True
,
'recordedYoutubeIsAvailable'
:
True
,
'completionEnabled'
:
False
,
'completionPercentage'
:
0.95
,
'publishCompletionUrl'
:
self
.
get_handler_url
(
'publish_completion'
,
''
),
})),
})),
'track'
:
None
,
'track'
:
None
,
'transcript_download_format'
:
u'srt'
,
'transcript_download_format'
:
u'srt'
,
...
@@ -223,16 +221,24 @@ class TestGetHtmlMethod(BaseTestXmodule):
...
@@ -223,16 +221,24 @@ class TestGetHtmlMethod(BaseTestXmodule):
'ytApiUrl'
:
'https://www.youtube.com/iframe_api'
,
'ytApiUrl'
:
'https://www.youtube.com/iframe_api'
,
'ytMetadataUrl'
:
'https://www.googleapis.com/youtube/v3/videos/'
,
'ytMetadataUrl'
:
'https://www.googleapis.com/youtube/v3/videos/'
,
'ytKey'
:
None
,
'ytKey'
:
None
,
'transcriptTranslationUrl'
:
self
.
item_descriptor
.
xmodule_runtime
.
handler_url
(
'transcriptTranslationUrl'
:
self
.
get_handler_url
(
'transcript'
,
'translation/__lang__'
),
self
.
item_descriptor
,
'transcript'
,
'translation/__lang__'
'transcriptAvailableTranslationsUrl'
:
self
.
get_handler_url
(
'transcript'
,
'available_translations'
),
)
.
rstrip
(
'/?'
),
'transcriptAvailableTranslationsUrl'
:
self
.
item_descriptor
.
xmodule_runtime
.
handler_url
(
self
.
item_descriptor
,
'transcript'
,
'available_translations'
)
.
rstrip
(
'/?'
),
'autohideHtml5'
:
False
,
'autohideHtml5'
:
False
,
'recordedYoutubeIsAvailable'
:
True
,
'recordedYoutubeIsAvailable'
:
True
,
'completionEnabled'
:
False
,
'completionPercentage'
:
0.95
,
'publishCompletionUrl'
:
self
.
get_handler_url
(
'publish_completion'
,
''
),
})
})
def
get_handler_url
(
self
,
handler
,
suffix
):
"""
Return the URL for the specified handler on the block represented by
self.item_descriptor.
"""
return
self
.
item_descriptor
.
xmodule_runtime
.
handler_url
(
self
.
item_descriptor
,
handler
,
suffix
)
.
rstrip
(
'/?'
)
def
test_get_html_track
(
self
):
def
test_get_html_track
(
self
):
SOURCE_XML
=
"""
SOURCE_XML
=
"""
<video show_captions="true"
<video show_captions="true"
...
@@ -318,20 +324,15 @@ class TestGetHtmlMethod(BaseTestXmodule):
...
@@ -318,20 +324,15 @@ class TestGetHtmlMethod(BaseTestXmodule):
)
)
self
.
initialize_module
(
data
=
DATA
)
self
.
initialize_module
(
data
=
DATA
)
track_url
=
self
.
item_descriptor
.
xmodule_runtime
.
handler_url
(
track_url
=
self
.
get_handler_url
(
'transcript'
,
'download'
)
self
.
item_descriptor
,
'transcript'
,
'download'
)
.
rstrip
(
'/?'
)
context
=
self
.
item_descriptor
.
render
(
STUDENT_VIEW
)
.
content
context
=
self
.
item_descriptor
.
render
(
STUDENT_VIEW
)
.
content
metadata
.
update
({
metadata
.
update
({
'transcriptLanguages'
:
{
"en"
:
"English"
}
if
not
data
[
'transcripts'
]
else
{
"uk"
:
u'Українська'
},
'transcriptLanguages'
:
{
"en"
:
"English"
}
if
not
data
[
'transcripts'
]
else
{
"uk"
:
u'Українська'
},
'transcriptLanguage'
:
u'en'
if
not
data
[
'transcripts'
]
or
data
.
get
(
'sub'
)
else
u'uk'
,
'transcriptLanguage'
:
u'en'
if
not
data
[
'transcripts'
]
or
data
.
get
(
'sub'
)
else
u'uk'
,
'transcriptTranslationUrl'
:
self
.
item_descriptor
.
xmodule_runtime
.
handler_url
(
'transcriptTranslationUrl'
:
self
.
get_handler_url
(
'transcript'
,
'translation/__lang__'
),
self
.
item_descriptor
,
'transcript'
,
'translation/__lang__'
'transcriptAvailableTranslationsUrl'
:
self
.
get_handler_url
(
'transcript'
,
'available_translations'
),
)
.
rstrip
(
'/?'
),
'publishCompletionUrl'
:
self
.
get_handler_url
(
'publish_completion'
,
''
),
'transcriptAvailableTranslationsUrl'
:
self
.
item_descriptor
.
xmodule_runtime
.
handler_url
(
self
.
item_descriptor
,
'transcript'
,
'available_translations'
)
.
rstrip
(
'/?'
),
'saveStateUrl'
:
self
.
item_descriptor
.
xmodule_runtime
.
ajax_url
+
'/save_user_state'
,
'saveStateUrl'
:
self
.
item_descriptor
.
xmodule_runtime
.
ajax_url
+
'/save_user_state'
,
'sub'
:
data
[
'sub'
],
'sub'
:
data
[
'sub'
],
})
})
...
@@ -441,12 +442,9 @@ class TestGetHtmlMethod(BaseTestXmodule):
...
@@ -441,12 +442,9 @@ class TestGetHtmlMethod(BaseTestXmodule):
expected_context
=
dict
(
initial_context
)
expected_context
=
dict
(
initial_context
)
expected_context
[
'metadata'
]
.
update
({
expected_context
[
'metadata'
]
.
update
({
'transcriptTranslationUrl'
:
self
.
item_descriptor
.
xmodule_runtime
.
handler_url
(
'transcriptTranslationUrl'
:
self
.
get_handler_url
(
'transcript'
,
'translation/__lang__'
),
self
.
item_descriptor
,
'transcript'
,
'translation/__lang__'
'transcriptAvailableTranslationsUrl'
:
self
.
get_handler_url
(
'transcript'
,
'available_translations'
),
)
.
rstrip
(
'/?'
),
'publishCompletionUrl'
:
self
.
get_handler_url
(
'publish_completion'
,
''
),
'transcriptAvailableTranslationsUrl'
:
self
.
item_descriptor
.
xmodule_runtime
.
handler_url
(
self
.
item_descriptor
,
'transcript'
,
'available_translations'
)
.
rstrip
(
'/?'
),
'saveStateUrl'
:
self
.
item_descriptor
.
xmodule_runtime
.
ajax_url
+
'/save_user_state'
,
'saveStateUrl'
:
self
.
item_descriptor
.
xmodule_runtime
.
ajax_url
+
'/save_user_state'
,
'sources'
:
data
[
'result'
]
.
get
(
'sources'
,
[]),
'sources'
:
data
[
'result'
]
.
get
(
'sources'
,
[]),
})
})
...
@@ -581,12 +579,9 @@ class TestGetHtmlMethod(BaseTestXmodule):
...
@@ -581,12 +579,9 @@ class TestGetHtmlMethod(BaseTestXmodule):
expected_context
=
dict
(
initial_context
)
expected_context
=
dict
(
initial_context
)
expected_context
[
'metadata'
]
.
update
({
expected_context
[
'metadata'
]
.
update
({
'transcriptTranslationUrl'
:
self
.
item_descriptor
.
xmodule_runtime
.
handler_url
(
'transcriptTranslationUrl'
:
self
.
get_handler_url
(
'transcript'
,
'translation/__lang__'
),
self
.
item_descriptor
,
'transcript'
,
'translation/__lang__'
'transcriptAvailableTranslationsUrl'
:
self
.
get_handler_url
(
'transcript'
,
'available_translations'
),
)
.
rstrip
(
'/?'
),
'publishCompletionUrl'
:
self
.
get_handler_url
(
'publish_completion'
,
''
),
'transcriptAvailableTranslationsUrl'
:
self
.
item_descriptor
.
xmodule_runtime
.
handler_url
(
self
.
item_descriptor
,
'transcript'
,
'available_translations'
)
.
rstrip
(
'/?'
),
'saveStateUrl'
:
self
.
item_descriptor
.
xmodule_runtime
.
ajax_url
+
'/save_user_state'
,
'saveStateUrl'
:
self
.
item_descriptor
.
xmodule_runtime
.
ajax_url
+
'/save_user_state'
,
'sources'
:
data
[
'result'
][
'sources'
],
'sources'
:
data
[
'result'
][
'sources'
],
})
})
...
@@ -742,12 +737,9 @@ class TestGetHtmlMethod(BaseTestXmodule):
...
@@ -742,12 +737,9 @@ class TestGetHtmlMethod(BaseTestXmodule):
# expected_context, expected context to be returned by get_html
# expected_context, expected context to be returned by get_html
expected_context
=
dict
(
initial_context
)
expected_context
=
dict
(
initial_context
)
expected_context
[
'metadata'
]
.
update
({
expected_context
[
'metadata'
]
.
update
({
'transcriptTranslationUrl'
:
self
.
item_descriptor
.
xmodule_runtime
.
handler_url
(
'transcriptTranslationUrl'
:
self
.
get_handler_url
(
'transcript'
,
'translation/__lang__'
),
self
.
item_descriptor
,
'transcript'
,
'translation/__lang__'
'transcriptAvailableTranslationsUrl'
:
self
.
get_handler_url
(
'transcript'
,
'available_translations'
),
)
.
rstrip
(
'/?'
),
'publishCompletionUrl'
:
self
.
get_handler_url
(
'publish_completion'
,
''
),
'transcriptAvailableTranslationsUrl'
:
self
.
item_descriptor
.
xmodule_runtime
.
handler_url
(
self
.
item_descriptor
,
'transcript'
,
'available_translations'
)
.
rstrip
(
'/?'
),
'saveStateUrl'
:
self
.
item_descriptor
.
xmodule_runtime
.
ajax_url
+
'/save_user_state'
,
'saveStateUrl'
:
self
.
item_descriptor
.
xmodule_runtime
.
ajax_url
+
'/save_user_state'
,
'sources'
:
data
[
'result'
][
'sources'
],
'sources'
:
data
[
'result'
][
'sources'
],
})
})
...
@@ -854,12 +846,9 @@ class TestGetHtmlMethod(BaseTestXmodule):
...
@@ -854,12 +846,9 @@ class TestGetHtmlMethod(BaseTestXmodule):
context
=
self
.
item_descriptor
.
render
(
'student_view'
)
.
content
context
=
self
.
item_descriptor
.
render
(
'student_view'
)
.
content
expected_context
=
dict
(
initial_context
)
expected_context
=
dict
(
initial_context
)
expected_context
[
'metadata'
]
.
update
({
expected_context
[
'metadata'
]
.
update
({
'transcriptTranslationUrl'
:
self
.
item_descriptor
.
xmodule_runtime
.
handler_url
(
'transcriptTranslationUrl'
:
self
.
get_handler_url
(
'transcript'
,
'translation/__lang__'
),
self
.
item_descriptor
,
'transcript'
,
'translation/__lang__'
'transcriptAvailableTranslationsUrl'
:
self
.
get_handler_url
(
'transcript'
,
'available_translations'
),
)
.
rstrip
(
'/?'
),
'publishCompletionUrl'
:
self
.
get_handler_url
(
'publish_completion'
,
''
),
'transcriptAvailableTranslationsUrl'
:
self
.
item_descriptor
.
xmodule_runtime
.
handler_url
(
self
.
item_descriptor
,
'transcript'
,
'available_translations'
)
.
rstrip
(
'/?'
),
'saveStateUrl'
:
self
.
item_descriptor
.
xmodule_runtime
.
ajax_url
+
'/save_user_state'
,
'saveStateUrl'
:
self
.
item_descriptor
.
xmodule_runtime
.
ajax_url
+
'/save_user_state'
,
'sources'
:
data
[
'result'
]
.
get
(
'sources'
,
[]),
'sources'
:
data
[
'result'
]
.
get
(
'sources'
,
[]),
})
})
...
@@ -1774,14 +1763,13 @@ class TestVideoWithBumper(TestVideo):
...
@@ -1774,14 +1763,13 @@ class TestVideoWithBumper(TestVideo):
'transcriptLanguage'
:
'en'
,
'transcriptLanguage'
:
'en'
,
'transcriptLanguages'
:
{
'en'
:
'English'
},
'transcriptLanguages'
:
{
'en'
:
'English'
},
'transcriptTranslationUrl'
:
video_utils
.
set_query_parameter
(
'transcriptTranslationUrl'
:
video_utils
.
set_query_parameter
(
self
.
item_descriptor
.
xmodule_runtime
.
handler_url
(
self
.
get_handler_url
(
'transcript'
,
'translation/__lang__'
),
'is_bumper'
,
1
self
.
item_descriptor
,
'transcript'
,
'translation/__lang__'
)
.
rstrip
(
'/?'
),
'is_bumper'
,
1
),
),
'transcriptAvailableTranslationsUrl'
:
video_utils
.
set_query_parameter
(
'transcriptAvailableTranslationsUrl'
:
video_utils
.
set_query_parameter
(
self
.
item_descriptor
.
xmodule_runtime
.
handler_url
(
self
.
get_handler_url
(
'transcript'
,
'available_translations'
),
'is_bumper'
,
1
self
.
item_descriptor
,
'transcript'
,
'available_translations'
),
)
.
rstrip
(
'/?'
),
'is_bumper'
,
1
"publishCompletionUrl"
:
video_utils
.
set_query_parameter
(
self
.
get_handler_url
(
'publish_completion'
,
''
),
'is_bumper'
,
1
),
),
})),
})),
'cdn_eval'
:
False
,
'cdn_eval'
:
False
,
...
@@ -1811,14 +1799,13 @@ class TestVideoWithBumper(TestVideo):
...
@@ -1811,14 +1799,13 @@ class TestVideoWithBumper(TestVideo):
'ytApiUrl'
:
'https://www.youtube.com/iframe_api'
,
'ytApiUrl'
:
'https://www.youtube.com/iframe_api'
,
'ytMetadataUrl'
:
'https://www.googleapis.com/youtube/v3/videos/'
,
'ytMetadataUrl'
:
'https://www.googleapis.com/youtube/v3/videos/'
,
'ytKey'
:
None
,
'ytKey'
:
None
,
'transcriptTranslationUrl'
:
self
.
item_descriptor
.
xmodule_runtime
.
handler_url
(
'transcriptTranslationUrl'
:
self
.
get_handler_url
(
'transcript'
,
'translation/__lang__'
),
self
.
item_descriptor
,
'transcript'
,
'translation/__lang__'
'transcriptAvailableTranslationsUrl'
:
self
.
get_handler_url
(
'transcript'
,
'available_translations'
),
)
.
rstrip
(
'/?'
),
'transcriptAvailableTranslationsUrl'
:
self
.
item_descriptor
.
xmodule_runtime
.
handler_url
(
self
.
item_descriptor
,
'transcript'
,
'available_translations'
)
.
rstrip
(
'/?'
),
'autohideHtml5'
:
False
,
'autohideHtml5'
:
False
,
'recordedYoutubeIsAvailable'
:
True
,
'recordedYoutubeIsAvailable'
:
True
,
'completionEnabled'
:
False
,
'completionPercentage'
:
0.95
,
'publishCompletionUrl'
:
self
.
get_handler_url
(
'publish_completion'
,
''
),
})),
})),
'track'
:
None
,
'track'
:
None
,
'transcript_download_format'
:
u'srt'
,
'transcript_download_format'
:
u'srt'
,
...
...
lms/envs/aws.py
View file @
82ad9295
...
@@ -1088,6 +1088,15 @@ EDX_PLATFORM_REVISION = ENV_TOKENS.get('EDX_PLATFORM_REVISION', EDX_PLATFORM_REV
...
@@ -1088,6 +1088,15 @@ EDX_PLATFORM_REVISION = ENV_TOKENS.get('EDX_PLATFORM_REVISION', EDX_PLATFORM_REV
# Allow extra middleware classes to be added to the app through configuration.
# Allow extra middleware classes to be added to the app through configuration.
MIDDLEWARE_CLASSES
.
extend
(
ENV_TOKENS
.
get
(
'EXTRA_MIDDLEWARE_CLASSES'
,
[]))
MIDDLEWARE_CLASSES
.
extend
(
ENV_TOKENS
.
get
(
'EXTRA_MIDDLEWARE_CLASSES'
,
[]))
########################## Settings for Completion API #####################
# Once a user has watched this percentage of a video, mark it as complete:
# (0.0 = 0%, 1.0 = 100%)
COMPLETION_VIDEO_COMPLETE_PERCENTAGE
=
ENV_TOKENS
.
get
(
'COMPLETION_VIDEO_COMPLETE_PERCENTAGE'
,
COMPLETION_VIDEO_COMPLETE_PERCENTAGE
,
)
########################## Derive Any Derived Settings #######################
########################## Derive Any Derived Settings #######################
derive_settings
(
__name__
)
derive_settings
(
__name__
)
lms/envs/common.py
View file @
82ad9295
...
@@ -3456,3 +3456,9 @@ ACE_ROUTING_KEY = LOW_PRIORITY_QUEUE
...
@@ -3456,3 +3456,9 @@ ACE_ROUTING_KEY = LOW_PRIORITY_QUEUE
# Initialize to 'unknown', but read from JSON in aws.py
# Initialize to 'unknown', but read from JSON in aws.py
EDX_PLATFORM_REVISION
=
'unknown'
EDX_PLATFORM_REVISION
=
'unknown'
############## Settings for Completion API #########################
# Once a user has watched this percentage of a video, mark it as complete:
# (0.0 = 0%, 1.0 = 100%)
COMPLETION_VIDEO_COMPLETE_PERCENTAGE
=
0.95
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment