add support to enable/disable hls as primary playback
EDUCATOR-2714
This commit is contained in:
@@ -1065,6 +1065,37 @@ function(VideoPlayer, HLS, _) {
|
||||
}).always(done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('HLS Primary Playback', function() {
|
||||
beforeEach(function() {
|
||||
spyOn(window.YT, 'Player').and.callThrough();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
YT.Player.calls.reset();
|
||||
});
|
||||
|
||||
it('loads youtube if flag is disabled', function() {
|
||||
state = jasmine.initializePlayer('video_all.html', {
|
||||
prioritizeHls: false,
|
||||
streams: '0.5:7tqY6eQzVhE,1.0:cogebirgzzM,1.5:abcdefghijkl'
|
||||
});
|
||||
expect(state.config.prioritizeHls).toBeFalsy();
|
||||
expect(YT.Player).toHaveBeenCalled();
|
||||
expect(state.videoPlayer.player.hls).toBeUndefined();
|
||||
});
|
||||
|
||||
it('does not load youtube if flag is enabled', function() {
|
||||
state = jasmine.initializePlayer('video_all.html', {
|
||||
prioritizeHls: true,
|
||||
streams: '0.5:7tqY6eQzVhE,1.0:cogebirgzzM,1.5:abcdefghijkl',
|
||||
sources: ['/base/fixtures/test.mp4', '/base/fixtures/test.webm', '/base/fixtures/hls/hls.m3u8']
|
||||
});
|
||||
expect(state.config.prioritizeHls).toBeTruthy();
|
||||
expect(YT.Player).not.toHaveBeenCalled();
|
||||
expect(state.videoPlayer.player.hls).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}(require, define));
|
||||
|
||||
@@ -585,7 +585,8 @@ function(VideoPlayer, i18n, moment, _) {
|
||||
|
||||
_setConfigurations(this);
|
||||
|
||||
if (!(_parseYouTubeIDs(this))) {
|
||||
// If `prioritizeHls` is set to true than `hls` is the primary playback
|
||||
if (this.config.prioritizeHls || !(_parseYouTubeIDs(this))) {
|
||||
// If we do not have YouTube ID's, try parsing HTML5 video sources.
|
||||
if (!_prepareHTML5Video(this)) {
|
||||
__dfd__.reject();
|
||||
|
||||
@@ -22,9 +22,11 @@ from operator import itemgetter
|
||||
from pkg_resources import resource_string
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.functional import cached_property
|
||||
from lxml import etree
|
||||
from opaque_keys.edx.locator import AssetLocator
|
||||
from openedx.core.djangoapps.video_config.models import HLSPlaybackEnabledFlag
|
||||
from openedx.core.djangoapps.waffle_utils import WaffleFlagNamespace, CourseWaffleFlag
|
||||
from openedx.core.lib.cache_utils import memoize_in_request_cache
|
||||
from openedx.core.lib.license import LicenseMixin
|
||||
from xblock.completable import XBlockCompletionMode
|
||||
@@ -98,6 +100,11 @@ log = logging.getLogger(__name__)
|
||||
# `django.utils.translation.ugettext_noop` because Django cannot be imported in this file
|
||||
_ = lambda text: text
|
||||
|
||||
# Waffle flags namespace for videos
|
||||
WAFFLE_VIDEOS_NAMESPACE = 'videos'
|
||||
|
||||
# Waffle flag to enable/disable hls as primary playback
|
||||
DEPRECATE_YOUTUBE = 'deprecate_youtube'
|
||||
|
||||
EXPORT_IMPORT_COURSE_DIR = u'course'
|
||||
EXPORT_IMPORT_STATIC_DIR = u'static'
|
||||
@@ -182,6 +189,36 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
|
||||
sorted_languages = OrderedDict(sorted_languages)
|
||||
return track_url, transcript_language, sorted_languages
|
||||
|
||||
@cached_property
|
||||
def youtube_deprecated(self):
|
||||
"""
|
||||
Return True if youtube is deprecated and hls as primary playback is enabled else False
|
||||
"""
|
||||
# Return False if `hls` playback feature is disabled.
|
||||
if not HLSPlaybackEnabledFlag.feature_enabled(self.location.course_key):
|
||||
return False
|
||||
|
||||
# check if hls as primary playback is enabled for this course
|
||||
return CourseWaffleFlag(
|
||||
WaffleFlagNamespace(name=WAFFLE_VIDEOS_NAMESPACE),
|
||||
DEPRECATE_YOUTUBE
|
||||
).is_enabled(self.location.course_key)
|
||||
|
||||
def prioritize_hls(self, youtube_streams, html5_sources):
|
||||
"""
|
||||
Decide whether hls can be prioritized as primary playback or not.
|
||||
|
||||
If both the youtube and hls sources are present then make decision on flag
|
||||
If only either youtube or hls is present then play whichever is present
|
||||
"""
|
||||
yt_present = bool(youtube_streams.strip())
|
||||
hls_present = any(source for source in html5_sources if source.strip().endswith('.m3u8'))
|
||||
|
||||
if yt_present and hls_present:
|
||||
return self.youtube_deprecated
|
||||
|
||||
return False
|
||||
|
||||
def get_html(self):
|
||||
|
||||
track_status = (self.download_track and self.track)
|
||||
@@ -363,6 +400,7 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
|
||||
# this user, based on what was recorded the last time we saw the
|
||||
# user, and defaulting to True.
|
||||
'recordedYoutubeIsAvailable': self.youtube_is_available,
|
||||
'prioritizeHls': self.prioritize_hls(self.youtube_streams, sources),
|
||||
}
|
||||
|
||||
bumperize(self)
|
||||
|
||||
@@ -4,36 +4,38 @@ Video xmodule tests in mongo.
|
||||
"""
|
||||
|
||||
import json
|
||||
import shutil
|
||||
from collections import OrderedDict
|
||||
from tempfile import mkdtemp
|
||||
from uuid import uuid4
|
||||
|
||||
from tempfile import mkdtemp
|
||||
import shutil
|
||||
import ddt
|
||||
from django.conf import settings
|
||||
from django.core.files import File
|
||||
from django.core.files.base import ContentFile
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
from fs.osfs import OSFS
|
||||
from fs.path import combine
|
||||
from edxval.api import (
|
||||
ValCannotCreateError,
|
||||
ValVideoNotFoundError,
|
||||
create_video_transcript,
|
||||
create_or_update_video_transcript,
|
||||
create_profile,
|
||||
create_video,
|
||||
create_video_transcript,
|
||||
get_video_info,
|
||||
get_video_transcript,
|
||||
get_video_transcript_data
|
||||
)
|
||||
from edxval.utils import create_file_in_fs
|
||||
from fs.osfs import OSFS
|
||||
from fs.path import combine
|
||||
from lxml import etree
|
||||
from mock import MagicMock, Mock, patch
|
||||
from path import Path as path
|
||||
|
||||
from openedx.core.lib.tests import attr
|
||||
from openedx.core.djangoapps.waffle_utils.models import WaffleFlagCourseOverrideModel
|
||||
from waffle.testutils import override_flag
|
||||
from xmodule.contentstore.content import StaticContent
|
||||
from xmodule.exceptions import NotFoundError
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
@@ -43,7 +45,12 @@ from xmodule.tests.test_import import DummySystem
|
||||
from xmodule.tests.test_video import VideoDescriptorTestBase, instantiate_descriptor
|
||||
from xmodule.video_module import VideoDescriptor, bumper_utils, rewrite_video_url, video_utils
|
||||
from xmodule.video_module.transcripts_utils import Transcript, save_to_store, subs_filename
|
||||
from xmodule.video_module.video_module import EXPORT_IMPORT_STATIC_DIR, EXPORT_IMPORT_COURSE_DIR
|
||||
from xmodule.video_module.video_module import (
|
||||
EXPORT_IMPORT_COURSE_DIR,
|
||||
EXPORT_IMPORT_STATIC_DIR,
|
||||
DEPRECATE_YOUTUBE,
|
||||
WAFFLE_VIDEOS_NAMESPACE,
|
||||
)
|
||||
from xmodule.x_module import STUDENT_VIEW
|
||||
|
||||
from .helpers import BaseTestXmodule
|
||||
@@ -55,6 +62,8 @@ MODULESTORES = {
|
||||
ModuleStoreEnum.Type.split: TEST_DATA_SPLIT_MODULESTORE,
|
||||
}
|
||||
|
||||
DEPRECATE_YOUTUBE_FLAG = '{}.{}'.format(WAFFLE_VIDEOS_NAMESPACE, DEPRECATE_YOUTUBE)
|
||||
|
||||
TRANSCRIPT_FILE_SRT_DATA = u"""
|
||||
1
|
||||
00:00:14,370 --> 00:00:16,530
|
||||
@@ -116,6 +125,7 @@ class TestVideoYouTube(TestVideo):
|
||||
'completionEnabled': False,
|
||||
'completionPercentage': 0.95,
|
||||
'publishCompletionUrl': self.get_handler_url('publish_completion', ''),
|
||||
'prioritizeHls': False,
|
||||
})),
|
||||
'track': None,
|
||||
'transcript_download_format': u'srt',
|
||||
@@ -197,6 +207,7 @@ class TestVideoNonYouTube(TestVideo):
|
||||
'completionEnabled': False,
|
||||
'completionPercentage': 0.95,
|
||||
'publishCompletionUrl': self.get_handler_url('publish_completion', ''),
|
||||
'prioritizeHls': False,
|
||||
})),
|
||||
'track': None,
|
||||
'transcript_download_format': u'srt',
|
||||
@@ -219,6 +230,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
'''
|
||||
Make sure that `get_html` works correctly.
|
||||
'''
|
||||
maxDiff = None
|
||||
CATEGORY = "video"
|
||||
DATA = SOURCE_XML
|
||||
METADATA = {}
|
||||
@@ -254,6 +266,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
'completionEnabled': False,
|
||||
'completionPercentage': 0.95,
|
||||
'publishCompletionUrl': self.get_handler_url('publish_completion', ''),
|
||||
'prioritizeHls': False,
|
||||
})
|
||||
|
||||
def get_handler_url(self, handler, suffix):
|
||||
@@ -986,6 +999,70 @@ class TestGetHtmlMethod(BaseTestXmodule):
|
||||
|
||||
self.assertIn("\'poster\': \'null\'", context)
|
||||
|
||||
@patch('xmodule.video_module.video_module.HLSPlaybackEnabledFlag.feature_enabled', Mock(return_value=False))
|
||||
def test_hls_primary_playback_on_toggling_hls_feature(self):
|
||||
"""
|
||||
Verify that `prioritize_hls` is set to `False` if `HLSPlaybackEnabledFlag` is disabled.
|
||||
"""
|
||||
video_xml = '<video display_name="Video" download_video="true" edx_video_id="12345-67890">[]</video>'
|
||||
self.initialize_module(data=video_xml)
|
||||
context = self.item_descriptor.render(STUDENT_VIEW).content
|
||||
self.assertIn('"prioritizeHls": false', context)
|
||||
|
||||
@ddt.data(
|
||||
{
|
||||
'course_override': WaffleFlagCourseOverrideModel.ALL_CHOICES.on,
|
||||
'waffle_enabled': False,
|
||||
'youtube': '3_yD_cEKoCk',
|
||||
'hls': ['https://hls.com/hls.m3u8'],
|
||||
'result': 'true'
|
||||
},
|
||||
{
|
||||
'course_override': WaffleFlagCourseOverrideModel.ALL_CHOICES.on,
|
||||
'waffle_enabled': False,
|
||||
'youtube': '',
|
||||
'hls': ['https://hls.com/hls.m3u8'],
|
||||
'result': 'false'
|
||||
},
|
||||
{
|
||||
'course_override': WaffleFlagCourseOverrideModel.ALL_CHOICES.on,
|
||||
'waffle_enabled': False,
|
||||
'youtube': '',
|
||||
'hls': [],
|
||||
'result': 'false'
|
||||
},
|
||||
{
|
||||
'course_override': WaffleFlagCourseOverrideModel.ALL_CHOICES.on,
|
||||
'waffle_enabled': False,
|
||||
'youtube': '3_yD_cEKoCk',
|
||||
'hls': [],
|
||||
'result': 'false'
|
||||
},
|
||||
{
|
||||
'course_override': WaffleFlagCourseOverrideModel.ALL_CHOICES.off,
|
||||
'waffle_enabled': True,
|
||||
'youtube': '3_yD_cEKoCk',
|
||||
'hls': ['https://hls.com/hls.m3u8'],
|
||||
'result': 'false'
|
||||
},
|
||||
)
|
||||
@patch('xmodule.video_module.video_module.HLSPlaybackEnabledFlag.feature_enabled', Mock(return_value=True))
|
||||
def test_deprecate_youtube_course_waffle_flag(self, data):
|
||||
"""
|
||||
Tests various combinations of a `prioritize_hls` flag being set in waffle and overridden for a course.
|
||||
"""
|
||||
metadata = {
|
||||
'html5_sources': ['http://youtu.be/3_yD_cEKoCk.mp4'] + data['hls'],
|
||||
}
|
||||
video_xml = '<video display_name="Video" edx_video_id="12345-67890" youtube_id_1_0="{}">[]</video>'.format(
|
||||
data['youtube']
|
||||
)
|
||||
with patch.object(WaffleFlagCourseOverrideModel, 'override_value', return_value=data['course_override']):
|
||||
with override_flag(DEPRECATE_YOUTUBE_FLAG, active=data['waffle_enabled']):
|
||||
self.initialize_module(data=video_xml, metadata=metadata)
|
||||
context = self.item_descriptor.render(STUDENT_VIEW).content
|
||||
self.assertIn('"prioritizeHls": {}'.format(data['result']), context)
|
||||
|
||||
|
||||
@attr(shard=7)
|
||||
@ddt.ddt
|
||||
@@ -2085,6 +2162,7 @@ class TestVideoWithBumper(TestVideo):
|
||||
'completionEnabled': False,
|
||||
'completionPercentage': 0.95,
|
||||
'publishCompletionUrl': self.get_handler_url('publish_completion', ''),
|
||||
'prioritizeHls': False,
|
||||
})),
|
||||
'track': None,
|
||||
'transcript_download_format': u'srt',
|
||||
@@ -2107,6 +2185,7 @@ class TestAutoAdvanceVideo(TestVideo):
|
||||
"""
|
||||
Tests the server side of video auto-advance.
|
||||
"""
|
||||
maxDiff = None
|
||||
CATEGORY = "video"
|
||||
METADATA = {}
|
||||
# Use temporary FEATURES in this test without affecting the original
|
||||
@@ -2160,6 +2239,7 @@ class TestAutoAdvanceVideo(TestVideo):
|
||||
'completionEnabled': False,
|
||||
'completionPercentage': 0.95,
|
||||
'publishCompletionUrl': self.get_handler_url('publish_completion', ''),
|
||||
'prioritizeHls': False,
|
||||
})),
|
||||
'track': None,
|
||||
'transcript_download_format': u'srt',
|
||||
|
||||
Reference in New Issue
Block a user