Adds a course option to auto-advance videos.
If enabled for a course, as soon as the video ends, the next unit or subsection will be loaded, and if it contains a single video, that video will be played. Course authors can enable the setting for a course, but learners can toggle the setting on or off once it's enabled on the course.
This commit is contained in:
committed by
Jillian Vogel
parent
98fd05b4ee
commit
ecf01d1b52
@@ -54,6 +54,7 @@ class TestVideoYouTube(TestVideo):
|
||||
sources = [u'example.mp4', u'example.webm']
|
||||
|
||||
expected_context = {
|
||||
'autoadvance_enabled': False,
|
||||
'branding_info': None,
|
||||
'license': None,
|
||||
'bumper_metadata': 'null',
|
||||
@@ -64,6 +65,7 @@ class TestVideoYouTube(TestVideo):
|
||||
'handout': None,
|
||||
'id': self.item_descriptor.location.html_id(),
|
||||
'metadata': json.dumps(OrderedDict({
|
||||
'autoAdvance': False,
|
||||
'saveStateUrl': self.item_descriptor.xmodule_runtime.ajax_url + '/save_user_state',
|
||||
'autoplay': False,
|
||||
'streams': '0.75:jNCf2gIqpeE,1.00:ZwkTiUPN0mg,1.25:rsq9auxASqI,1.50:kMyNdzVHHgg',
|
||||
@@ -134,6 +136,7 @@ class TestVideoNonYouTube(TestVideo):
|
||||
sources = [u'example.mp4', u'example.webm']
|
||||
|
||||
expected_context = {
|
||||
'autoadvance_enabled': False,
|
||||
'branding_info': None,
|
||||
'license': None,
|
||||
'bumper_metadata': 'null',
|
||||
@@ -144,6 +147,7 @@ class TestVideoNonYouTube(TestVideo):
|
||||
'handout': None,
|
||||
'id': self.item_descriptor.location.html_id(),
|
||||
'metadata': json.dumps(OrderedDict({
|
||||
'autoAdvance': False,
|
||||
'saveStateUrl': self.item_descriptor.xmodule_runtime.ajax_url + '/save_user_state',
|
||||
'autoplay': False,
|
||||
'streams': '1.00:3_yD_cEKoCk',
|
||||
@@ -201,6 +205,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
super(TestGetHtmlMethod, self).setUp()
|
||||
self.setup_course()
|
||||
self.default_metadata_dict = OrderedDict({
|
||||
'autoAdvance': False,
|
||||
'saveStateUrl': '',
|
||||
'autoplay': settings.FEATURES.get('AUTOPLAY_VIDEOS', True),
|
||||
'streams': '1.00:3_yD_cEKoCk',
|
||||
@@ -293,6 +298,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
sources = [u'example.mp4', u'example.webm']
|
||||
|
||||
expected_context = {
|
||||
'autoadvance_enabled': False,
|
||||
'branding_info': None,
|
||||
'license': None,
|
||||
'bumper_metadata': 'null',
|
||||
@@ -411,6 +417,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
]
|
||||
|
||||
initial_context = {
|
||||
'autoadvance_enabled': False,
|
||||
'branding_info': None,
|
||||
'license': None,
|
||||
'bumper_metadata': 'null',
|
||||
@@ -533,6 +540,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
metadata['autoplay'] = False
|
||||
metadata['sources'] = ""
|
||||
initial_context = {
|
||||
'autoadvance_enabled': False,
|
||||
'branding_info': None,
|
||||
'license': None,
|
||||
'bumper_metadata': 'null',
|
||||
@@ -704,6 +712,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
metadata = self.default_metadata_dict
|
||||
metadata['sources'] = ""
|
||||
initial_context = {
|
||||
'autoadvance_enabled': False,
|
||||
'branding_info': None,
|
||||
'license': None,
|
||||
'bumper_metadata': 'null',
|
||||
@@ -810,6 +819,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
]
|
||||
|
||||
initial_context = {
|
||||
'autoadvance_enabled': False,
|
||||
'branding_info': {
|
||||
'logo_src': 'http://www.xuetangx.com/static/images/logo.png',
|
||||
'logo_tag': 'Video hosted by XuetangX.com',
|
||||
@@ -1705,7 +1715,8 @@ class TestVideoWithBumper(TestVideo):
|
||||
"""
|
||||
CATEGORY = "video"
|
||||
METADATA = {}
|
||||
FEATURES = settings.FEATURES
|
||||
# Use temporary FEATURES in this test without affecting the original
|
||||
FEATURES = dict(settings.FEATURES)
|
||||
|
||||
@patch('xmodule.video_module.bumper_utils.get_bumper_settings')
|
||||
def test_is_bumper_enabled(self, get_bumper_settings):
|
||||
@@ -1753,6 +1764,7 @@ class TestVideoWithBumper(TestVideo):
|
||||
content = self.item_descriptor.render(STUDENT_VIEW).content
|
||||
sources = [u'example.mp4', u'example.webm']
|
||||
expected_context = {
|
||||
'autoadvance_enabled': False,
|
||||
'branding_info': None,
|
||||
'license': None,
|
||||
'bumper_metadata': json.dumps(OrderedDict({
|
||||
@@ -1779,6 +1791,7 @@ class TestVideoWithBumper(TestVideo):
|
||||
'handout': None,
|
||||
'id': self.item_descriptor.location.html_id(),
|
||||
'metadata': json.dumps(OrderedDict({
|
||||
'autoAdvance': False,
|
||||
'saveStateUrl': self.item_descriptor.xmodule_runtime.ajax_url + '/save_user_state',
|
||||
'autoplay': False,
|
||||
'streams': '0.75:jNCf2gIqpeE,1.00:ZwkTiUPN0mg,1.25:rsq9auxASqI,1.50:kMyNdzVHHgg',
|
||||
@@ -1821,3 +1834,131 @@ class TestVideoWithBumper(TestVideo):
|
||||
|
||||
expected_content = self.item_descriptor.xmodule_runtime.render_template('video.html', expected_context)
|
||||
self.assertEqual(content, expected_content)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestAutoAdvanceVideo(TestVideo):
|
||||
"""
|
||||
Tests the server side of video auto-advance.
|
||||
"""
|
||||
CATEGORY = "video"
|
||||
METADATA = {}
|
||||
# Use temporary FEATURES in this test without affecting the original
|
||||
FEATURES = dict(settings.FEATURES)
|
||||
|
||||
def prepare_expected_context(self, autoadvanceenabled_flag, autoadvance_flag):
|
||||
"""
|
||||
Build a dictionary with data expected by some operations in this test.
|
||||
Only parameters related to auto-advance are variable, rest is fixed.
|
||||
"""
|
||||
context = {
|
||||
'autoadvance_enabled': autoadvanceenabled_flag,
|
||||
'branding_info': None,
|
||||
'license': None,
|
||||
'cdn_eval': False,
|
||||
'cdn_exp_group': None,
|
||||
'display_name': u'A Name',
|
||||
'download_video_link': u'example.mp4',
|
||||
'handout': None,
|
||||
'id': self.item_descriptor.location.html_id(),
|
||||
'bumper_metadata': 'null',
|
||||
'metadata': json.dumps(OrderedDict({
|
||||
'autoAdvance': autoadvance_flag,
|
||||
'saveStateUrl': self.item_descriptor.xmodule_runtime.ajax_url + '/save_user_state',
|
||||
'autoplay': False,
|
||||
'streams': '0.75:jNCf2gIqpeE,1.00:ZwkTiUPN0mg,1.25:rsq9auxASqI,1.50:kMyNdzVHHgg',
|
||||
'sub': 'a_sub_file.srt.sjson',
|
||||
'sources': [u'example.mp4', u'example.webm'],
|
||||
'duration': None,
|
||||
'poster': None,
|
||||
'captionDataDir': None,
|
||||
'showCaptions': 'true',
|
||||
'generalSpeed': 1.0,
|
||||
'speed': None,
|
||||
'savedVideoPosition': 0.0,
|
||||
'start': 3603.0,
|
||||
'end': 3610.0,
|
||||
'transcriptLanguage': 'en',
|
||||
'transcriptLanguages': OrderedDict({'en': 'English', 'uk': u'Українська'}),
|
||||
'ytTestTimeout': 1500,
|
||||
'ytApiUrl': 'https://www.youtube.com/iframe_api',
|
||||
'ytMetadataUrl': 'https://www.googleapis.com/youtube/v3/videos/',
|
||||
'ytKey': None,
|
||||
'transcriptTranslationUrl': self.item_descriptor.xmodule_runtime.handler_url(
|
||||
self.item_descriptor, 'transcript', 'translation/__lang__'
|
||||
).rstrip('/?'),
|
||||
'transcriptAvailableTranslationsUrl': self.item_descriptor.xmodule_runtime.handler_url(
|
||||
self.item_descriptor, 'transcript', 'available_translations'
|
||||
).rstrip('/?'),
|
||||
'autohideHtml5': False,
|
||||
'recordedYoutubeIsAvailable': True,
|
||||
'completionEnabled': False,
|
||||
'completionPercentage': 0.95,
|
||||
'publishCompletionUrl': self.get_handler_url('publish_completion', ''),
|
||||
})),
|
||||
'track': None,
|
||||
'transcript_download_format': u'srt',
|
||||
'transcript_download_formats_list': [
|
||||
{'display_name': 'SubRip (.srt) file', 'value': 'srt'},
|
||||
{'display_name': 'Text (.txt) file', 'value': 'txt'}
|
||||
],
|
||||
'poster': 'null'
|
||||
}
|
||||
return context
|
||||
|
||||
def assert_content_matches_expectations(self, autoadvanceenabled_must_be, autoadvance_must_be):
|
||||
"""
|
||||
Check (assert) that loading video.html produces content that corresponds
|
||||
to the passed context.
|
||||
Helper function to avoid code repetition.
|
||||
"""
|
||||
|
||||
with override_settings(FEATURES=self.FEATURES):
|
||||
content = self.item_descriptor.render(STUDENT_VIEW).content
|
||||
|
||||
expected_context = self.prepare_expected_context(
|
||||
autoadvanceenabled_flag=autoadvanceenabled_must_be,
|
||||
autoadvance_flag=autoadvance_must_be,
|
||||
)
|
||||
|
||||
with override_settings(FEATURES=self.FEATURES):
|
||||
expected_content = self.item_descriptor.xmodule_runtime.render_template('video.html', expected_context)
|
||||
|
||||
self.assertEqual(content, expected_content)
|
||||
|
||||
def change_course_setting_autoadvance(self, new_value):
|
||||
"""
|
||||
Change the .video_auto_advance course setting (a.k.a. advanced setting).
|
||||
This avoids doing .save(), and instead modifies the instance directly.
|
||||
Based on test code for video_bumper setting.
|
||||
"""
|
||||
# This first render is done to initialize the instance
|
||||
self.item_descriptor.render(STUDENT_VIEW)
|
||||
item_instance = self.item_descriptor.xmodule_runtime.xmodule_instance
|
||||
item_instance.video_auto_advance = new_value
|
||||
# After this step, render() should see the new value
|
||||
# e.g. use self.item_descriptor.render(STUDENT_VIEW).content
|
||||
|
||||
@ddt.data(
|
||||
(False, False),
|
||||
(False, True),
|
||||
(True, False),
|
||||
(True, True),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_is_autoadvance_available_and_enabled(self, global_setting, course_setting):
|
||||
"""
|
||||
Check that the autoadvance is not available when it is disabled via feature flag
|
||||
(ENABLE_AUTOADVANCE_VIDEOS set to False) or by the course setting.
|
||||
It checks that:
|
||||
- only when the feature flag and the course setting are True (at the same time)
|
||||
the controls are visible
|
||||
- in that case (when the controls are visible) the video will autoadvance
|
||||
(because that's the default), in other cases it won't
|
||||
"""
|
||||
self.FEATURES.update({"ENABLE_AUTOADVANCE_VIDEOS": global_setting})
|
||||
self.change_course_setting_autoadvance(new_value=course_setting)
|
||||
self.assert_content_matches_expectations(
|
||||
autoadvanceenabled_must_be=(global_setting and course_setting),
|
||||
autoadvance_must_be=(global_setting and course_setting),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user