feat: Add stream_priority for videos (#30767)

* feat: Add stream_priority for videos
This commit is contained in:
Moeez Zahid
2022-07-29 13:33:42 +05:00
committed by GitHub
parent 2af65d1b77
commit 3311225292
3 changed files with 252 additions and 0 deletions

View File

@@ -11,6 +11,7 @@ from .extra_fields import ExtraFieldsTransformer
from .navigation import BlockNavigationTransformer
from .student_view import StudentViewTransformer
from .video_urls import VideoBlockURLTransformer
from .video_stream_priority import VideoBlockStreamPriorityTransformer
class BlocksAPITransformer(BlockStructureTransformer):
@@ -23,6 +24,7 @@ class BlocksAPITransformer(BlockStructureTransformer):
BlockCountsTransformer
BlockDepthTransformer
BlockNavigationTransformer
VideoBlockStreamPriorityTransformer
ExtraFieldsTransformer
Note:
@@ -72,4 +74,5 @@ class BlocksAPITransformer(BlockStructureTransformer):
BlockDepthTransformer(self.depth).transform(usage_info, block_structure)
BlockNavigationTransformer(self.nav_depth).transform(usage_info, block_structure)
VideoBlockURLTransformer().transform(usage_info, block_structure)
VideoBlockStreamPriorityTransformer().transform(usage_info, block_structure)
ExtraFieldsTransformer().transform(usage_info, block_structure)

View File

@@ -0,0 +1,178 @@
"""
Tests for VideoBlockStreamPriorityTransformer.
"""
from unittest import mock
from openedx.core.djangoapps.content.block_structure.factory import BlockStructureFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore.tests.factories import ToyCourseFactory # lint-amnesty, pylint: disable=wrong-import-order
from ..student_view import StudentViewTransformer
from ..video_stream_priority import VideoBlockStreamPriorityTransformer
class TestVideoBlockStreamPriorityTransformer(ModuleStoreTestCase):
"""
Test the stream priority for videos using VideoBlockStreamPriorityTransformer.
"""
def setUp(self):
super().setUp()
self.course_key = ToyCourseFactory.create().id
self.course_usage_key = self.store.make_course_usage_key(self.course_key)
self.block_structure = BlockStructureFactory.create_from_modulestore(self.course_usage_key, self.store)
def get_pre_transform_data(self, block_key):
"""
Return the student view data before the transformation for given video block.
"""
video_block = self.block_structure.get_xblock(block_key)
return video_block.student_view_data()
def change_encoded_videos_presentation(self, encoded_videos):
"""
Relocate stream priority data in new dictionary for pre & post transformation
data comparison.
"""
stream_priorities = {}
for video_format, video_data in encoded_videos.items():
stream_priorities[video_format] = video_data['stream_priority']
return stream_priorities
def get_post_transform_data(self, block_key):
"""
Return the block's student view data after transformation.
"""
return self.block_structure.get_transformer_block_field(
block_key, StudentViewTransformer, StudentViewTransformer.STUDENT_VIEW_DATA
)
def collect_and_transform(self):
"""
Perform transformer operations.
"""
StudentViewTransformer.collect(self.block_structure)
self.block_structure._collect_requested_xblock_fields() # pylint: disable=protected-access
StudentViewTransformer(['video']).transform(
usage_info=None,
block_structure=self.block_structure,
)
VideoBlockStreamPriorityTransformer().transform(
usage_info=self.course_usage_key,
block_structure=self.block_structure,
)
@mock.patch('lms.djangoapps.course_blocks.usage_info.CourseUsageInfo')
@mock.patch('openedx.core.djangoapps.waffle_utils.CourseWaffleFlag.is_enabled')
@mock.patch('xmodule.video_module.VideoBlock.student_view_data')
def test_write_for_deprecated_youtube_flag_on(self, mock_video_data, deprecate_youtube_flag, usage_info):
"""
Test that video stream priority is written correctly with
videos.deprecate_youtube flag on.
"""
mock_video_data.return_value = {
'encoded_videos': {
'hls': {
'url': 'https://xyz123.cloudfront.net/XYZ123ABC.mp4',
'file_size': 0
},
'mobile_low': {
'url': 'https://1234a.cloudfront.net/A1234a.mp4',
'file_size': 0
},
'mobile_high': {
'url': 'https://1234ab.cloudfront.net/A1234ab.mp4',
'file_size': 0
},
'desktop_mp4': {
'url': 'https://1234abc.cloudfront.net/A1234abc.mp4',
'file_size': 0
},
'fallback': {
'url': 'https://1234abcd.cloudfront.net/A1234abcd.mp4',
'file_size': 0
},
'youtube': {
'url': 'https://1234abcde.cloudfront.net/A1234abcde.mp4',
'file_size': 0
}
},
'only_on_web': False
}
deprecate_youtube_flag.return_value = True
usage_info.return_value = {'course_key': self.course_key}
video_block_key = self.course_key.make_usage_key('video', 'sample_video')
self.collect_and_transform()
post_transform_data = self.get_post_transform_data(video_block_key)
post_transform_data = self.change_encoded_videos_presentation(post_transform_data['encoded_videos'])
for video_format, stream_priority in post_transform_data.items():
assert post_transform_data[video_format] == \
VideoBlockStreamPriorityTransformer.DEPRECATE_YOUTUBE_VIDEO_STREAM_PRIORITY[video_format]
@mock.patch('lms.djangoapps.course_blocks.usage_info.CourseUsageInfo')
@mock.patch('openedx.core.djangoapps.waffle_utils.CourseWaffleFlag.is_enabled')
@mock.patch('xmodule.video_module.VideoBlock.student_view_data')
def test_write_for_deprecated_youtube_flag_off(self, mock_video_data, deprecate_youtube_flag, usage_info):
"""
Test that video stream priority is written correctly with
videos.deprecate_youtube flag off.
"""
mock_video_data.return_value = {
'encoded_videos': {
'hls': {
'url': 'https://xyz123.cloudfront.net/XYZ123ABC.mp4',
'file_size': 0
},
'mobile_low': {
'url': 'https://1234a.cloudfront.net/A1234a.mp4',
'file_size': 0
},
'mobile_high': {
'url': 'https://1234ab.cloudfront.net/A1234ab.mp4',
'file_size': 0
},
'desktop_mp4': {
'url': 'https://1234abc.cloudfront.net/A1234abc.mp4',
'file_size': 0
},
'fallback': {
'url': 'https://1234abcd.cloudfront.net/A1234abcd.mp4',
'file_size': 0
},
'youtube': {
'url': 'https://1234abcde.cloudfront.net/A1234abcde.mp4',
'file_size': 0
}
},
'only_on_web': False
}
deprecate_youtube_flag.return_value = False
usage_info.return_value = {'course_key': self.course_key}
video_block_key = self.course_key.make_usage_key('video', 'sample_video')
self.collect_and_transform()
post_transform_data = self.get_post_transform_data(video_block_key)
post_transform_data = self.change_encoded_videos_presentation(post_transform_data['encoded_videos'])
for video_format, stream_priority in post_transform_data.items():
assert post_transform_data[video_format] == \
VideoBlockStreamPriorityTransformer.DEFAULT_VIDEO_STREAM_PRIORITY[video_format]
@mock.patch('xmodule.video_module.VideoBlock.student_view_data')
def test_no_priority_for_web_only_videos(self, mock_video_data):
"""
Verify no write attempt is made for the videos
available on web only.
"""
mock_video_data.return_value = {
'only_on_web': True
}
video_block_key = self.course_key.make_usage_key('video', 'sample_video')
pre_transform_data = self.get_pre_transform_data(video_block_key)
self.collect_and_transform()
post_transform_data = self.get_post_transform_data(video_block_key)
self.assertDictEqual(pre_transform_data, post_transform_data)

View File

@@ -0,0 +1,71 @@
"""
Video block stream priority Transformer
"""
from openedx.core.djangoapps.content.block_structure.transformer import BlockStructureTransformer
from openedx.core.djangoapps.video_pipeline.config.waffle import DEPRECATE_YOUTUBE
from .student_view import StudentViewTransformer
class VideoBlockStreamPriorityTransformer(BlockStructureTransformer):
"""
Transformer to add stream priority for encoded_videos.
If DEPRECATE_YOUTUBE waffle flag is on for a course, Youtube videos
have highest priority i.e. 0. Else, the default priority for videos
is as shown in DEFAULT_VIDEO_STREAM_PRIORITY below.
"""
WRITE_VERSION = 1
READ_VERSION = 1
DEFAULT_VIDEO_STREAM_PRIORITY = {
'hls': 0,
'mobile_low': 1,
'mobile_high': 2,
'desktop_mp4': 3,
'fallback': 4,
'youtube': 5,
}
DEPRECATE_YOUTUBE_VIDEO_STREAM_PRIORITY = {
'youtube': 0,
'hls': 1,
'mobile_low': 2,
'mobile_high': 3,
'desktop_mp4': 4,
'fallback': 5,
}
@classmethod
def name(cls):
return "blocks_api:video_stream_priority"
def transform(self, usage_info, block_structure):
"""
Write all the video blocks' stream priority.
For the encoded_videos dictionary, a field called stream_priority
will be added to all the available video blocks. Client end can use this
value to prioritise streaming for different video formats.
"""
for block_key in block_structure.topological_traversal(
filter_func=lambda block_key: block_key.block_type == 'video',
yield_descendants_of_unyielded=True,
):
student_view_data = block_structure.get_transformer_block_field(
block_key, StudentViewTransformer, StudentViewTransformer.STUDENT_VIEW_DATA
)
if not student_view_data:
return
# web-only videos don't contain any video information for native clients
only_on_web = student_view_data.get('only_on_web')
if only_on_web:
continue
encoded_videos = student_view_data.get('encoded_videos')
for video_format, video_data in encoded_videos.items():
if DEPRECATE_YOUTUBE.is_enabled(usage_info.course_key):
video_data['stream_priority'] = self.DEPRECATE_YOUTUBE_VIDEO_STREAM_PRIORITY[video_format]
else:
video_data['stream_priority'] = self.DEFAULT_VIDEO_STREAM_PRIORITY[video_format]