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:
Daniel Clemente
2017-07-06 22:54:24 -06:00
committed by Jillian Vogel
parent 98fd05b4ee
commit ecf01d1b52
22 changed files with 556 additions and 14 deletions

View File

@@ -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),
)