228 lines
7.3 KiB
Python
228 lines
7.3 KiB
Python
"""
|
|
Video player in the courseware.
|
|
"""
|
|
|
|
|
|
import logging
|
|
|
|
from bok_choy.javascript import js_defined, wait_for_js
|
|
from bok_choy.page_object import PageObject
|
|
from bok_choy.promise import EmptyPromise # lint-amnesty, pylint: disable=unused-import
|
|
|
|
log = logging.getLogger('VideoPage')
|
|
|
|
VIDEO_BUTTONS = {
|
|
'transcript': '.language-menu',
|
|
'transcript_button': '.toggle-transcript',
|
|
'cc_button': '.toggle-captions',
|
|
'volume': '.volume',
|
|
'play': '.video_control.play',
|
|
'pause': '.video_control.pause',
|
|
'fullscreen': '.add-fullscreen',
|
|
'download_transcript': '.video-tracks > a',
|
|
'speed': '.speeds',
|
|
'quality': '.quality-control',
|
|
'do_not_show_again': '.skip-control',
|
|
'skip_bumper': '.play-skip-control',
|
|
}
|
|
|
|
CSS_CLASS_NAMES = {
|
|
'captions_closed': '.video.closed',
|
|
'captions_rendered': '.video.is-captions-rendered',
|
|
'captions': '.subtitles',
|
|
'captions_text': '.subtitles li span',
|
|
'captions_text_getter': '.subtitles li span[role="link"][data-index="{}"]',
|
|
'closed_captions': '.closed-captions',
|
|
'error_message': '.video .video-player .video-error',
|
|
'video_container': '.video',
|
|
'video_sources': '.video-player video source',
|
|
'video_spinner': '.video-wrapper .spinner',
|
|
'video_xmodule': '.xmodule_VideoBlock',
|
|
'video_init': '.is-initialized',
|
|
'video_time': '.vidtime',
|
|
'video_display_name': '.vert h3',
|
|
'captions_lang_list': '.langs-list li',
|
|
'video_speed': '.speeds .value',
|
|
'poster': '.poster',
|
|
'active_caption_text': '.subtitles-menu > li.current span',
|
|
}
|
|
|
|
VIDEO_MODES = {
|
|
'html5': '.video video',
|
|
'youtube': '.video iframe',
|
|
'hls': '.video video',
|
|
}
|
|
|
|
VIDEO_MENUS = {
|
|
'language': '.lang .menu',
|
|
'speed': '.speed .menu',
|
|
'download_transcript': '.video-tracks .a11y-menu-list',
|
|
'transcript-format': {
|
|
'srt': '.wrapper-download-transcripts .list-download-transcripts .btn-link[data-value="srt"]',
|
|
'txt': '.wrapper-download-transcripts .list-download-transcripts .btn-link[data-value="txt"]'
|
|
},
|
|
'transcript-skip': '.sr-is-focusable.transcript-start',
|
|
}
|
|
|
|
|
|
@js_defined('window.Video', 'window.jQuery', 'window.MathJax')
|
|
class VideoPage(PageObject):
|
|
"""
|
|
Video player in the courseware.
|
|
"""
|
|
|
|
url = None
|
|
current_video_display_name = None
|
|
|
|
@wait_for_js
|
|
def is_browser_on_page(self):
|
|
return self.q(css='div{}'.format(CSS_CLASS_NAMES['video_xmodule'])).present
|
|
|
|
@wait_for_js
|
|
def wait_for_video_class(self):
|
|
"""
|
|
Wait until element with class name `video` appeared in DOM.
|
|
|
|
"""
|
|
self.wait_for_ajax()
|
|
|
|
video_selector = '{}'.format(CSS_CLASS_NAMES['video_container'])
|
|
self.wait_for_element_presence(video_selector, 'Video is initialized')
|
|
|
|
@wait_for_js
|
|
def wait_for_video_player_render(self, autoplay=False):
|
|
"""
|
|
Wait until Video Player Rendered Completely.
|
|
|
|
"""
|
|
self.wait_for_video_class()
|
|
self.wait_for_element_presence(CSS_CLASS_NAMES['video_init'], 'Video Player Initialized')
|
|
self.wait_for_element_presence(CSS_CLASS_NAMES['video_time'], 'Video Player Initialized')
|
|
|
|
video_player_buttons = ['volume', 'fullscreen', 'speed']
|
|
if autoplay:
|
|
video_player_buttons.append('pause')
|
|
else:
|
|
video_player_buttons.append('play')
|
|
|
|
for button in video_player_buttons:
|
|
self.wait_for_element_visibility(VIDEO_BUTTONS[button], f'{button} button is visible')
|
|
|
|
def _is_finished_loading():
|
|
"""
|
|
Check if video loading completed.
|
|
|
|
Returns:
|
|
bool: Tells Video Finished Loading.
|
|
|
|
"""
|
|
return not self.q(css=CSS_CLASS_NAMES['video_spinner']).visible
|
|
|
|
EmptyPromise(_is_finished_loading, 'Finished loading the video', timeout=200).fulfill()
|
|
|
|
self.wait_for_ajax()
|
|
|
|
def get_video_vertical_selector(self, video_display_name=None):
|
|
"""
|
|
Get selector for a video vertical with display name specified by `video_display_name`.
|
|
|
|
Arguments:
|
|
video_display_name (str or None): Display name of a Video. Default vertical selector if None.
|
|
|
|
Returns:
|
|
str: Vertical Selector for video.
|
|
|
|
"""
|
|
if video_display_name:
|
|
video_display_names = self.q(css=CSS_CLASS_NAMES['video_display_name']).text
|
|
if video_display_name not in video_display_names:
|
|
raise ValueError(f"Incorrect Video Display Name: '{video_display_name}'")
|
|
return '.vert.vert-{}'.format(video_display_names.index(video_display_name))
|
|
else:
|
|
return '.vert.vert-0'
|
|
|
|
def get_element_selector(self, class_name, vertical=True):
|
|
"""
|
|
Construct unique element selector.
|
|
|
|
Arguments:
|
|
class_name (str): css class name for an element.
|
|
vertical (bool): do we need vertical css selector or not. vertical css selector is not present in Studio
|
|
|
|
Returns:
|
|
str: Element Selector.
|
|
|
|
"""
|
|
if vertical:
|
|
return '{vertical} {video_element}'.format(
|
|
vertical=self.get_video_vertical_selector(self.current_video_display_name),
|
|
video_element=class_name)
|
|
else:
|
|
return class_name
|
|
|
|
def use_video(self, video_display_name):
|
|
"""
|
|
Set current video display name.
|
|
|
|
Arguments:
|
|
video_display_name (str): Display name of a Video.
|
|
|
|
"""
|
|
self.current_video_display_name = video_display_name
|
|
|
|
def is_button_shown(self, button_id):
|
|
"""
|
|
Check if a video button specified by `button_id` is visible.
|
|
|
|
Arguments:
|
|
button_id (str): key in VIDEO_BUTTONS dictionary, its value will give us the css selector for button.
|
|
|
|
Returns:
|
|
bool: Tells about a buttons visibility.
|
|
|
|
"""
|
|
selector = self.get_element_selector(VIDEO_BUTTONS[button_id])
|
|
return self.q(css=selector).visible
|
|
|
|
def show_captions(self):
|
|
"""
|
|
Make Captions Visible.
|
|
"""
|
|
self._captions_visibility(True)
|
|
|
|
def is_captions_visible(self):
|
|
"""
|
|
Get current visibility sate of captions.
|
|
|
|
Returns:
|
|
bool: True means captions are visible, False means captions are not visible
|
|
|
|
"""
|
|
self.wait_for_ajax()
|
|
caption_state_selector = self.get_element_selector(CSS_CLASS_NAMES['captions'])
|
|
return self.q(css=caption_state_selector).visible
|
|
|
|
@wait_for_js
|
|
def _captions_visibility(self, captions_new_state):
|
|
"""
|
|
Set the video captions visibility state.
|
|
|
|
Arguments:
|
|
captions_new_state (bool): True means show captions, False means hide captions
|
|
|
|
"""
|
|
states = {True: 'Shown', False: 'Hidden'}
|
|
state = states[captions_new_state]
|
|
|
|
# Make sure that the transcript button is there
|
|
EmptyPromise(lambda: self.is_button_shown('transcript_button'),
|
|
"transcript button is shown").fulfill()
|
|
|
|
# toggle captions visibility state if needed
|
|
if self.is_captions_visible() != captions_new_state:
|
|
self.click_player_button('transcript_button') # lint-amnesty, pylint: disable=no-member
|
|
|
|
# Verify that captions state is toggled/changed
|
|
EmptyPromise(lambda: self.is_captions_visible() == captions_new_state,
|
|
f"Transcripts are {state}").fulfill()
|