add support to enable/disable hls as primary playback

EDUCATOR-2714
This commit is contained in:
muhammad-ammar
2018-07-30 14:46:46 +05:00
committed by M. Rehan
parent 7698a277cd
commit 809175d4cf
4 changed files with 157 additions and 7 deletions

View File

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

View File

@@ -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();

View File

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

View File

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