Commit a6e83834 by clrux

Merge pull request #10706 from edx/clrux/ac-188-no-draggabilly

Video: Adding closed captioning to the video player (not draggable)
parents 06caf65b b883d527
......@@ -283,7 +283,7 @@ require.config({
"osda":{
exports: "osda",
deps: ["annotator", "annotator-harvardx", "video.dev", "vjs.youtube", "rangeslider", "share-annotator", "richText-annotator", "reply-annotator", "tags-annotator", "flagging-annotator", "grouping-annotator", "diacritic-annotator", "openseadragon", "jquery-Watch", "catch", "handlebars", "URI"]
},
}
// end of annotation tool files
}
});
......@@ -247,6 +247,22 @@ html:not('.afontgarde') .icon-fallback-img {
}
}
.closed-captions {
position: absolute;
width: 85%;
left: 5%;
top: 70%;
text-align: center;
}
.closed-captions.is-visible {
max-height: ($baseline * 3);
border-radius: ($baseline / 5);
padding: 8px ($baseline / 2) 8px ($baseline * 1.5);
background: rgba(0, 0, 0, .75);
color: $yellow;
}
.video-player {
overflow: hidden;
min-height: 300px;
......@@ -701,39 +717,44 @@ html:not('.afontgarde') .icon-fallback-img {
.subtitles {
@include float(left);
overflow: auto;
margin: 0;
max-height: 460px;
width: flex-grid(3, 9);
padding: 0;
font-size: 14px;
list-style: none;
visibility: visible;
li {
@extend %ui-fake-link;
margin-bottom: 8px;
border: 0;
.subtitles-menu {
height: 100%;
margin: 0;
padding: 0;
color: #0074b5; // AA compliant
line-height: lh();
list-style: none;
&.current {
color: #333;
font-weight: 700;
}
li {
@extend %ui-fake-link;
margin-bottom: 8px;
border: 0;
padding: 0;
color: #0074b5; // AA compliant
line-height: lh();
&.focused {
outline: #000 dotted thin;
outline-offset: -1px;
}
&.current {
color: #333;
font-weight: 700;
}
&:hover,
&:focus {
text-decoration: underline;
}
&.focused {
outline: #000 dotted thin;
outline-offset: -1px;
}
&:hover,
&:focus {
text-decoration: underline;
}
&:empty {
margin-bottom: 0;
&:empty {
margin-bottom: 0;
}
}
}
}
......
......@@ -17,6 +17,7 @@
<div id="id"></div>
</section>
<div class="video-player-post"></div>
<div class="closed-captions"></div>
<section class="video-controls is-hidden">
<div class="slider"></div>
<div>
......
......@@ -48,6 +48,12 @@
});
});
it('adds the captioning control to the video player', function() {
state = jasmine.initializePlayer();
expect($('.video')).toContain('.toggle-captions');
expect($('.video')).toContain('.closed-captions');
});
it('fetch the transcript in HTML5 mode', function () {
runs(function () {
state = jasmine.initializePlayer();
......@@ -122,16 +128,16 @@
it('bind the mouse movement', function () {
state = jasmine.initializePlayer();
expect($('.subtitles')).toHandle('mouseover');
expect($('.subtitles')).toHandle('mouseout');
expect($('.subtitles')).toHandle('mousemove');
expect($('.subtitles')).toHandle('mousewheel');
expect($('.subtitles')).toHandle('DOMMouseScroll');
expect($('.subtitles-menu')).toHandle('mouseover');
expect($('.subtitles-menu')).toHandle('mouseout');
expect($('.subtitles-menu')).toHandle('mousemove');
expect($('.subtitles-menu')).toHandle('mousewheel');
expect($('.subtitles-menu')).toHandle('DOMMouseScroll');
});
it('bind the scroll', function () {
state = jasmine.initializePlayer();
expect($('.subtitles'))
expect($('.subtitles-menu'))
.toHandleWith('scroll', state.videoControl.showControls);
});
......@@ -158,14 +164,61 @@
});
});
describe('renderCaptions', function() {
describe('is rendered', function() {
var KEY = $.ui.keyCode,
keyPressEvent = function(key) {
return $.Event('keydown', { keyCode: key });
};
it('toggles the captions on control click', function() {
state = jasmine.initializePlayer();
$('.toggle-captions').click();
expect($('.toggle-captions')).toHaveClass('is-active');
expect($('.closed-captions')).toHaveClass('is-visible');
$('.toggle-captions').click();
expect($('.toggle-captions')).not.toHaveClass('is-active');
expect($('.closed-captions')).not.toHaveClass('is-visible');
});
it('toggles the captions on keypress ENTER', function() {
state = jasmine.initializePlayer();
$('.toggle-captions').focus().trigger(keyPressEvent(KEY.ENTER));
expect($('.toggle-captions')).toHaveClass('is-active');
expect($('.closed-captions')).toHaveClass('is-visible');
$('.toggle-captions').focus().trigger(keyPressEvent(KEY.ENTER));
expect($('.toggle-captions')).not.toHaveClass('is-active');
expect($('.closed-captions')).not.toHaveClass('is-visible');
});
it('toggles the captions on keypress SPACE', function() {
state = jasmine.initializePlayer();
$('.toggle-captions').focus().trigger(keyPressEvent(KEY.SPACE));
expect($('.toggle-captions')).toHaveClass('is-active');
expect($('.closed-captions')).toHaveClass('is-visible');
$('.toggle-captions').focus().trigger(keyPressEvent(KEY.SPACE));
expect($('.toggle-captions')).not.toHaveClass('is-active');
expect($('.closed-captions')).not.toHaveClass('is-visible');
});
});
});
describe('renderLanguageMenu', function () {
describe('is rendered', function () {
var KEY = $.ui.keyCode,
keyPressEvent = function(key) {
return $.Event('keydown', { keyCode: key });
};
keyPressEvent = function(key) {
return $.Event('keydown', { keyCode: key });
};
it('if languages more than 1', function () {
state = jasmine.initializePlayer();
......@@ -364,9 +417,8 @@
});
it('show explanation message', function () {
expect($('.subtitles li')).toHaveHtml(
'Caption will be displayed when you start playing ' +
'the video.'
expect($('.subtitles-menu li')).toHaveHtml(
'Transcript will be displayed when you start playing the video.'
);
});
......@@ -444,7 +496,7 @@
runs(function () {
$(window).trigger(jQuery.Event('mousemove'));
jasmine.Clock.tick(state.config.captionsFreezeTime);
$('.subtitles').trigger(jQuery.Event('mouseenter'));
$('.subtitles-menu').trigger(jQuery.Event('mouseenter'));
jasmine.Clock.tick(state.config.captionsFreezeTime);
});
});
......@@ -459,7 +511,7 @@
describe('when the cursor is moving', function () {
it('reset the freezing timeout', function () {
runs(function () {
$('.subtitles').trigger(jQuery.Event('mousemove'));
$('.subtitles-menu').trigger(jQuery.Event('mousemove'));
expect(window.clearTimeout).toHaveBeenCalled();
});
});
......@@ -468,7 +520,7 @@
describe('when the mouse is scrolling', function () {
it('reset the freezing timeout', function () {
runs(function () {
$('.subtitles').trigger(jQuery.Event('mousewheel'));
$('.subtitles-menu').trigger(jQuery.Event('mousewheel'));
expect(window.clearTimeout).toHaveBeenCalled();
});
});
......@@ -486,7 +538,7 @@
describe('always', function () {
beforeEach(function () {
$('.subtitles').trigger(jQuery.Event('mouseout'));
$('.subtitles-menu').trigger(jQuery.Event('mouseout'));
});
it('reset the freezing timeout', function () {
......@@ -501,9 +553,9 @@
describe('when the player is playing', function () {
beforeEach(function () {
state.videoCaption.playing = true;
$('.subtitles li[data-index]:first')
$('.subtitles-menu li[data-index]:first')
.addClass('current');
$('.subtitles').trigger(jQuery.Event('mouseout'));
$('.subtitles-menu').trigger(jQuery.Event('mouseout'));
});
it('scroll the transcript', function () {
......@@ -514,7 +566,7 @@
describe('when the player is not playing', function () {
beforeEach(function () {
state.videoCaption.playing = false;
$('.subtitles').trigger(jQuery.Event('mouseout'));
$('.subtitles-menu').trigger(jQuery.Event('mouseout'));
});
it('does not scroll the transcript', function () {
......
......@@ -92,7 +92,8 @@ function (VideoPlayer) {
showinfo: 0,
enablejsapi: 1,
modestbranding: 1,
html5: 1
html5: 1,
cc_load_policy: 0
},
videoId: 'cogebirgzzM',
events: events
......@@ -118,7 +119,8 @@ function (VideoPlayer) {
rel: 0,
showinfo: 0,
enablejsapi: 1,
modestbranding: 1
modestbranding: 1,
cc_load_policy: 0
},
videoId: 'abcdefghijkl',
events: jasmine.any(Object)
......
......@@ -139,7 +139,8 @@ function (HTML5Video, Resizer) {
rel: 0,
showinfo: 0,
enablejsapi: 1,
modestbranding: 1
modestbranding: 1,
cc_load_policy: 0
};
if (!state.isFlashMode()) {
......
(function (define) {
// VideoCaption module.
// VideoCaption module.
'use strict';
define(
'video/09_video_caption.js',
['video/00_sjson.js', 'video/00_async_process.js'],
function (Sjson, AsyncProcess) {
/**
* @desc VideoCaption module exports a function.
*
......@@ -29,11 +30,14 @@
'onContainerMouseEnter', 'onContainerMouseLeave', 'fetchCaption',
'onResize', 'pause', 'play', 'onCaptionUpdate', 'onCaptionHandler', 'destroy',
'handleKeypress', 'handleKeypressLink', 'openLanguageMenu', 'closeLanguageMenu',
'previousLanguageMenuItem', 'nextLanguageMenuItem'
'previousLanguageMenuItem', 'nextLanguageMenuItem', 'handleCaptionToggle',
'showClosedCaptions', 'hideClosedCaptions', 'toggleClosedCaptions',
'updateCaptioningCookie', 'handleCaptioningCookie', 'handleTranscriptToggle'
);
this.state = state;
this.state.videoCaption = this;
this.renderElements();
this.handleCaptioningCookie();
return $.Deferred().resolve().promise();
};
......@@ -41,6 +45,14 @@
VideoCaption.prototype = {
langTemplate: [
'<div class="grouped-controls">',
'<button class="control toggle-captions" aria-disabled="false">',
'<span class="icon-fallback-img">',
'<span class="icon fa fa-cc" aria-hidden="true"></span>',
'<span class="sr control-text">',
gettext('Turn on closed captioning'),
'</span>',
'</span>',
'</button>',
'<button class="control toggle-transcript" aria-disabled="false">',
'<span class="icon-fallback-img">',
'<span class="icon fa fa-quote-left" aria-hidden="true"></span>',
......@@ -66,12 +78,13 @@
].join(''),
template: [
'<ol id="transcript-captions" class="subtitles" aria-label="',
'<div class="subtitles" role="region" aria-label="',
/* jshint maxlen:200 */
gettext('Activating an item in this group will spool the video to the corresponding time point. To skip transcript, go to previous item.'),
'">',
'<li></li>',
'</ol>'
'<ol id="transcript-captions" class="subtitles-menu">',
'</ol>',
'</div>'
].join(''),
destroy: function () {
......@@ -106,7 +119,10 @@
this.loaded = false;
this.subtitlesEl = $(this.template);
this.subtitlesMenuEl = this.subtitlesEl.find('.subtitles-menu');
this.container = $(this.langTemplate);
this.captionControlEl = this.container.find('.toggle-captions');
this.captionDisplayEl = this.state.el.find('.closed-captions');
this.transcriptControlEl = this.container.find('.toggle-transcript');
this.languageChooserEl = this.container.find('.lang');
this.menuChooserEl = this.languageChooserEl.parent();
......@@ -129,16 +145,26 @@
'keydown'
].join(' ');
this.transcriptControlEl.on('click', this.toggle);
this.subtitlesEl
.on({
mouseenter: this.onMouseEnter,
mouseleave: this.onMouseLeave,
mousemove: this.onMovement,
mousewheel: this.onMovement,
DOMMouseScroll: this.onMovement
})
.on(events, 'li[data-index]', this.onCaptionHandler);
this.captionControlEl.on({
click: this.toggleClosedCaptions,
keydown: this.handleCaptionToggle
});
this.transcriptControlEl.on({
click: this.toggle,
keydown: this.handleTranscriptToggle
});
this.subtitlesMenuEl.on({
mouseenter: this.onMouseEnter,
mouseleave: this.onMouseLeave,
mousemove: this.onMovement,
mousewheel: this.onMovement,
DOMMouseScroll: this.onMovement
})
.on(events, 'li[data-index]', this.onCaptionHandler);
this.container.on({
mouseenter: this.onContainerMouseEnter,
mouseleave: this.onContainerMouseLeave
});
if (this.showLanguageMenu) {
this.languageChooserEl.on({
......@@ -148,11 +174,6 @@
this.languageChooserEl.on({
keydown: this.handleKeypressLink
}, '.control-lang');
this.container.on({
mouseenter: this.onContainerMouseEnter,
mouseleave: this.onContainerMouseLeave
});
}
state.el
......@@ -168,7 +189,7 @@
});
if ((state.videoType === 'html5') && (state.config.autohideHtml5)) {
this.subtitlesEl.on('scroll', state.videoControl.showControls);
this.subtitlesMenuEl.on('scroll', state.videoControl.showControls);
}
},
......@@ -176,6 +197,30 @@
this.updatePlayTime(time);
},
handleCaptionToggle: function(event) {
var KEY = $.ui.keyCode,
keyCode = event.keyCode;
switch(keyCode) {
case KEY.SPACE:
case KEY.ENTER:
event.preventDefault();
this.toggleClosedCaptions(event);
}
},
handleTranscriptToggle: function(event) {
var KEY = $.ui.keyCode,
keyCode = event.keyCode;
switch(keyCode) {
case KEY.SPACE:
case KEY.ENTER:
event.preventDefault();
this.toggle();
}
},
handleKeypressLink: function(event) {
var KEY = $.ui.keyCode,
keyCode = event.keyCode,
......@@ -188,7 +233,7 @@
index = this.languageChooserEl.find('li').index(focused);
total = this.languageChooserEl.find('li').size() - 1;
this.previousLanguageMenuItem(event, index, total);
this.previousLanguageMenuItem(event, index);
break;
case KEY.DOWN:
......@@ -241,13 +286,13 @@
if (index === total) {
this.languageChooserEl
.find('.control-lang').first()
.focus();
.focus();
} else {
this.languageChooserEl
.find('li:eq(' + index + ')')
.next()
.find('.control-lang')
.focus();
.focus();
}
return false;
......@@ -256,11 +301,7 @@
previousLanguageMenuItem: function(event, index) {
event.preventDefault();
if (event.altKey) {
return true;
}
if (event.shiftKey) {
if (event.altKey || event.shiftKey) {
return true;
}
......@@ -287,11 +328,13 @@
this.state.el.trigger('language_menu:show');
button
.addClass('is-opened');
menu
.find('.control-lang').last()
.focus();
.focus();
},
closeLanguageMenu: function(event) {
......@@ -300,6 +343,7 @@
var button = this.languageChooserEl;
this.state.el.trigger('language_menu:hide');
button
.removeClass('is-opened')
.find('.language-menu')
......@@ -473,7 +517,7 @@
state.el.removeClass('is-captions-rendered');
// Fetch the captions file. If no file was specified, or if an error
// occurred, then we hide the captions panel, and the "CC" button
// occurred, then we hide the captions panel, and the "Transcript" button
this.fetchXHR = $.ajaxWithPrefix({
url: url,
notifyOnError: false,
......@@ -491,10 +535,10 @@
}
} else {
if (state.isTouch) {
self.subtitlesEl.find('li').html(
self.subtitlesEl.find('.subtitles-menu').html(
gettext(
'Caption will be displayed when ' +
'you start playing the video.'
'<li>Transcript will be displayed when ' +
'you start playing the video.</li>'
)
);
} else {
......@@ -706,9 +750,9 @@
};
this.rendered = false;
this.subtitlesEl.empty();
this.subtitlesMenuEl.empty();
this.setSubtitlesHeight();
this.buildCaptions(this.subtitlesEl, start, captions).done(onRender);
this.buildCaptions(this.subtitlesMenuEl, start, captions).done(onRender);
},
/**
......@@ -718,7 +762,7 @@
*/
addPaddings: function () {
this.subtitlesEl
this.subtitlesMenuEl
.prepend(
$('<li class="spacing">')
.height(this.topSpacingHeight())
......@@ -936,6 +980,7 @@
.addClass('current');
this.currentIndex = newIndex;
this.captionDisplayEl.text(this.subtitlesEl.find("li[data-index='" + newIndex + "']").text());
this.scrollCaption();
}
}
......@@ -1017,6 +1062,82 @@
}
},
handleCaptioningCookie: function() {
if ($.cookie('show_closed_captions') === 'true') {
this.state.showClosedCaptions = true;
this.showClosedCaptions();
// keep it going until turned off
$.cookie('show_closed_captions', 'true', {
expires: 3650,
path: '/'
});
} else {
this.hideClosedCaptions();
}
},
toggleClosedCaptions: function(event) {
event.preventDefault();
if (this.state.el.hasClass('has-captions')) {
this.state.showClosedCaptions = false;
this.updateCaptioningCookie(false);
this.hideClosedCaptions();
} else {
this.state.showClosedCaptions = true;
this.updateCaptioningCookie(true);
this.showClosedCaptions();
}
},
showClosedCaptions: function() {
this.state.el.addClass('has-captions');
this.captionDisplayEl
.show()
.addClass('is-visible');
this.captionControlEl
.addClass('is-active')
.find('.control-text')
.text(gettext('Hide closed captions'));
if (this.subtitlesEl.find('.current').text()) {
this.captionDisplayEl
.text(this.subtitlesEl.find('.current').text());
} else {
this.captionDisplayEl
.text(gettext('(Caption will be displayed when you start playing the video.)'));
}
},
hideClosedCaptions: function() {
this.state.el.removeClass('has-captions');
this.captionDisplayEl
.hide()
.removeClass('is-visible');
this.captionControlEl
.removeClass('is-active')
.find('.control-text')
.text(gettext('Turn on closed captioning'));
},
updateCaptioningCookie: function(method) {
if (method) {
$.cookie('show_closed_captions', 'true', {
expires: 3650,
path: '/'
});
} else {
$.cookie('show_closed_captions', null, {
path: '/'
});
}
},
/**
* @desc Shows/Hides captions and updates the cookie.
*
......
......@@ -16,6 +16,7 @@ log = logging.getLogger('VideoPage')
VIDEO_BUTTONS = {
'transcript': '.lang',
'transcript_button': '.toggle-transcript',
'cc_button': '.toggle-captions',
'volume': '.volume',
'play': '.video_control.play',
'pause': '.video_control.pause',
......@@ -28,10 +29,12 @@ VIDEO_BUTTONS = {
}
CSS_CLASS_NAMES = {
'closed_captions': '.video.closed',
'captions_closed': '.video.closed',
'captions_rendered': '.video.is-captions-rendered',
'captions': '.subtitles',
'captions_text': '.subtitles > li',
'captions_text': '.subtitles li',
'captions_text_getter': '.subtitles li[role="link"][data-index="1"]',
'closed_captions': '.closed-captions',
'error_message': '.video .video-player h3',
'video_container': '.video',
'video_sources': '.video-player video source',
......@@ -293,6 +296,18 @@ class VideoPage(PageObject):
"""
self._captions_visibility(False)
def show_closed_captions(self):
"""
Make closed captions visible.
"""
self._closed_captions_visibility(True)
def hide_closed_captions(self):
"""
Make closed captions invisible.
"""
self._closed_captions_visibility(False)
def is_captions_visible(self):
"""
Get current visibility sate of captions.
......@@ -302,8 +317,20 @@ class VideoPage(PageObject):
"""
self.wait_for_ajax()
caption_state_selector = self.get_element_selector(CSS_CLASS_NAMES['closed_captions'])
return not self.q(css=caption_state_selector).present
caption_state_selector = self.get_element_selector(CSS_CLASS_NAMES['captions'])
return self.q(css=caption_state_selector).visible
def is_closed_captions_visible(self):
"""
Get current visibility sate of closed captions.
Returns:
bool: True means captions are visible, False means captions are not visible
"""
self.wait_for_ajax()
closed_caption_state_selector = self.get_element_selector(CSS_CLASS_NAMES['closed_captions'])
return self.q(css=closed_caption_state_selector).visible
@wait_for_js
def _captions_visibility(self, captions_new_state):
......@@ -327,7 +354,24 @@ class VideoPage(PageObject):
# Verify that captions state is toggled/changed
EmptyPromise(lambda: self.is_captions_visible() == captions_new_state,
"Captions are {state}".format(state=state)).fulfill()
"Transcripts are {state}".format(state=state)).fulfill()
@wait_for_js
def _closed_captions_visibility(self, closed_captions_new_state):
"""
Set the video closed captioning visibility state.
Arguments:
closed_captions_new_state (bool): True means show closed captioning
"""
states = {True: 'shown', False: 'hidden'}
state = states[closed_captions_new_state]
self.click_player_button('cc_button')
# Make sure that the captions are visible
EmptyPromise(lambda: self.is_closed_captions_visible() == closed_captions_new_state,
"Closed captions are {state}".format(state=state)).fulfill()
@property
def captions_text(self):
......@@ -346,6 +390,31 @@ class VideoPage(PageObject):
return ' '.join(subs)
@property
def closed_captions_text(self):
"""
Extract closed captioning text.
Returns:
str: closed captions Text.
"""
self.wait_for_closed_captions()
closed_captions_selector = self.get_element_selector(CSS_CLASS_NAMES['closed_captions'])
subs = self.q(css=closed_captions_selector).html
return ' '.join(subs)
def click_first_line_in_transcript(self):
"""
Clicks a line in the transcript updating the current caption.
"""
self.wait_for_captions()
captions_selector = self.q(css=CSS_CLASS_NAMES['captions_text_getter'])
captions_selector.click()
@property
def speed(self):
"""
Get current video speed value.
......@@ -494,6 +563,12 @@ class VideoPage(PageObject):
response = requests.get(url, **kwargs)
return response.status_code < 400, response.headers, response.content
def get_cookie(self, cookie_name):
"""
Searches for and returns `cookie_name`
"""
return self.browser.get_cookie(cookie_name)
def downloaded_transcript_contains_text(self, transcript_format, text_to_search):
"""
Download the transcript in format `transcript_format` and check that it contains the text `text_to_search`
......@@ -837,6 +912,20 @@ class VideoPage(PageObject):
captions_rendered_selector = self.get_element_selector(CSS_CLASS_NAMES['captions_rendered'])
self.wait_for_element_presence(captions_rendered_selector, 'Captions Rendered')
def wait_for_closed_captions(self):
"""
Wait until closed captions are rendered completely.
"""
cc_rendered_selector = self.get_element_selector(CSS_CLASS_NAMES['closed_captions'])
self.wait_for_element_visibility(cc_rendered_selector, 'Closed captions rendered')
def wait_for_closed_captions_to_be_hidden(self):
"""
Waits for the closed captions to be turned off completely.
"""
cc_rendered_selector = self.get_element_selector(CSS_CLASS_NAMES['closed_captions'])
self.wait_for_element_invisibility(cc_rendered_selector, 'Closed captions hidden')
def _parse_time_str(time_str):
"""
......
......@@ -13,11 +13,11 @@ from selenium.webdriver.common.keys import Keys
CLASS_SELECTORS = {
'video_container': 'div.video',
'video_container': '.video',
'video_init': '.is-initialized',
'video_xmodule': '.xmodule_VideoModule',
'video_spinner': '.video-wrapper .spinner',
'video_controls': 'section.video-controls',
'video_controls': '.video-controls',
'attach_asset': '.upload-dialog > input[type="file"]',
'upload_dialog': '.wrapper-modal-window-assetupload',
'xblock': '.add-xblock-component',
......@@ -264,7 +264,7 @@ class VideoComponentPage(VideoPage):
line_number (int): caption line number
"""
caption_line_selector = ".subtitles > li[data-index='{index}']".format(index=line_number - 1)
caption_line_selector = ".subtitles li[data-index='{index}']".format(index=line_number - 1)
self.q(css=caption_line_selector).results[0].send_keys(Keys.ENTER)
def is_caption_line_focused(self, line_number):
......@@ -275,7 +275,7 @@ class VideoComponentPage(VideoPage):
line_number (int): caption line number
"""
caption_line_selector = ".subtitles > li[data-index='{index}']".format(index=line_number - 1)
caption_line_selector = ".subtitles li[data-index='{index}']".format(index=line_number - 1)
attributes = self.q(css=caption_line_selector).attrs('class')
return 'focused' in attributes
......@@ -504,7 +504,7 @@ class VideoComponentPage(VideoPage):
As all the captions lines are exactly same so only getting partial lines will work.
"""
self.wait_for_captions()
selector = '.subtitles > li:nth-child({})'
selector = '.subtitles li:nth-child({})'
return ' '.join([self.q(css=selector.format(i)).text[0] for i in range(1, 6)])
def set_url_field(self, url, field_number):
......
......@@ -142,7 +142,7 @@ class VideoEditorTest(CMSVideoBaseTest):
self.open_advanced_tab()
self.video.upload_translation('1mb_transcripts.srt', 'uk')
self.save_unit_settings()
self.assertTrue(self.video.is_captions_visible())
self.video.wait_for(self.video.is_captions_visible, 'Captions are visible', timeout=10)
unicode_text = "Привіт, edX вітає вас.".decode('utf-8')
self.assertIn(unicode_text, self.video.captions_lines())
......
......@@ -255,7 +255,6 @@ class CMSVideoTest(CMSVideoBaseTest):
Then when I view the video it does show the captions
"""
self._create_course_unit(subtitles=True)
self.assertTrue(self.video.is_captions_visible())
def test_captions_toggling(self):
......
......@@ -212,9 +212,9 @@ class YouTubeVideoTest(VideoBaseTest):
# Verify that video has rendered in "Youtube" mode
self.assertTrue(self.video.is_video_rendered('youtube'))
def test_cc_button_wo_english_transcript(self):
def test_transcript_button_wo_english_transcript(self):
"""
Scenario: CC button works correctly w/o english transcript in Youtube mode
Scenario: Transcript button works correctly w/o english transcript in Youtube mode
Given the course has a Video component in "Youtube" mode
And I have defined a non-english transcript for the video
And I have uploaded a non-english transcript file to assets
......@@ -226,13 +226,38 @@ class YouTubeVideoTest(VideoBaseTest):
self.navigate_to_video()
self.video.show_captions()
# Verify that we see "好 各位同学" text in the captions
# Verify that we see "好 各位同学" text in the transcript
unicode_text = "好 各位同学".decode('utf-8')
self.assertIn(unicode_text, self.video.captions_text)
def test_cc_button_transcripts_and_sub_fields_empty(self):
def test_cc_button(self):
"""
Scenario: CC button works correctly with transcript in YouTube mode
Given the course has a video component in "Youtube" mode
And I have defined a transcript for the video
Then I see the closed captioning element over the video
"""
Scenario: CC button works correctly if transcripts and sub fields are empty,
data = {'transcripts': {'zh': 'chinese_transcripts.srt'}}
self.metadata = self.metadata_for_mode('youtube', data)
self.assets.append('chinese_transcripts.srt')
self.navigate_to_video()
# Show captions and make sure they're visible and cookie is set
self.video.show_closed_captions()
self.video.wait_for_closed_captions()
self.assertTrue(self.video.is_closed_captions_visible)
self.video.reload_page()
self.assertTrue(self.video.is_closed_captions_visible)
# Hide captions and make sure they're hidden and cookie is unset
self.video.hide_closed_captions()
self.video.wait_for_closed_captions_to_be_hidden()
self.video.reload_page()
self.video.wait_for_closed_captions_to_be_hidden()
def test_transcript_button_transcripts_and_sub_fields_empty(self):
"""
Scenario: Transcript button works correctly if transcripts and sub fields are empty,
but transcript file exists in assets (Youtube mode of Video component)
Given the course has a Video component in "Youtube" mode
And I have uploaded a .srt.sjson file to assets
......@@ -247,11 +272,11 @@ class YouTubeVideoTest(VideoBaseTest):
# Verify that we see "Welcome to edX." text in the captions
self.assertIn('Welcome to edX.', self.video.captions_text)
def test_cc_button_hidden_no_translations(self):
def test_transcript_button_hidden_no_translations(self):
"""
Scenario: CC button is hidden if no translations
Scenario: Transcript button is hidden if no translations
Given the course has a Video component in "Youtube" mode
Then the "CC" button is hidden
Then the "Transcript" button is hidden
"""
self.navigate_to_video()
self.assertFalse(self.video.is_button_shown('transcript_button'))
......@@ -522,6 +547,16 @@ class YouTubeVideoTest(VideoBaseTest):
timeout=5
)
def _verify_closed_caption_text(self, text):
"""
Scenario: returns True if the captions are visible, False is else
"""
self.video.wait_for(
lambda: (text in self.video.closed_captions_text),
u'Closed captions contain "{}" text'.format(text),
timeout=5
)
def test_video_language_menu_working(self):
"""
Scenario: Language menu works correctly in Video component
......@@ -554,6 +589,43 @@ class YouTubeVideoTest(VideoBaseTest):
self.video.select_language('en')
self._verify_caption_text('Welcome to edX.')
def test_video_language_menu_working_closed_captions(self):
"""
Scenario: Language menu works correctly in Video component, checks closed captions
Given the course has a Video component in "Youtube" mode
And I have defined multiple language transcripts for the videos
And I make sure captions are closed
And I see video menu "language" with correct items
And I select language with code "en"
Then I see "Welcome to edX." text in the closed captions
And I select language with code "zh"
Then I see "我们今天要讲的题目是" text in the closed captions
"""
self.assets.extend(['chinese_transcripts.srt', 'subs_3_yD_cEKoCk.srt.sjson'])
data = {'transcripts': {"zh": "chinese_transcripts.srt"}, 'sub': '3_yD_cEKoCk'}
self.metadata = self.metadata_for_mode('youtube', additional_data=data)
# go to video
self.navigate_to_video()
self.video.show_closed_captions()
correct_languages = {'en': 'English', 'zh': 'Chinese'}
self.assertEqual(self.video.caption_languages, correct_languages)
# we start the video, then pause it to activate the transcript
self.video.click_player_button('play')
self.video.wait_for_position('0:01')
self.video.click_player_button('pause')
self.video.select_language('en')
self.video.click_first_line_in_transcript()
self._verify_closed_caption_text('Welcome to edX.')
self.video.select_language('zh')
unicode_text = "我们今天要讲的题目是".decode('utf-8')
self.video.click_first_line_in_transcript()
self._verify_closed_caption_text(unicode_text)
def test_multiple_videos_in_sequentials_load_and_work(self):
"""
Scenario: Multiple videos in sequentials all load and work, switching between sequentials
......
......@@ -1257,7 +1257,7 @@ main_vendor_js = base_vendor_js + [
'js/vendor/jquery.ba-bbq.min.js',
'js/vendor/afontgarde/modernizr.fontface-generatedcontent.js',
'js/vendor/afontgarde/afontgarde.js',
'js/vendor/afontgarde/edx-icons.js',
'js/vendor/afontgarde/edx-icons.js'
]
# Common files used by both RequireJS code and non-RequireJS code
......
......@@ -26,6 +26,7 @@
<h3 class="hidden">${_('No playable video sources found.')}</h3>
</section>
<div class="video-player-post"></div>
<div class="closed-captions"></div>
<section class="video-controls is-hidden">
<div>
<div class="vcr"><div class="vidtime">0:00 / 0:00</div></div>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment