diff --git a/cms/djangoapps/contentstore/features/video.feature b/cms/djangoapps/contentstore/features/video.feature
index 105a26c868..2ca3b813a5 100644
--- a/cms/djangoapps/contentstore/features/video.feature
+++ b/cms/djangoapps/contentstore/features/video.feature
@@ -2,15 +2,18 @@
Feature: CMS.Video Component
As a course author, I want to be able to view my created videos in Studio.
+ # 1
# Video Alpha Features will work in Firefox only when Firefox is the active window
Scenario: Autoplay is disabled in Studio
Given I have created a Video component
Then when I view the video it does not have autoplay enabled
+ # 2
Scenario: Creating a video takes a single click
Given I have clicked the new unit button
Then creating a video takes a single click
+ # 3
# Sauce Labs cannot delete cookies
@skip_sauce
Scenario: Captions are hidden correctly
@@ -18,12 +21,14 @@ Feature: CMS.Video Component
And I have hidden captions
Then when I view the video it does not show the captions
+ # 4
# Sauce Labs cannot delete cookies
@skip_sauce
Scenario: Captions are shown correctly
Given I have created a Video component with subtitles
Then when I view the video it does show the captions
+ # 5
# Sauce Labs cannot delete cookies
@skip_sauce
Scenario: Captions are toggled correctly
@@ -31,7 +36,36 @@ Feature: CMS.Video Component
And I have toggled captions
Then when I view the video it does show the captions
+ # 6
Scenario: Video data is shown correctly
Given I have created a video with only XML data
And I reload the page
Then the correct Youtube video is shown
+
+ # 7
+ Scenario: Closed captions become visible when the mouse hovers over CC button
+ Given I have created a Video component with subtitles
+ And Make sure captions are closed
+ Then Captions become "invisible" after 3 seconds
+ And I hover over button "CC"
+ Then Captions become "visible"
+ And I hover over button "volume"
+ Then Captions become "invisible" after 3 seconds
+
+ # 8
+ Scenario: Open captions never become invisible
+ Given I have created a Video component with subtitles
+ And Make sure captions are open
+ Then Captions are "visible"
+ And I hover over button "CC"
+ Then Captions are "visible"
+ And I hover over button "volume"
+ Then Captions are "visible"
+
+ # 9
+ Scenario: Closed captions are invisible when mouse doesn't hover on CC button
+ Given I have created a Video component with subtitles
+ And Make sure captions are closed
+ Then Captions become "invisible" after 3 seconds
+ And I hover over button "volume"
+ Then Captions are "invisible"
diff --git a/cms/djangoapps/contentstore/features/video.py b/cms/djangoapps/contentstore/features/video.py
index 20db375184..12f2992568 100644
--- a/cms/djangoapps/contentstore/features/video.py
+++ b/cms/djangoapps/contentstore/features/video.py
@@ -4,6 +4,11 @@ from lettuce import world, step
from xmodule.modulestore import Location
from contentstore.utils import get_modulestore
+BUTTONS = {
+ 'CC': '.hide-subtitles',
+ 'volume': '.volume',
+}
+
@step('I have created a Video component$')
def i_created_a_video_component(step):
@@ -19,6 +24,7 @@ def i_created_a_video_component(step):
def i_created_a_video_with_subs(_step):
_step.given('I have created a Video component with subtitles "OEoXaMPEzfM"')
+
@step('I have created a Video component with subtitles "([^"]*)"$')
def i_created_a_video_with_subs_with_name(_step, sub_id):
_step.given('I have created a Video component')
@@ -115,3 +121,38 @@ def the_youtube_video_is_shown(_step):
world.wait_for_xmodule()
ele = world.css_find('.video').first
assert ele['data-streams'].split(':')[1] == world.scenario_dict['YOUTUBE_ID']
+
+
+@step('Make sure captions are (.+)$')
+def set_captions_visibility_state(_step, captions_state):
+ if captions_state == 'closed':
+ if world.css_visible('.subtitles'):
+ world.browser.find_by_css('.hide-subtitles').click()
+ else:
+ if not world.css_visible('.subtitles'):
+ world.browser.find_by_css('.hide-subtitles').click()
+
+
+@step('I hover over button "([^"]*)"$')
+def hover_over_button(_step, button):
+ world.css_find(BUTTONS[button.strip()]).mouse_over()
+
+
+@step('Captions (?:are|become) "([^"]*)"$')
+def are_captions_visibile(_step, visibility_state):
+ _step.given('Captions become "{0}" after 0 seconds'.format(visibility_state))
+
+
+@step('Captions (?:are|become) "([^"]*)" after (.+) seconds$')
+def check_captions_visibility_state(_step, visibility_state, timeout):
+ timeout = int(timeout.strip())
+
+ # Captions become invisible by fading out. We must wait by a specified
+ # time.
+ world.wait(timeout)
+
+ if visibility_state == 'visible':
+ assert world.css_visible('.subtitles')
+ else:
+ assert not world.css_visible('.subtitles')
+
diff --git a/common/lib/xmodule/xmodule/css/video/display.scss b/common/lib/xmodule/xmodule/css/video/display.scss
index 538f4672cc..f21ed73444 100644
--- a/common/lib/xmodule/xmodule/css/video/display.scss
+++ b/common/lib/xmodule/xmodule/css/video/display.scss
@@ -392,7 +392,7 @@ div.video {
@include transition(none);
-webkit-font-smoothing: antialiased;
width: 30px;
-
+
&:hover, &:active {
background-color: #444;
color: #fff;
@@ -457,7 +457,7 @@ div.video {
text-indent: -9999px;
@include transition(none);
width: 30px;
-
+
&:hover, &:active {
background-color: #444;
color: #fff;
@@ -611,6 +611,7 @@ div.video {
ol.subtitles {
width: 0;
height: 0;
+
visibility: hidden;
}
@@ -645,6 +646,7 @@ div.video {
ol.subtitles {
right: -(flex-grid(4));
width: auto;
+
visibility: hidden;
}
}
diff --git a/common/lib/xmodule/xmodule/js/fixtures/video.html b/common/lib/xmodule/xmodule/js/fixtures/video.html
index f607430ba0..e658912885 100644
--- a/common/lib/xmodule/xmodule/js/fixtures/video.html
+++ b/common/lib/xmodule/xmodule/js/fixtures/video.html
@@ -12,6 +12,7 @@
data-autoplay="False"
data-yt-test-timeout="1500"
data-yt-test-url="https://gdata.youtube.com/feeds/api/videos/"
+ data-autohide-html5="True"
>
diff --git a/common/lib/xmodule/xmodule/js/fixtures/video_all.html b/common/lib/xmodule/xmodule/js/fixtures/video_all.html
index 57052bf65d..b774134cf7 100644
--- a/common/lib/xmodule/xmodule/js/fixtures/video_all.html
+++ b/common/lib/xmodule/xmodule/js/fixtures/video_all.html
@@ -15,6 +15,7 @@
data-autoplay="False"
data-yt-test-timeout="1500"
data-yt-test-url="https://gdata.youtube.com/feeds/api/videos/"
+ data-autohide-html5="True"
>
diff --git a/common/lib/xmodule/xmodule/js/fixtures/video_html5.html b/common/lib/xmodule/xmodule/js/fixtures/video_html5.html
index 32789b6ba9..fcb5a3c319 100644
--- a/common/lib/xmodule/xmodule/js/fixtures/video_html5.html
+++ b/common/lib/xmodule/xmodule/js/fixtures/video_html5.html
@@ -15,6 +15,7 @@
data-autoplay="False"
data-yt-test-timeout="1500"
data-yt-test-url="https://gdata.youtube.com/feeds/api/videos/"
+ data-autohide-html5="True"
>
diff --git a/common/lib/xmodule/xmodule/js/fixtures/video_no_captions.html b/common/lib/xmodule/xmodule/js/fixtures/video_no_captions.html
index 61975784c1..ceb24299e9 100644
--- a/common/lib/xmodule/xmodule/js/fixtures/video_no_captions.html
+++ b/common/lib/xmodule/xmodule/js/fixtures/video_no_captions.html
@@ -12,6 +12,7 @@
data-autoplay="False"
data-yt-test-timeout="1500"
data-yt-test-url="https://gdata.youtube.com/feeds/api/videos/"
+ data-autohide-html5="True"
>
diff --git a/common/lib/xmodule/xmodule/js/fixtures/video_yt_multiple.html b/common/lib/xmodule/xmodule/js/fixtures/video_yt_multiple.html
index c6b40cdf16..bf9272d230 100644
--- a/common/lib/xmodule/xmodule/js/fixtures/video_yt_multiple.html
+++ b/common/lib/xmodule/xmodule/js/fixtures/video_yt_multiple.html
@@ -12,6 +12,7 @@
data-autoplay="False"
data-yt-test-timeout="1500"
data-yt-test-url="https://gdata.youtube.com/feeds/api/videos/"
+ data-autohide-html5="True"
>
@@ -73,6 +74,8 @@
data-autoplay="False"
data-yt-test-timeout="1500"
data-yt-test-url="https://gdata.youtube.com/feeds/api/videos/"
+
+ data-autohide-html5="True"
>
@@ -130,6 +133,8 @@
data-autoplay="False"
data-yt-test-timeout="1500"
data-yt-test-url="https://gdata.youtube.com/feeds/api/videos/"
+
+ data-autohide-html5="True"
>
diff --git a/common/lib/xmodule/xmodule/js/spec/video/video_caption_spec.js b/common/lib/xmodule/xmodule/js/spec/video/video_caption_spec.js
index 0f729da62d..061576efd2 100644
--- a/common/lib/xmodule/xmodule/js/spec/video/video_caption_spec.js
+++ b/common/lib/xmodule/xmodule/js/spec/video/video_caption_spec.js
@@ -1,673 +1,798 @@
-(function() {
- describe('VideoCaption', function() {
- var state, videoPlayer, videoCaption, videoSpeedControl, oldOTBD;
+(function () {
+ describe('VideoCaption', function () {
+ var state, videoPlayer, videoCaption, videoSpeedControl, oldOTBD;
- function initialize() {
- loadFixtures('video_all.html');
- state = new Video('#example');
- videoPlayer = state.videoPlayer;
- videoCaption = state.videoCaption;
- videoSpeedControl = state.videoSpeedControl;
- videoControl = state.videoControl;
- }
+ function initialize() {
+ loadFixtures('video_all.html');
+ state = new Video('#example');
+ videoPlayer = state.videoPlayer;
+ videoCaption = state.videoCaption;
+ videoSpeedControl = state.videoSpeedControl;
+ videoControl = state.videoControl;
+ }
- beforeEach(function() {
- oldOTBD = window.onTouchBasedDevice;
- window.onTouchBasedDevice = jasmine.createSpy('onTouchBasedDevice').andReturn(false);
- initialize();
- });
-
- afterEach(function() {
- YT.Player = void 0;
- $.fn.scrollTo.reset();
- $('.subtitles').remove();
- $('source').remove();
- window.onTouchBasedDevice = oldOTBD;
- });
-
- describe('constructor', function() {
- describe('always', function() {
- beforeEach(function() {
- spyOn($, 'ajaxWithPrefix').andCallThrough();
- initialize();
- });
-
- it('create the caption element', function() {
- expect($('.video')).toContain('ol.subtitles');
- });
-
- it('add caption control to video player', function() {
- expect($('.video')).toContain('a.hide-subtitles');
- });
-
- it('fetch the caption', function() {
- waitsFor(function () {
- if (videoCaption.loaded === true) {
- return true;
- }
-
- return false;
- }, 'Expect captions to be loaded.', 1000);
-
- runs(function () {
- expect($.ajaxWithPrefix).toHaveBeenCalledWith({
- url: videoCaption.captionURL(),
- notifyOnError: false,
- success: jasmine.any(Function),
- error: jasmine.any(Function),
- });
- });
- });
-
- it('bind window resize event', function() {
- expect($(window)).toHandleWith('resize', videoCaption.resize);
- });
-
- it('bind the hide caption button', function() {
- expect($('.hide-subtitles')).toHandleWith('click', videoCaption.toggle);
- });
-
- it('bind the mouse movement', function() {
- expect($('.subtitles')).toHandleWith('mouseover', videoCaption.onMouseEnter);
- expect($('.subtitles')).toHandleWith('mouseout', videoCaption.onMouseLeave);
- expect($('.subtitles')).toHandleWith('mousemove', videoCaption.onMovement);
- expect($('.subtitles')).toHandleWith('mousewheel', videoCaption.onMovement);
- expect($('.subtitles')).toHandleWith('DOMMouseScroll', videoCaption.onMovement);
- });
-
- it('bind the scroll', function() {
- expect($('.subtitles')).toHandleWith('scroll', videoCaption.autoShowCaptions);
- expect($('.subtitles')).toHandleWith('scroll', videoControl.showControls);
- });
- });
-
- describe('when on a non touch-based device', function() {
- beforeEach(function() {
- initialize();
- });
-
- it('render the caption', function() {
- var captionsData;
-
- captionsData = jasmine.stubbedCaption;
- $('.subtitles li[data-index]').each(function(index, link) {
- expect($(link)).toHaveData('index', index);
- expect($(link)).toHaveData('start', captionsData.start[index]);
- expect($(link)).toHaveAttr('tabindex', 0);
- expect($(link)).toHaveText(captionsData.text[index]);
- });
- });
-
- it('add a padding element to caption', function() {
- expect($('.subtitles li:first').hasClass('spacing')).toBe(true);
- expect($('.subtitles li:last').hasClass('spacing')).toBe(true);
- });
-
- it('bind all the caption link', function() {
- $('.subtitles li[data-index]').each(function(index, link) {
- expect($(link)).toHandleWith('mouseover', videoCaption.captionMouseOverOut);
- expect($(link)).toHandleWith('mouseout', videoCaption.captionMouseOverOut);
- expect($(link)).toHandleWith('mousedown', videoCaption.captionMouseDown);
- expect($(link)).toHandleWith('click', videoCaption.captionClick);
- expect($(link)).toHandleWith('focus', videoCaption.captionFocus);
- expect($(link)).toHandleWith('blur', videoCaption.captionBlur);
- expect($(link)).toHandleWith('keydown', videoCaption.captionKeyDown);
- });
- });
-
- it('set rendered to true', function() {
- expect(videoCaption.rendered).toBeTruthy();
- });
- });
-
- describe('when on a touch-based device', function() {
- beforeEach(function() {
- window.onTouchBasedDevice.andReturn(true);
- initialize();
- });
-
- it('show explaination message', function() {
- expect($('.subtitles li')).toHaveHtml("Caption will be displayed when you start playing the video.");
- });
-
- it('does not set rendered to true', function() {
- expect(videoCaption.rendered).toBeFalsy();
- });
- });
-
- describe('when no captions file was specified', function () {
- beforeEach(function () {
- loadFixtures('video_all.html');
-
- // Unspecify the captions file.
- $('#example').find('#video_id').data('sub', '');
-
- state = new Video('#example');
- videoCaption = state.videoCaption;
- });
-
- it('captions panel is not shown', function () {
- expect(videoCaption.hideSubtitlesEl).toBeHidden();
- });
- });
- });
-
- describe('mouse movement', function() {
- // We will store default window.setTimeout() function here.
- var oldSetTimeout = null;
-
- beforeEach(function() {
- // Store original window.setTimeout() function. If we do not do this, then
- // all other tests that rely on code which uses window.setTimeout()
- // function might (and probably will) fail.
- oldSetTimeout = window.setTimeout;
- // Redefine window.setTimeout() function as a spy.
- window.setTimeout = jasmine.createSpy().andCallFake(function(callback, timeout) { return 5; })
- window.setTimeout.andReturn(100);
- spyOn(window, 'clearTimeout');
- });
-
- afterEach(function () {
- // Reset the default window.setTimeout() function. If we do not do this,
- // then all other tests that rely on code which uses window.setTimeout()
- // function might (and probably will) fail.
- window.setTimeout = oldSetTimeout;
- });
-
- describe('when cursor is outside of the caption box', function() {
- beforeEach(function() {
- $(window).trigger(jQuery.Event('mousemove'));
- });
-
- it('does not set freezing timeout', function() {
- expect(videoCaption.frozen).toBeFalsy();
- });
- });
-
- describe('when cursor is in the caption box', function() {
- beforeEach(function() {
- $('.subtitles').trigger(jQuery.Event('mouseenter'));
- });
-
- it('set the freezing timeout', function() {
- expect(videoCaption.frozen).toEqual(100);
- });
-
- describe('when the cursor is moving', function() {
- beforeEach(function() {
- $('.subtitles').trigger(jQuery.Event('mousemove'));
- });
-
- it('reset the freezing timeout', function() {
- expect(window.clearTimeout).toHaveBeenCalledWith(100);
- });
- });
-
- describe('when the mouse is scrolling', function() {
- beforeEach(function() {
- $('.subtitles').trigger(jQuery.Event('mousewheel'));
- });
-
- it('reset the freezing timeout', function() {
- expect(window.clearTimeout).toHaveBeenCalledWith(100);
- });
- });
- });
-
- describe('when cursor is moving out of the caption box', function() {
- beforeEach(function() {
- videoCaption.frozen = 100;
- $.fn.scrollTo.reset();
- });
-
- describe('always', function() {
- beforeEach(function() {
- $('.subtitles').trigger(jQuery.Event('mouseout'));
- });
-
- it('reset the freezing timeout', function() {
- expect(window.clearTimeout).toHaveBeenCalledWith(100);
- });
-
- it('unfreeze the caption', function() {
- expect(videoCaption.frozen).toBeNull();
- });
- });
-
- describe('when the player is playing', function() {
- beforeEach(function() {
- videoCaption.playing = true;
- $('.subtitles li[data-index]:first').addClass('current');
- $('.subtitles').trigger(jQuery.Event('mouseout'));
- });
-
- it('scroll the caption', function() {
- expect($.fn.scrollTo).toHaveBeenCalled();
- });
- });
-
- describe('when the player is not playing', function() {
- beforeEach(function() {
- videoCaption.playing = false;
- $('.subtitles').trigger(jQuery.Event('mouseout'));
- });
-
- it('does not scroll the caption', function() {
- expect($.fn.scrollTo).not.toHaveBeenCalled();
- });
- });
- });
- });
-
- describe('search', function() {
- it('return a correct caption index', function() {
- expect(videoCaption.search(0)).toEqual(-1);
- expect(videoCaption.search(3120)).toEqual(1);
- expect(videoCaption.search(6270)).toEqual(2);
- expect(videoCaption.search(8490)).toEqual(2);
- expect(videoCaption.search(21620)).toEqual(4);
- expect(videoCaption.search(24920)).toEqual(5);
- });
- });
-
- describe('play', function() {
- describe('when the caption was not rendered', function() {
- beforeEach(function() {
- window.onTouchBasedDevice.andReturn(true);
- initialize();
- videoCaption.play();
- });
-
- it('render the caption', function() {
- var captionsData;
-
- captionsData = jasmine.stubbedCaption;
- $('.subtitles li[data-index]').each(function(index, link) {
- expect($(link)).toHaveData('index', index);
- expect($(link)).toHaveData('start', captionsData.start[index]);
- expect($(link)).toHaveAttr('tabindex', 0);
- expect($(link)).toHaveText(captionsData.text[index]);
- });
- });
-
- it('add a padding element to caption', function() {
- expect($('.subtitles li:first')).toBe('.spacing');
- expect($('.subtitles li:last')).toBe('.spacing');
- });
-
- it('bind all the caption link', function() {
- $('.subtitles li[data-index]').each(function(index, link) {
- expect($(link)).toHandleWith('mouseover', videoCaption.captionMouseOverOut);
- expect($(link)).toHandleWith('mouseout', videoCaption.captionMouseOverOut);
- expect($(link)).toHandleWith('mousedown', videoCaption.captionMouseDown);
- expect($(link)).toHandleWith('click', videoCaption.captionClick);
- expect($(link)).toHandleWith('focus', videoCaption.captionFocus);
- expect($(link)).toHandleWith('blur', videoCaption.captionBlur);
- expect($(link)).toHandleWith('keydown', videoCaption.captionKeyDown);
- });
- });
-
- it('set rendered to true', function() {
- expect(videoCaption.rendered).toBeTruthy();
- });
-
- it('set playing to true', function() {
- expect(videoCaption.playing).toBeTruthy();
- });
- });
- });
-
- describe('pause', function() {
- beforeEach(function() {
- videoCaption.playing = true;
- videoCaption.pause();
- });
-
- it('set playing to false', function() {
- expect(videoCaption.playing).toBeFalsy();
- });
- });
-
- describe('updatePlayTime', function() {
- describe('when the video speed is 1.0x', function() {
- beforeEach(function() {
- videoSpeedControl.currentSpeed = '1.0';
- videoCaption.updatePlayTime(25.000);
- });
-
- it('search the caption based on time', function() {
- expect(videoCaption.currentIndex).toEqual(5);
- });
- });
-
- describe('when the video speed is not 1.0x', function() {
- beforeEach(function() {
- videoSpeedControl.currentSpeed = '0.75';
- videoCaption.updatePlayTime(25.000);
- });
-
- it('search the caption based on 1.0x speed', function() {
- expect(videoCaption.currentIndex).toEqual(5);
- });
- });
-
- describe('when the index is not the same', function() {
- beforeEach(function() {
- videoCaption.currentIndex = 1;
- $('.subtitles li[data-index=5]').addClass('current');
- videoCaption.updatePlayTime(25.000);
- });
-
- it('deactivate the previous caption', function() {
- expect($('.subtitles li[data-index=1]')).not.toHaveClass('current');
- });
-
- it('activate new caption', function() {
- expect($('.subtitles li[data-index=5]')).toHaveClass('current');
- });
-
- it('save new index', function() {
- expect(videoCaption.currentIndex).toEqual(5);
- });
-
- it('scroll caption to new position', function() {
- expect($.fn.scrollTo).toHaveBeenCalled();
- });
- });
-
- describe('when the index is the same', function() {
- beforeEach(function() {
- videoCaption.currentIndex = 1;
- $('.subtitles li[data-index=3]').addClass('current');
- videoCaption.updatePlayTime(15.000);
- });
-
- it('does not change current subtitle', function() {
- expect($('.subtitles li[data-index=3]')).toHaveClass('current');
- });
- });
- });
-
- describe('resize', function() {
- beforeEach(function() {
- initialize();
- $('.subtitles li[data-index=1]').addClass('current');
- videoCaption.resize();
- });
-
- describe('set the height of caption container', function(){
- // Temporarily disabled due to intermittent failures
- // with error "Expected 745 to be close to 805, 2." in Firefox
- xit('when CC button is enabled', function() {
- var realHeight = parseInt($('.subtitles').css('maxHeight'), 10),
- shouldBeHeight = $('.video-wrapper').height();
-
- // Because of some problems with rounding on different enviroments:
- // Linux * Mac * FF * Chrome
- expect(realHeight).toBeCloseTo(shouldBeHeight, 2);
- });
-
- it('when CC button is disabled ', function() {
- var realHeight, videoWrapperHeight, progressSliderHeight,
- controlHeight, shouldBeHeight;
-
- state.captionsHidden = true;
- videoCaption.setSubtitlesHeight();
-
- realHeight = parseInt($('.subtitles').css('maxHeight'), 10);
- videoWrapperHeight = $('.video-wrapper').height();
- progressSliderHeight = videoControl.sliderEl.height();
- controlHeight = videoControl.el.height();
- shouldBeHeight = videoWrapperHeight -
- 0.5 * progressSliderHeight -
- controlHeight;
-
- expect(realHeight).toBe(shouldBeHeight);
- });
- });
-
- it('set the height of caption spacing', function() {
- var firstSpacing, lastSpacing;
- firstSpacing = Math.abs(parseInt($('.subtitles .spacing:first').css('height'), 10));
- lastSpacing = Math.abs(parseInt($('.subtitles .spacing:last').css('height'), 10));
- expect(firstSpacing - videoCaption.topSpacingHeight()).toBeLessThan(1);
- expect(lastSpacing - videoCaption.bottomSpacingHeight()).toBeLessThan(1);
- });
-
- it('scroll caption to new position', function() {
- expect($.fn.scrollTo).toHaveBeenCalled();
- });
- });
-
- describe('scrollCaption', function() {
- beforeEach(function() {
- initialize();
- });
-
- describe('when frozen', function() {
- beforeEach(function() {
- videoCaption.frozen = true;
- $('.subtitles li[data-index=1]').addClass('current');
- videoCaption.scrollCaption();
- });
-
- it('does not scroll the caption', function() {
- expect($.fn.scrollTo).not.toHaveBeenCalled();
- });
- });
-
- describe('when not frozen', function() {
- beforeEach(function() {
- videoCaption.frozen = false;
- });
-
- describe('when there is no current caption', function() {
- beforeEach(function() {
- videoCaption.scrollCaption();
- });
-
- it('does not scroll the caption', function() {
- expect($.fn.scrollTo).not.toHaveBeenCalled();
- });
- });
-
- describe('when there is a current caption', function() {
- beforeEach(function() {
- $('.subtitles li[data-index=1]').addClass('current');
- videoCaption.scrollCaption();
- });
-
- it('scroll to current caption', function() {
- expect($.fn.scrollTo).toHaveBeenCalled();
- });
- });
- });
- });
-
- describe('seekPlayer', function() {
- describe('when the video speed is 1.0x', function() {
- beforeEach(function() {
- videoSpeedControl.currentSpeed = '1.0';
- $('.subtitles li[data-start="14910"]').trigger('click');
- });
-
- // Temporarily disabled due to intermittent failures
- // Fails with error: "InvalidStateError: An attempt was made to
- // use an object that is not, or is no longer, usable
- // Expected 0 to equal 14.91."
- // on Firefox
- xit('trigger seek event with the correct time', function() {
- expect(videoPlayer.currentTime).toEqual(14.91);
- });
- });
-
- describe('when the video speed is not 1.0x', function() {
- beforeEach(function() {
- initialize();
- videoSpeedControl.currentSpeed = '0.75';
- $('.subtitles li[data-start="14910"]').trigger('click');
- });
-
- it('trigger seek event with the correct time', function() {
- expect(videoPlayer.currentTime).toEqual(14.91);
- });
- });
-
- describe('when the player type is Flash at speed 0.75x', function () {
beforeEach(function () {
+ oldOTBD = window.onTouchBasedDevice;
+ window.onTouchBasedDevice = jasmine.createSpy('onTouchBasedDevice')
+ .andReturn(false);
initialize();
- videoSpeedControl.currentSpeed = '0.75';
- state.currentPlayerMode = 'flash';
- $('.subtitles li[data-start="14910"]').trigger('click');
});
- it('trigger seek event with the correct time', function () {
- expect(videoPlayer.currentTime).toEqual(15);
+ afterEach(function () {
+ YT.Player = undefined;
+ $.fn.scrollTo.reset();
+ $('.subtitles').remove();
+ $('source').remove();
+ window.onTouchBasedDevice = oldOTBD;
+ });
+
+ describe('constructor', function () {
+ describe('always', function () {
+ beforeEach(function () {
+ spyOn($, 'ajaxWithPrefix').andCallThrough();
+ initialize();
+ });
+
+ it('create the caption element', function () {
+ expect($('.video')).toContain('ol.subtitles');
+ });
+
+ it('add caption control to video player', function () {
+ expect($('.video')).toContain('a.hide-subtitles');
+ });
+
+ it('fetch the caption', function () {
+ waitsFor(function () {
+ if (videoCaption.loaded === true) {
+ return true;
+ }
+
+ return false;
+ }, 'Expect captions to be loaded.', 1000);
+
+ runs(function () {
+ expect($.ajaxWithPrefix).toHaveBeenCalledWith({
+ url: videoCaption.captionURL(),
+ notifyOnError: false,
+ success: jasmine.any(Function),
+ error: jasmine.any(Function)
+ });
+ });
+ });
+
+ it('bind window resize event', function () {
+ expect($(window)).toHandleWith(
+ 'resize', videoCaption.resize
+ );
+ });
+
+ it('bind the hide caption button', function () {
+ expect($('.hide-subtitles')).toHandleWith(
+ 'click', videoCaption.toggle
+ );
+ });
+
+ it('bind the mouse movement', function () {
+ expect($('.subtitles')).toHandleWith(
+ 'mouseover', videoCaption.onMouseEnter
+ );
+ expect($('.subtitles')).toHandleWith(
+ 'mouseout', videoCaption.onMouseLeave
+ );
+ expect($('.subtitles')).toHandleWith(
+ 'mousemove', videoCaption.onMovement
+ );
+ expect($('.subtitles')).toHandleWith(
+ 'mousewheel', videoCaption.onMovement
+ );
+ expect($('.subtitles')).toHandleWith(
+ 'DOMMouseScroll', videoCaption.onMovement
+ );
+ });
+
+ it('bind the scroll', function () {
+ expect($('.subtitles'))
+ .toHandleWith('scroll', videoCaption.autoShowCaptions);
+ expect($('.subtitles'))
+ .toHandleWith('scroll', videoControl.showControls);
+ });
+ });
+
+ describe('when on a non touch-based device', function () {
+ beforeEach(function () {
+ initialize();
+ });
+
+ it('render the caption', function () {
+ var captionsData;
+
+ captionsData = jasmine.stubbedCaption;
+ $('.subtitles li[data-index]').each(
+ function (index, link) {
+
+ expect($(link)).toHaveData('index', index);
+ expect($(link)).toHaveData(
+ 'start', captionsData.start[index]
+ );
+ expect($(link)).toHaveAttr('tabindex', 0);
+ expect($(link)).toHaveText(captionsData.text[index]);
+ });
+ });
+
+ it('add a padding element to caption', function () {
+ expect($('.subtitles li:first').hasClass('spacing'))
+ .toBe(true);
+ expect($('.subtitles li:last').hasClass('spacing'))
+ .toBe(true);
+ });
+
+ it('bind all the caption link', function () {
+ $('.subtitles li[data-index]').each(
+ function (index, link) {
+
+ expect($(link)).toHandleWith(
+ 'mouseover', videoCaption.captionMouseOverOut
+ );
+ expect($(link)).toHandleWith(
+ 'mouseout', videoCaption.captionMouseOverOut
+ );
+ expect($(link)).toHandleWith(
+ 'mousedown', videoCaption.captionMouseDown
+ );
+ expect($(link)).toHandleWith(
+ 'click', videoCaption.captionClick
+ );
+ expect($(link)).toHandleWith(
+ 'focus', videoCaption.captionFocus
+ );
+ expect($(link)).toHandleWith(
+ 'blur', videoCaption.captionBlur
+ );
+ expect($(link)).toHandleWith(
+ 'keydown', videoCaption.captionKeyDown
+ );
+ });
+ });
+
+ it('set rendered to true', function () {
+ expect(videoCaption.rendered).toBeTruthy();
+ });
+ });
+
+ describe('when on a touch-based device', function () {
+ beforeEach(function () {
+ window.onTouchBasedDevice.andReturn(true);
+ initialize();
+ });
+
+ it('show explaination message', function () {
+ expect($('.subtitles li')).toHaveHtml(
+ 'Caption will be displayed when you start playing ' +
+ 'the video.'
+ );
+ });
+
+ it('does not set rendered to true', function () {
+ expect(videoCaption.rendered).toBeFalsy();
+ });
+ });
+
+ describe('when no captions file was specified', function () {
+ beforeEach(function () {
+ loadFixtures('video_all.html');
+
+ // Unspecify the captions file.
+ $('#example').find('#video_id').data('sub', '');
+
+ state = new Video('#example');
+ videoCaption = state.videoCaption;
+ });
+
+ it('captions panel is not shown', function () {
+ expect(videoCaption.hideSubtitlesEl).toBeHidden();
+ });
+ });
+ });
+
+ describe('mouse movement', function () {
+ // We will store default window.setTimeout() function here.
+ var oldSetTimeout = null;
+
+ beforeEach(function () {
+ // Store original window.setTimeout() function. If we do not do
+ // this, then all other tests that rely on code which uses
+ // window.setTimeout() function might (and probably will) fail.
+ oldSetTimeout = window.setTimeout;
+ // Redefine window.setTimeout() function as a spy.
+ window.setTimeout = jasmine.createSpy().andCallFake(
+ function (callback, timeout) {
+ return 5;
+ }
+ );
+ window.setTimeout.andReturn(100);
+ spyOn(window, 'clearTimeout');
+ });
+
+ afterEach(function () {
+ // Reset the default window.setTimeout() function. If we do not
+ // do this, then all other tests that rely on code which uses
+ // window.setTimeout() function might (and probably will) fail.
+ window.setTimeout = oldSetTimeout;
+ });
+
+ describe('when cursor is outside of the caption box', function () {
+ beforeEach(function () {
+ $(window).trigger(jQuery.Event('mousemove'));
+ });
+
+ it('does not set freezing timeout', function () {
+ expect(videoCaption.frozen).toBeFalsy();
+ });
+ });
+
+ describe('when cursor is in the caption box', function () {
+ beforeEach(function () {
+ $('.subtitles').trigger(jQuery.Event('mouseenter'));
+ });
+
+ it('set the freezing timeout', function () {
+ expect(videoCaption.frozen).toEqual(100);
+ });
+
+ describe('when the cursor is moving', function () {
+ beforeEach(function () {
+ $('.subtitles').trigger(jQuery.Event('mousemove'));
+ });
+
+ it('reset the freezing timeout', function () {
+ expect(window.clearTimeout).toHaveBeenCalledWith(100);
+ });
+ });
+
+ describe('when the mouse is scrolling', function () {
+ beforeEach(function () {
+ $('.subtitles').trigger(jQuery.Event('mousewheel'));
+ });
+
+ it('reset the freezing timeout', function () {
+ expect(window.clearTimeout).toHaveBeenCalledWith(100);
+ });
+ });
+ });
+
+ describe(
+ 'when cursor is moving out of the caption box',
+ function () {
+
+ beforeEach(function () {
+ videoCaption.frozen = 100;
+ $.fn.scrollTo.reset();
+ });
+
+ describe('always', function () {
+ beforeEach(function () {
+ $('.subtitles').trigger(jQuery.Event('mouseout'));
+ });
+
+ it('reset the freezing timeout', function () {
+ expect(window.clearTimeout).toHaveBeenCalledWith(100);
+ });
+
+ it('unfreeze the caption', function () {
+ expect(videoCaption.frozen).toBeNull();
+ });
+ });
+
+ describe('when the player is playing', function () {
+ beforeEach(function () {
+ videoCaption.playing = true;
+ $('.subtitles li[data-index]:first')
+ .addClass('current');
+ $('.subtitles').trigger(jQuery.Event('mouseout'));
+ });
+
+ it('scroll the caption', function () {
+ expect($.fn.scrollTo).toHaveBeenCalled();
+ });
+ });
+
+ describe('when the player is not playing', function () {
+ beforeEach(function () {
+ videoCaption.playing = false;
+ $('.subtitles').trigger(jQuery.Event('mouseout'));
+ });
+
+ it('does not scroll the caption', function () {
+ expect($.fn.scrollTo).not.toHaveBeenCalled();
+ });
+ });
+ });
+ });
+
+ describe('search', function () {
+ it('return a correct caption index', function () {
+ expect(videoCaption.search(0)).toEqual(-1);
+ expect(videoCaption.search(3120)).toEqual(1);
+ expect(videoCaption.search(6270)).toEqual(2);
+ expect(videoCaption.search(8490)).toEqual(2);
+ expect(videoCaption.search(21620)).toEqual(4);
+ expect(videoCaption.search(24920)).toEqual(5);
+ });
+ });
+
+ describe('play', function () {
+ describe('when the caption was not rendered', function () {
+ beforeEach(function () {
+ window.onTouchBasedDevice.andReturn(true);
+ initialize();
+ videoCaption.play();
+ });
+
+ it('render the caption', function () {
+ var captionsData;
+
+ captionsData = jasmine.stubbedCaption;
+ $('.subtitles li[data-index]').each(
+ function (index, link) {
+
+ expect($(link)).toHaveData('index', index);
+ expect($(link)).toHaveData(
+ 'start', captionsData.start[index]
+ );
+ expect($(link)).toHaveAttr('tabindex', 0);
+ expect($(link)).toHaveText(captionsData.text[index]);
+ });
+ });
+
+ it('add a padding element to caption', function () {
+ expect($('.subtitles li:first')).toBe('.spacing');
+ expect($('.subtitles li:last')).toBe('.spacing');
+ });
+
+ it('bind all the caption link', function () {
+ $('.subtitles li[data-index]').each(
+ function (index, link) {
+
+ expect($(link)).toHandleWith(
+ 'mouseover', videoCaption.captionMouseOverOut
+ );
+ expect($(link)).toHandleWith(
+ 'mouseout', videoCaption.captionMouseOverOut
+ );
+ expect($(link)).toHandleWith(
+ 'mousedown', videoCaption.captionMouseDown
+ );
+ expect($(link)).toHandleWith(
+ 'click', videoCaption.captionClick
+ );
+ expect($(link)).toHandleWith(
+ 'focus', videoCaption.captionFocus
+ );
+ expect($(link)).toHandleWith(
+ 'blur', videoCaption.captionBlur
+ );
+ expect($(link)).toHandleWith(
+ 'keydown', videoCaption.captionKeyDown
+ );
+ });
+ });
+
+ it('set rendered to true', function () {
+ expect(videoCaption.rendered).toBeTruthy();
+ });
+
+ it('set playing to true', function () {
+ expect(videoCaption.playing).toBeTruthy();
+ });
+ });
+ });
+
+ describe('pause', function () {
+ beforeEach(function () {
+ videoCaption.playing = true;
+ videoCaption.pause();
+ });
+
+ it('set playing to false', function () {
+ expect(videoCaption.playing).toBeFalsy();
+ });
+ });
+
+ describe('updatePlayTime', function () {
+ describe('when the video speed is 1.0x', function () {
+ beforeEach(function () {
+ videoSpeedControl.currentSpeed = '1.0';
+ videoCaption.updatePlayTime(25.000);
+ });
+
+ it('search the caption based on time', function () {
+ expect(videoCaption.currentIndex).toEqual(5);
+ });
+ });
+
+ describe('when the video speed is not 1.0x', function () {
+ beforeEach(function () {
+ videoSpeedControl.currentSpeed = '0.75';
+ videoCaption.updatePlayTime(25.000);
+ });
+
+ it('search the caption based on 1.0x speed', function () {
+ expect(videoCaption.currentIndex).toEqual(5);
+ });
+ });
+
+ describe('when the index is not the same', function () {
+ beforeEach(function () {
+ videoCaption.currentIndex = 1;
+ $('.subtitles li[data-index=5]').addClass('current');
+ videoCaption.updatePlayTime(25.000);
+ });
+
+ it('deactivate the previous caption', function () {
+ expect($('.subtitles li[data-index=1]'))
+ .not.toHaveClass('current');
+ });
+
+ it('activate new caption', function () {
+ expect($('.subtitles li[data-index=5]'))
+ .toHaveClass('current');
+ });
+
+ it('save new index', function () {
+ expect(videoCaption.currentIndex).toEqual(5);
+ });
+
+ it('scroll caption to new position', function () {
+ expect($.fn.scrollTo).toHaveBeenCalled();
+ });
+ });
+
+ describe('when the index is the same', function () {
+ beforeEach(function () {
+ videoCaption.currentIndex = 1;
+ $('.subtitles li[data-index=3]').addClass('current');
+ videoCaption.updatePlayTime(15.000);
+ });
+
+ it('does not change current subtitle', function () {
+ expect($('.subtitles li[data-index=3]'))
+ .toHaveClass('current');
+ });
+ });
+ });
+
+ describe('resize', function () {
+ beforeEach(function () {
+ initialize();
+ $('.subtitles li[data-index=1]').addClass('current');
+ videoCaption.resize();
+ });
+
+ describe('set the height of caption container', function () {
+ // Temporarily disabled due to intermittent failures
+ // with error "Expected 745 to be close to 805, 2." in Firefox
+ xit('when CC button is enabled', function () {
+ var realHeight = parseInt(
+ $('.subtitles').css('maxHeight'), 10
+ ),
+ shouldBeHeight = $('.video-wrapper').height();
+
+ // Because of some problems with rounding on different
+ // environments: Linux * Mac * FF * Chrome
+ expect(realHeight).toBeCloseTo(shouldBeHeight, 2);
+ });
+
+ it('when CC button is disabled ', function () {
+ var realHeight, videoWrapperHeight, progressSliderHeight,
+ controlHeight, shouldBeHeight;
+
+ state.captionsHidden = true;
+ videoCaption.setSubtitlesHeight();
+
+ realHeight = parseInt(
+ $('.subtitles').css('maxHeight'), 10
+ );
+ videoWrapperHeight = $('.video-wrapper').height();
+ progressSliderHeight = videoControl.sliderEl.height();
+ controlHeight = videoControl.el.height();
+ shouldBeHeight = videoWrapperHeight -
+ 0.5 * progressSliderHeight -
+ controlHeight;
+
+ expect(realHeight).toBe(shouldBeHeight);
+ });
+ });
+
+ it('set the height of caption spacing', function () {
+ var firstSpacing, lastSpacing;
+
+ firstSpacing = Math.abs(parseInt(
+ $('.subtitles .spacing:first').css('height'), 10
+ ));
+ lastSpacing = Math.abs(parseInt(
+ $('.subtitles .spacing:last').css('height'), 10
+ ));
+
+ expect(firstSpacing - videoCaption.topSpacingHeight())
+ .toBeLessThan(1);
+ expect(lastSpacing - videoCaption.bottomSpacingHeight())
+ .toBeLessThan(1);
+ });
+
+ it('scroll caption to new position', function () {
+ expect($.fn.scrollTo).toHaveBeenCalled();
+ });
+ });
+
+ describe('scrollCaption', function () {
+ beforeEach(function () {
+ initialize();
+ });
+
+ describe('when frozen', function () {
+ beforeEach(function () {
+ videoCaption.frozen = true;
+ $('.subtitles li[data-index=1]').addClass('current');
+ videoCaption.scrollCaption();
+ });
+
+ it('does not scroll the caption', function () {
+ expect($.fn.scrollTo).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('when not frozen', function () {
+ beforeEach(function () {
+ videoCaption.frozen = false;
+ });
+
+ describe('when there is no current caption', function () {
+ beforeEach(function () {
+ videoCaption.scrollCaption();
+ });
+
+ it('does not scroll the caption', function () {
+ expect($.fn.scrollTo).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('when there is a current caption', function () {
+ beforeEach(function () {
+ $('.subtitles li[data-index=1]').addClass('current');
+ videoCaption.scrollCaption();
+ });
+
+ it('scroll to current caption', function () {
+ expect($.fn.scrollTo).toHaveBeenCalled();
+ });
+ });
+ });
+ });
+
+ describe('seekPlayer', function () {
+ describe('when the video speed is 1.0x', function () {
+ beforeEach(function () {
+ videoSpeedControl.currentSpeed = '1.0';
+ $('.subtitles li[data-start="14910"]').trigger('click');
+ });
+
+ // Temporarily disabled due to intermittent failures
+ // Fails with error: "InvalidStateError: An attempt was made to
+ // use an object that is not, or is no longer, usable
+ // Expected 0 to equal 14.91."
+ // on Firefox
+ xit('trigger seek event with the correct time', function () {
+ expect(videoPlayer.currentTime).toEqual(14.91);
+ });
+ });
+
+ describe('when the video speed is not 1.0x', function () {
+ beforeEach(function () {
+ initialize();
+ videoSpeedControl.currentSpeed = '0.75';
+ $('.subtitles li[data-start="14910"]').trigger('click');
+ });
+
+ it('trigger seek event with the correct time', function () {
+ expect(videoPlayer.currentTime).toEqual(14.91);
+ });
+ });
+
+ describe('when the player type is Flash at speed 0.75x',
+ function () {
+
+ beforeEach(function () {
+ initialize();
+ videoSpeedControl.currentSpeed = '0.75';
+ state.currentPlayerMode = 'flash';
+ $('.subtitles li[data-start="14910"]').trigger('click');
+ });
+
+ it('trigger seek event with the correct time', function () {
+ expect(videoPlayer.currentTime).toEqual(15);
+ });
+ });
+ });
+
+ describe('toggle', function () {
+ beforeEach(function () {
+ initialize();
+ spyOn(videoPlayer, 'log');
+ $('.subtitles li[data-index=1]').addClass('current');
+ });
+
+ describe('when the caption is visible', function () {
+ beforeEach(function () {
+ state.el.removeClass('closed');
+ videoCaption.toggle(jQuery.Event('click'));
+ });
+
+ it('log the hide_transcript event', function () {
+ expect(videoPlayer.log).toHaveBeenCalledWith(
+ 'hide_transcript',
+ {
+ currentTime: videoPlayer.currentTime
+ }
+ );
+ });
+
+ it('hide the caption', function () {
+ expect(state.el).toHaveClass('closed');
+ });
+ });
+
+ describe('when the caption is hidden', function () {
+ beforeEach(function () {
+ state.el.addClass('closed');
+ videoCaption.toggle(jQuery.Event('click'));
+ });
+
+ it('log the show_transcript event', function () {
+ expect(videoPlayer.log).toHaveBeenCalledWith(
+ 'show_transcript',
+ {
+ currentTime: videoPlayer.currentTime
+ }
+ );
+ });
+
+ it('show the caption', function () {
+ expect(state.el).not.toHaveClass('closed');
+ });
+
+ it('scroll the caption', function () {
+ expect($.fn.scrollTo).toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('caption accessibility', function () {
+ beforeEach(function () {
+ initialize();
+ });
+
+ describe('when getting focus through TAB key', function () {
+ beforeEach(function () {
+ videoCaption.isMouseFocus = false;
+ $('.subtitles li[data-index=0]').trigger(
+ jQuery.Event('focus')
+ );
+ });
+
+ it('shows an outline around the caption', function () {
+ expect($('.subtitles li[data-index=0]'))
+ .toHaveClass('focused');
+ });
+
+ it('has automatic scrolling disabled', function () {
+ expect(videoCaption.autoScrolling).toBe(false);
+ });
+ });
+
+ describe('when loosing focus through TAB key', function () {
+ beforeEach(function () {
+ $('.subtitles li[data-index=0]').trigger(
+ jQuery.Event('blur')
+ );
+ });
+
+ it('does not show an outline around the caption', function () {
+ expect($('.subtitles li[data-index=0]'))
+ .not.toHaveClass('focused');
+ });
+
+ it('has automatic scrolling enabled', function () {
+ expect(videoCaption.autoScrolling).toBe(true);
+ });
+ });
+
+ describe(
+ 'when same caption gets the focus through mouse after ' +
+ 'having focus through TAB key',
+ function () {
+
+ beforeEach(function () {
+ videoCaption.isMouseFocus = false;
+ $('.subtitles li[data-index=0]')
+ .trigger(jQuery.Event('focus'));
+ $('.subtitles li[data-index=0]')
+ .trigger(jQuery.Event('mousedown'));
+ });
+
+ it('does not show an outline around it', function () {
+ expect($('.subtitles li[data-index=0]'))
+ .not.toHaveClass('focused');
+ });
+
+ it('has automatic scrolling enabled', function () {
+ expect(videoCaption.autoScrolling).toBe(true);
+ });
+ });
+
+ describe(
+ 'when a second caption gets focus through mouse after ' +
+ 'first had focus through TAB key',
+ function () {
+
+ var subDataLiIdx__0, subDataLiIdx__1;
+
+ beforeEach(function () {
+ subDataLiIdx__0 = $('.subtitles li[data-index=0]');
+ subDataLiIdx__1 = $('.subtitles li[data-index=1]');
+
+ videoCaption.isMouseFocus = false;
+
+ subDataLiIdx__0.trigger(jQuery.Event('focus'));
+ subDataLiIdx__0.trigger(jQuery.Event('blur'));
+
+ videoCaption.isMouseFocus = true;
+
+ subDataLiIdx__1.trigger(jQuery.Event('mousedown'));
+ });
+
+ it('does not show an outline around the first', function () {
+ expect(subDataLiIdx__0).not.toHaveClass('focused');
+ });
+
+ it('does not show an outline around the second', function () {
+ expect(subDataLiIdx__1).not.toHaveClass('focused');
+ });
+
+ it('has automatic scrolling enabled', function () {
+ expect(videoCaption.autoScrolling).toBe(true);
+ });
+ });
+
+ xdescribe('when enter key is pressed on a caption', function () {
+ var subDataLiIdx__0;
+
+ beforeEach(function () {
+ var e;
+
+ subDataLiIdx__0 = $('.subtitles li[data-index=0]');
+
+ spyOn(videoCaption, 'seekPlayer').andCallThrough();
+ videoCaption.isMouseFocus = false;
+ subDataLiIdx__0.trigger(jQuery.Event('focus'));
+ e = jQuery.Event('keydown');
+ e.which = 13; // ENTER key
+ subDataLiIdx__0.trigger(e);
+ });
+
+ // Temporarily disabled due to intermittent failures.
+ //
+ // Fails with error: "InvalidStateError: InvalidStateError: An
+ // attempt was made to use an object that is not, or is no
+ // longer, usable".
+ xit('shows an outline around it', function () {
+ expect(subDataLiIdx__0).toHaveClass('focused');
+ });
+
+ xit('calls seekPlayer', function () {
+ expect(videoCaption.seekPlayer).toHaveBeenCalled();
+ });
+ });
});
- });
});
- describe('toggle', function() {
- beforeEach(function() {
- initialize();
- spyOn(videoPlayer, 'log');
- $('.subtitles li[data-index=1]').addClass('current');
- });
-
- describe('when the caption is visible', function() {
- beforeEach(function() {
- state.el.removeClass('closed');
- videoCaption.toggle(jQuery.Event('click'));
- });
-
- it('log the hide_transcript event', function() {
- expect(videoPlayer.log).toHaveBeenCalledWith('hide_transcript', {
- currentTime: videoPlayer.currentTime
- });
- });
-
- it('hide the caption', function() {
- expect(state.el).toHaveClass('closed');
- });
- });
-
- describe('when the caption is hidden', function() {
- beforeEach(function() {
- state.el.addClass('closed');
- videoCaption.toggle(jQuery.Event('click'));
- });
-
- it('log the show_transcript event', function() {
- expect(videoPlayer.log).toHaveBeenCalledWith('show_transcript', {
- currentTime: videoPlayer.currentTime
- });
- });
-
- it('show the caption', function() {
- expect(state.el).not.toHaveClass('closed');
- });
-
- it('scroll the caption', function() {
- expect($.fn.scrollTo).toHaveBeenCalled();
- });
- });
- });
-
- describe('caption accessibility', function() {
- beforeEach(function() {
- initialize();
- });
-
- describe('when getting focus through TAB key', function() {
- beforeEach(function() {
- videoCaption.isMouseFocus = false;
- $('.subtitles li[data-index=0]').trigger(jQuery.Event('focus'));
- });
-
- it('shows an outline around the caption', function() {
- expect($('.subtitles li[data-index=0]')).toHaveClass('focused');
- });
-
- it('has automatic scrolling disabled', function() {
- expect(videoCaption.autoScrolling).toBe(false);
- });
- });
-
- describe('when loosing focus through TAB key', function() {
- beforeEach(function() {
- $('.subtitles li[data-index=0]').trigger(jQuery.Event('blur'));
- });
-
- it('does not show an outline around the caption', function() {
- expect($('.subtitles li[data-index=0]')).not.toHaveClass('focused');
- });
-
- it('has automatic scrolling enabled', function() {
- expect(videoCaption.autoScrolling).toBe(true);
- });
- });
-
- describe('when same caption gets the focus through mouse after having focus through TAB key', function() {
- beforeEach(function() {
- videoCaption.isMouseFocus = false;
- $('.subtitles li[data-index=0]').trigger(jQuery.Event('focus'));
- $('.subtitles li[data-index=0]').trigger(jQuery.Event('mousedown'));
- });
-
- it('does not show an outline around it', function() {
- expect($('.subtitles li[data-index=0]')).not.toHaveClass('focused');
- });
-
- it('has automatic scrolling enabled', function() {
- expect(videoCaption.autoScrolling).toBe(true);
- });
- });
-
- describe('when a second caption gets focus through mouse after first had focus through TAB key', function() {
- beforeEach(function() {
- videoCaption.isMouseFocus = false;
- $('.subtitles li[data-index=0]').trigger(jQuery.Event('focus'));
- $('.subtitles li[data-index=0]').trigger(jQuery.Event('blur'));
- videoCaption.isMouseFocus = true;
- $('.subtitles li[data-index=1]').trigger(jQuery.Event('mousedown'));
- });
-
- it('does not show an outline around the first', function() {
- expect($('.subtitles li[data-index=0]')).not.toHaveClass('focused');
- });
-
- it('does not show an outline around the second', function() {
- expect($('.subtitles li[data-index=1]')).not.toHaveClass('focused');
- });
-
- it('has automatic scrolling enabled', function() {
- expect(videoCaption.autoScrolling).toBe(true);
- });
- });
-
- xdescribe('when enter key is pressed on a caption', function() {
- beforeEach(function() {
- var e;
- spyOn(videoCaption, 'seekPlayer').andCallThrough();
- videoCaption.isMouseFocus = false;
- $('.subtitles li[data-index=0]').trigger(jQuery.Event('focus'));
- e = jQuery.Event('keydown');
- e.which = 13; // ENTER key
- $('.subtitles li[data-index=0]').trigger(e);
- });
-
- // Temporarily disabled due to intermittent failures
- // Fails with error: "InvalidStateError: InvalidStateError: An attempt
- // was made to use an object that is not, or is no longer, usable"
- xit('shows an outline around it', function() {
- expect($('.subtitles li[data-index=0]')).toHaveClass('focused');
- });
-
- xit('calls seekPlayer', function() {
- expect(videoCaption.seekPlayer).toHaveBeenCalled();
- });
- });
- });
- });
-
}).call(this);
diff --git a/common/lib/xmodule/xmodule/js/src/video/01_initialize.js b/common/lib/xmodule/xmodule/js/src/video/01_initialize.js
index 7f8057c025..845d79ddb9 100644
--- a/common/lib/xmodule/xmodule/js/src/video/01_initialize.js
+++ b/common/lib/xmodule/xmodule/js/src/video/01_initialize.js
@@ -264,15 +264,21 @@ function (VideoPlayer) {
// The function set initial configuration and preparation.
function initialize(element) {
- var _this = this, tempYtTestTimeout;
+ var _this = this,
+ regExp = /^true$/i,
+ data, tempYtTestTimeout;
// This is used in places where we instead would have to check if an
// element has a CSS class 'fullscreen'.
this.isFullScreen = false;
// The parent element of the video, and the ID.
this.el = $(element).find('.video');
+ this.elVideoWrapper = this.el.find('.video-wrapper');
this.id = this.el.attr('id').replace(/video_/, '');
+ // jQuery .data() return object with keys in lower camelCase format.
+ data = this.el.data();
+
console.log(
'[Video info]: Initializing video with id "' + this.id + '".'
);
@@ -283,32 +289,26 @@ function (VideoPlayer) {
this.config = {
element: element,
- start: this.el.data('start'),
- end: this.el.data('end'),
-
- caption_data_dir: this.el.data('caption-data-dir'),
- caption_asset_path: this.el.data('caption-asset-path'),
- show_captions: (
- this.el.data('show-captions')
- .toString().toLowerCase() === 'true'
- ),
- youtubeStreams: this.el.data('streams'),
-
- sub: this.el.data('sub'),
- mp4Source: this.el.data('mp4-source'),
- webmSource: this.el.data('webm-source'),
- oggSource: this.el.data('ogg-source'),
-
- ytTestUrl: this.el.data('yt-test-url'),
-
+ start: data['start'],
+ end: data['end'],
+ caption_data_dir: data['captionDataDir'],
+ caption_asset_path: data['captionAssetPath'],
+ show_captions: regExp.test(data['showCaptions'].toString()),
+ youtubeStreams: data['streams'],
+ autohideHtml5: regExp.test(data['autohideHtml5'].toString()),
+ sub: data['sub'],
+ mp4Source: data['mp4Source'],
+ webmSource: data['webmSource'],
+ oggSource: data['oggSource'],
+ ytTestUrl: data['ytTestUrl'],
fadeOutTimeout: 1400,
-
+ captionsFreezeTime: 10000,
availableQualities: ['hd720', 'hd1080', 'highres']
};
// Check if the YT test timeout has been set. If not, or it is in
// improper format, then set to default value.
- tempYtTestTimeout = parseInt(this.el.data('yt-test-timeout'), 10);
+ tempYtTestTimeout = parseInt(data['ytTestTimeout'], 10);
if (!isFinite(tempYtTestTimeout)) {
tempYtTestTimeout = 1500;
}
diff --git a/common/lib/xmodule/xmodule/js/src/video/04_video_control.js b/common/lib/xmodule/xmodule/js/src/video/04_video_control.js
index 796ba07060..8ba7490cef 100644
--- a/common/lib/xmodule/xmodule/js/src/video/04_video_control.js
+++ b/common/lib/xmodule/xmodule/js/src/video/04_video_control.js
@@ -57,7 +57,7 @@ function () {
state.videoControl.play();
}
- if (state.videoType === 'html5') {
+ if ((state.videoType === 'html5') && (state.config.autohideHtml5)) {
state.videoControl.fadeOutTimeout = state.config.fadeOutTimeout;
state.videoControl.el.addClass('html5');
@@ -81,7 +81,7 @@ function () {
state.videoControl.fullScreenEl.on('click', state.videoControl.toggleFullScreen);
$(document).on('keyup', state.videoControl.exitFullScreen);
- if (state.videoType === 'html5') {
+ if ((state.videoType === 'html5') && (state.config.autohideHtml5)) {
state.el.on('mousemove', state.videoControl.showControls);
state.el.on('keydown', state.videoControl.showControls);
}
diff --git a/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js b/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js
index 71198335e6..f9efbdecb5 100644
--- a/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js
+++ b/common/lib/xmodule/xmodule/js/src/video/09_video_caption.js
@@ -34,46 +34,63 @@ function () {
// function _makeFunctionsPublic(state)
//
- // Functions which will be accessible via 'state' object. When called, these functions will
- // get the 'state' object as a context.
+ // Functions which will be accessible via 'state' object. When called,
+ // these functions will get the 'state' object as a context.
function _makeFunctionsPublic(state) {
- state.videoCaption.autoShowCaptions = _.bind(autoShowCaptions, state);
- state.videoCaption.autoHideCaptions = _.bind(autoHideCaptions, state);
- state.videoCaption.resize = _.bind(resize, state);
- state.videoCaption.toggle = _.bind(toggle, state);
- state.videoCaption.onMouseEnter = _.bind(onMouseEnter, state);
- state.videoCaption.onMouseLeave = _.bind(onMouseLeave, state);
- state.videoCaption.onMovement = _.bind(onMovement, state);
- state.videoCaption.renderCaption = _.bind(renderCaption, state);
- state.videoCaption.captionHeight = _.bind(captionHeight, state);
- state.videoCaption.topSpacingHeight = _.bind(topSpacingHeight, state);
- state.videoCaption.bottomSpacingHeight = _.bind(bottomSpacingHeight, state);
- state.videoCaption.scrollCaption = _.bind(scrollCaption, state);
- state.videoCaption.search = _.bind(search, state);
- state.videoCaption.play = _.bind(play, state);
- state.videoCaption.pause = _.bind(pause, state);
- state.videoCaption.seekPlayer = _.bind(seekPlayer, state);
- state.videoCaption.hideCaptions = _.bind(hideCaptions, state);
- state.videoCaption.calculateOffset = _.bind(calculateOffset, state);
- state.videoCaption.updatePlayTime = _.bind(updatePlayTime, state);
- state.videoCaption.setSubtitlesHeight = _.bind(setSubtitlesHeight, state);
+ state.videoCaption.autoShowCaptions = _.bind(
+ autoShowCaptions, state
+ );
+ state.videoCaption.autoHideCaptions = _.bind(
+ autoHideCaptions, state
+ );
+ state.videoCaption.resize = _.bind(resize, state);
+ state.videoCaption.toggle = _.bind(toggle, state);
+ state.videoCaption.onMouseEnter = _.bind(onMouseEnter, state);
+ state.videoCaption.onMouseLeave = _.bind(onMouseLeave, state);
+ state.videoCaption.onMovement = _.bind(onMovement, state);
+ state.videoCaption.renderCaption = _.bind(renderCaption, state);
+ state.videoCaption.captionHeight = _.bind(captionHeight, state);
+ state.videoCaption.topSpacingHeight = _.bind(
+ topSpacingHeight, state
+ );
+ state.videoCaption.bottomSpacingHeight = _.bind(
+ bottomSpacingHeight, state
+ );
+ state.videoCaption.scrollCaption = _.bind(scrollCaption, state);
+ state.videoCaption.search = _.bind(search, state);
+ state.videoCaption.play = _.bind(play, state);
+ state.videoCaption.pause = _.bind(pause, state);
+ state.videoCaption.seekPlayer = _.bind(seekPlayer, state);
+ state.videoCaption.hideCaptions = _.bind(hideCaptions, state);
+ state.videoCaption.calculateOffset = _.bind(
+ calculateOffset, state
+ );
+ state.videoCaption.updatePlayTime = _.bind(updatePlayTime, state);
+ state.videoCaption.setSubtitlesHeight = _.bind(
+ setSubtitlesHeight, state
+ );
- state.videoCaption.renderElements = _.bind(renderElements, state);
- state.videoCaption.bindHandlers = _.bind(bindHandlers, state);
- state.videoCaption.fetchCaption = _.bind(fetchCaption, state);
- state.videoCaption.captionURL = _.bind(captionURL, state);
- state.videoCaption.captionMouseOverOut = _.bind(captionMouseOverOut, state);
- state.videoCaption.captionMouseDown = _.bind(captionMouseDown, state);
- state.videoCaption.captionClick = _.bind(captionClick, state);
- state.videoCaption.captionFocus = _.bind(captionFocus, state);
- state.videoCaption.captionBlur = _.bind(captionBlur, state);
- state.videoCaption.captionKeyDown = _.bind(captionKeyDown, state);
+ state.videoCaption.renderElements = _.bind(renderElements, state);
+ state.videoCaption.bindHandlers = _.bind(bindHandlers, state);
+ state.videoCaption.fetchCaption = _.bind(fetchCaption, state);
+ state.videoCaption.captionURL = _.bind(captionURL, state);
+ state.videoCaption.captionMouseOverOut = _.bind(
+ captionMouseOverOut, state
+ );
+ state.videoCaption.captionMouseDown = _.bind(
+ captionMouseDown, state
+ );
+ state.videoCaption.captionClick = _.bind(captionClick, state);
+ state.videoCaption.captionFocus = _.bind(captionFocus, state);
+ state.videoCaption.captionBlur = _.bind(captionBlur, state);
+ state.videoCaption.captionKeyDown = _.bind(captionKeyDown, state);
}
// ***************************************************************
// Public functions start here.
- // These are available via the 'state' object. Their context ('this' keyword) is the 'state' object.
- // The magic private function that makes them available and sets up their context is makeFunctionsPublic().
+ // These are available via the 'state' object. Their context ('this'
+ // keyword) is the 'state' object. The magic private function that makes
+ // them available and sets up their context is makeFunctionsPublic().
// ***************************************************************
/**
@@ -109,10 +126,13 @@ function () {
// function bindHandlers()
//
- // Bind any necessary function callbacks to DOM events (click, mousemove, etc.).
+ // Bind any necessary function callbacks to DOM events (click,
+ // mousemove, etc.).
function bindHandlers() {
$(window).bind('resize', this.videoCaption.resize);
- this.videoCaption.hideSubtitlesEl.on('click', this.videoCaption.toggle);
+ this.videoCaption.hideSubtitlesEl.on(
+ 'click', this.videoCaption.toggle
+ );
this.videoCaption.subtitlesEl
.on(
@@ -132,14 +152,41 @@ function () {
this.videoCaption.onMovement
);
- if (this.videoType === 'html5') {
- this.el.on('mousemove', this.videoCaption.autoShowCaptions);
- this.el.on('keydown', this.videoCaption.autoShowCaptions);
+ if ((this.videoType === 'html5') && (this.config.autohideHtml5)) {
+ this.el.on({
+ mousemove: this.videoCaption.autoShowCaptions,
+ keydown: this.videoCaption.autoShowCaptions
+ });
- // Moving slider on subtitles is not a mouse move,
- // but captions and controls should be showed.
- this.videoCaption.subtitlesEl.on('scroll', this.videoCaption.autoShowCaptions);
- this.videoCaption.subtitlesEl.on('scroll', this.videoControl.showControls);
+ // Moving slider on subtitles is not a mouse move, but captions and
+ // controls should be shown.
+ this.videoCaption.subtitlesEl
+ .on(
+ 'scroll', this.videoCaption.autoShowCaptions
+ )
+ .on(
+ 'scroll', this.videoControl.showControls
+ );
+ } else if (!this.config.autohideHtml5) {
+ this.videoCaption.subtitlesEl.on({
+ keydown: this.videoCaption.autoShowCaptions,
+ focus: this.videoCaption.autoShowCaptions,
+
+ // Moving slider on subtitles is not a mouse move, but captions
+ // should not be auto-hidden.
+ scroll: this.videoCaption.autoShowCaptions,
+
+ mouseout: this.videoCaption.autoHideCaptions,
+ blur: this.videoCaption.autoHideCaptions
+ });
+
+ this.videoCaption.hideSubtitlesEl.on({
+ mousemove: this.videoCaption.autoShowCaptions,
+ focus: this.videoCaption.autoShowCaptions,
+
+ mouseout: this.videoCaption.autoHideCaptions,
+ blur: this.videoCaption.autoHideCaptions
+ });
}
}
@@ -209,7 +256,8 @@ function () {
}
function captionURL() {
- return '' + this.config.caption_asset_path + this.youtubeId('1.0') + '.srt.sjson';
+ return '' + this.config.caption_asset_path +
+ this.youtubeId('1.0') + '.srt.sjson';
}
function autoShowCaptions(event) {
@@ -224,13 +272,19 @@ function () {
this.videoCaption.subtitlesEl.show();
this.captionState = 'visible';
} else if (this.captionState === 'hiding') {
- this.videoCaption.subtitlesEl.stop(true, false).css('opacity', 1).show();
+ this.videoCaption.subtitlesEl
+ .stop(true, false).css('opacity', 1).show();
this.captionState = 'visible';
} else if (this.captionState === 'visible') {
clearTimeout(this.captionHideTimeout);
}
- this.captionHideTimeout = setTimeout(this.videoCaption.autoHideCaptions, this.videoCaption.fadeOutTimeout);
+ if (this.config.autohideHtml5) {
+ this.captionHideTimeout = setTimeout(
+ this.videoCaption.autoHideCaptions,
+ this.videoCaption.fadeOutTimeout
+ );
+ }
this.captionsShowLock = false;
}
@@ -249,15 +303,21 @@ function () {
_this = this;
- this.videoCaption.subtitlesEl.fadeOut(this.videoCaption.fadeOutTimeout, function () {
- _this.captionState = 'invisible';
- });
+ this.videoCaption.subtitlesEl
+ .fadeOut(
+ this.videoCaption.fadeOutTimeout,
+ function () {
+ _this.captionState = 'invisible';
+ }
+ );
}
function resize() {
this.videoCaption.subtitlesEl
- .find('.spacing:first').height(this.videoCaption.topSpacingHeight())
- .find('.spacing:last').height(this.videoCaption.bottomSpacingHeight());
+ .find('.spacing:first')
+ .height(this.videoCaption.topSpacingHeight())
+ .find('.spacing:last')
+ .height(this.videoCaption.bottomSpacingHeight());
this.videoCaption.scrollCaption();
@@ -269,7 +329,10 @@ function () {
clearTimeout(this.videoCaption.frozen);
}
- this.videoCaption.frozen = setTimeout(this.videoCaption.onMouseLeave, 10000);
+ this.videoCaption.frozen = setTimeout(
+ this.videoCaption.onMouseLeave,
+ this.config.captionsFreezeTime
+ );
}
function onMouseLeave() {
@@ -285,6 +348,10 @@ function () {
}
function onMovement() {
+ if (!this.config.autohideHtml5) {
+ this.videoCaption.autoShowCaptions();
+ }
+
this.videoCaption.onMouseEnter();
}
@@ -292,16 +359,28 @@ function () {
var container = $(''),
_this = this;
- this.el.find('.video-wrapper').after(this.videoCaption.subtitlesEl);
- this.el.find('.video-controls .secondary-controls').append(this.videoCaption.hideSubtitlesEl);
+ this.elVideoWrapper.after(this.videoCaption.subtitlesEl);
+ this.el.find('.video-controls .secondary-controls')
+ .append(this.videoCaption.hideSubtitlesEl);
this.videoCaption.setSubtitlesHeight();
- if (this.videoType === 'html5') {
+ if ((this.videoType === 'html5') && (this.config.autohideHtml5)) {
this.videoCaption.fadeOutTimeout = this.config.fadeOutTimeout;
this.videoCaption.subtitlesEl.addClass('html5');
- this.captionHideTimeout = setTimeout(this.videoCaption.autoHideCaptions, this.videoCaption.fadeOutTimeout);
+ this.captionHideTimeout = setTimeout(
+ this.videoCaption.autoHideCaptions,
+ this.videoCaption.fadeOutTimeout
+ );
+ } else if (!this.config.autohideHtml5) {
+ this.videoCaption.fadeOutTimeout = this.config.fadeOutTimeout;
+ this.videoCaption.subtitlesEl.addClass('html5');
+
+ this.captionHideTimeout = setTimeout(
+ this.videoCaption.autoHideCaptions,
+ 0
+ );
}
this.videoCaption.hideCaptions(this.hide_captions);
@@ -322,17 +401,18 @@ function () {
container.append(liEl);
});
- this.videoCaption.subtitlesEl.html(container.html());
-
- this.videoCaption.subtitlesEl.find('li[data-index]').on({
- mouseover: this.videoCaption.captionMouseOverOut,
- mouseout: this.videoCaption.captionMouseOverOut,
- mousedown: this.videoCaption.captionMouseDown,
- click: this.videoCaption.captionClick,
- focus: this.videoCaption.captionFocus,
- blur: this.videoCaption.captionBlur,
- keydown: this.videoCaption.captionKeyDown
- });
+ this.videoCaption.subtitlesEl
+ .html(container.html())
+ .find('li[data-index]')
+ .on({
+ mouseover: this.videoCaption.captionMouseOverOut,
+ mouseout: this.videoCaption.captionMouseOverOut,
+ mousedown: this.videoCaption.captionMouseDown,
+ click: this.videoCaption.captionClick,
+ focus: this.videoCaption.captionFocus,
+ blur: this.videoCaption.captionBlur,
+ keydown: this.videoCaption.captionKeyDown
+ });
// Enables or disables automatic scrolling of the captions when the
// video is playing. This feature has to be disabled when tabbing
@@ -344,16 +424,24 @@ function () {
this.videoCaption.autoScrolling = true;
// Keeps track of where the focus is situated in the array of captions.
// Used to implement the automatic scrolling behavior and decide if the
- // outline around a caption has to be hidden or shown on a mouseenter or
- // mouseleave. Initially, no caption has the focus, set the index to -1.
+ // outline around a caption has to be hidden or shown on a mouseenter
+ // or mouseleave. Initially, no caption has the focus, set the
+ // index to -1.
this.videoCaption.currentCaptionIndex = -1;
// Used to track if the focus is coming from a click or tabbing. This
// has to be known to decide if, when a caption gets the focus, an
// outline has to be drawn (tabbing) or not (mouse click).
this.videoCaption.isMouseFocus = false;
- this.videoCaption.subtitlesEl.prepend($('- ').height(this.videoCaption.topSpacingHeight()));
- this.videoCaption.subtitlesEl.append($('
- ').height(this.videoCaption.bottomSpacingHeight()));
+ this.videoCaption.subtitlesEl
+ .prepend(
+ $('
- ')
+ .height(this.videoCaption.topSpacingHeight())
+ )
+ .append(
+ $('
- ')
+ .height(this.videoCaption.bottomSpacingHeight())
+ );
this.videoCaption.rendered = true;
}
@@ -403,7 +491,10 @@ function () {
caption.addClass('focused');
// The second and second to last elements turn automatic scrolling
// off again as it may have been enabled in captionBlur.
- if (captionIndex <= 1 || captionIndex >= this.videoCaption.captions.length-2) {
+ if (
+ captionIndex <= 1 ||
+ captionIndex >= this.videoCaption.captions.length - 2
+ ) {
this.videoCaption.autoScrolling = false;
}
}
@@ -413,13 +504,15 @@ function () {
var caption = $(event.target),
captionIndex = parseInt(caption.attr('data-index'), 10);
caption.removeClass('focused');
- // If we are on first or last index, we have to turn automatic scroll on
- // again when losing focus. There is no way to know in what direction we
- // are tabbing. So we could be on the first element and tabbing back out
- // of the captions or on the last element and tabbing forward out of the
- // captions.
+ // If we are on first or last index, we have to turn automatic scroll
+ // on again when losing focus. There is no way to know in what
+ // direction we are tabbing. So we could be on the first element and
+ // tabbing back out of the captions or on the last element and tabbing
+ // forward out of the captions.
if (captionIndex === 0 ||
captionIndex === this.videoCaption.captions.length-1) {
+ this.videoCaption.autoHideCaptions();
+
this.videoCaption.autoScrolling = true;
}
}
@@ -434,9 +527,13 @@ function () {
function scrollCaption() {
var el = this.videoCaption.subtitlesEl.find('.current:first');
- // Automatic scrolling gets disabled if one of the captions has received
- // focus through tabbing.
- if (!this.videoCaption.frozen && el.length && this.videoCaption.autoScrolling) {
+ // Automatic scrolling gets disabled if one of the captions has
+ // received focus through tabbing.
+ if (
+ !this.videoCaption.frozen &&
+ el.length &&
+ this.videoCaption.autoScrolling
+ ) {
this.videoCaption.subtitlesEl.scrollTo(
el,
{
@@ -565,11 +662,15 @@ function () {
}
function topSpacingHeight() {
- return this.videoCaption.calculateOffset(this.videoCaption.subtitlesEl.find('li:not(.spacing):first'));
+ return this.videoCaption.calculateOffset(
+ this.videoCaption.subtitlesEl.find('li:not(.spacing):first')
+ );
}
function bottomSpacingHeight() {
- return this.videoCaption.calculateOffset(this.videoCaption.subtitlesEl.find('li:not(.spacing):last'));
+ return this.videoCaption.calculateOffset(
+ this.videoCaption.subtitlesEl.find('li:not(.spacing):last')
+ );
}
function toggle(event) {
@@ -579,11 +680,27 @@ function () {
this.videoCaption.hideCaptions(false);
} else {
this.videoCaption.hideCaptions(true);
+
+ // In the case when captions are not auto-hidden based on mouse
+ // movement anywhere on the video, we must hide them explicitly
+ // after the "CC" button has been clicked (to hide captions).
+ //
+ // Otherwise, in order for the captions to disappear again, the
+ // user must move the mouse button over the "CC" button, or over
+ // the captions themselves. In this case, an "autoShow" will be
+ // triggered, and after a timeout, an "autoHide".
+ if (!this.config.autohideHtml5) {
+ this.captionHideTimeout = setTimeout(
+ this.videoCaption.autoHideCaptions(),
+ 0
+ );
+ }
}
}
function hideCaptions(hide_captions, update_cookie) {
- var type;
+ var hideSubtitlesEl = this.videoCaption.hideSubtitlesEl,
+ type;
if (typeof update_cookie === 'undefined') {
update_cookie = true;
@@ -592,14 +709,20 @@ function () {
if (hide_captions) {
type = 'hide_transcript';
this.captionsHidden = true;
- this.videoCaption.hideSubtitlesEl.attr('title', gettext('Turn on captions'));
- this.videoCaption.hideSubtitlesEl.text(gettext('Turn on captions'));
+
+ hideSubtitlesEl
+ .attr('title', gettext('Turn on captions'))
+ .text(gettext('Turn on captions'));
+
this.el.addClass('closed');
} else {
type = 'show_transcript';
this.captionsHidden = false;
- this.videoCaption.hideSubtitlesEl.attr('title', gettext('Turn off captions'));
- this.videoCaption.hideSubtitlesEl.text(gettext('Turn off captions'));
+
+ hideSubtitlesEl
+ .attr('title', gettext('Turn off captions'))
+ .text(gettext('Turn off captions'));
+
this.el.removeClass('closed');
this.videoCaption.scrollCaption();
}
@@ -621,27 +744,43 @@ function () {
}
function captionHeight() {
+ var paddingTop;
+
if (this.isFullScreen) {
- return $(window).height() - this.el.find('.video-controls').height() -
- 0.5 * this.videoControl.sliderEl.height() -
- 2 * parseInt(this.videoCaption.subtitlesEl.css('padding-top'), 10);
+ paddingTop = parseInt(
+ this.videoCaption.subtitlesEl.css('padding-top'), 10
+ );
+
+ return $(window).height() -
+ this.videoControl.el.height() -
+ 0.5 * this.videoControl.sliderEl.height() -
+ 2 * paddingTop;
} else {
- return this.el.find('.video-wrapper').height();
+ return this.elVideoWrapper.height();
}
}
function setSubtitlesHeight() {
var height = 0;
- if (this.videoType === 'html5'){
+ if (
+ ((this.videoType === 'html5') && (this.config.autohideHtml5)) ||
+ (!this.config.autohideHtml5)
+ ){
// on page load captionHidden = undefined
if (
- (this.captionsHidden === undefined && this.hide_captions === true ) ||
- (this.captionsHidden === true) ) {
- // In case of html5 autoshowing subtitles,
- // we ajdust height of subs, by height of scrollbar
- height = this.videoControl.el.height() + 0.5 * this.videoControl.sliderEl.height();
- // height of videoControl does not contain height of slider.
- // (css is set to absolute, to avoid yanking when slider autochanges its height)
+ (
+ this.captionsHidden === undefined &&
+ this.hide_captions === true
+ ) ||
+ (this.captionsHidden === true)
+ ) {
+ // In case of html5 autoshowing subtitles, we adjust height of
+ // subs, by height of scrollbar.
+ height = this.videoControl.el.height() +
+ 0.5 * this.videoControl.sliderEl.height();
+ // Height of videoControl does not contain height of slider.
+ // css is set to absolute, to avoid yanking when slider
+ // autochanges its height.
}
}
this.videoCaption.subtitlesEl.css({
diff --git a/lms/djangoapps/courseware/features/video.feature b/lms/djangoapps/courseware/features/video.feature
index 001481a6a5..7518bf6bdb 100644
--- a/lms/djangoapps/courseware/features/video.feature
+++ b/lms/djangoapps/courseware/features/video.feature
@@ -2,38 +2,45 @@
Feature: LMS.Video component
As a student, I want to view course videos in LMS.
+ # 1
Scenario: Video component is fully rendered in the LMS in HTML5 mode
Given the course has a Video component in HTML5 mode
Then when I view the video it has rendered in HTML5 mode
And all sources are correct
+ # 2
# Firefox doesn't have HTML5 (only mp4 - fix here)
@skip_firefox
Scenario: Autoplay is disabled in LMS for a Video component
Given the course has a Video component in HTML5 mode
Then when I view the video it does not have autoplay enabled
+ # 3
# Youtube testing
Scenario: Video component is fully rendered in the LMS in Youtube mode with HTML5 sources
Given youtube server is up and response time is 0.4 seconds
And the course has a Video component in Youtube_HTML5 mode
Then when I view the video it has rendered in Youtube mode
+ # 4
Scenario: Video component is not rendered in the LMS in Youtube mode with HTML5 sources
Given youtube server is up and response time is 2 seconds
And the course has a Video component in Youtube_HTML5 mode
Then when I view the video it has rendered in HTML5 mode
+ # 5
Scenario: Video component is rendered in the LMS in Youtube mode without HTML5 sources
Given youtube server is up and response time is 2 seconds
And the course has a Video component in Youtube mode
Then when I view the video it has rendered in Youtube mode
+ # 6
Scenario: Video component is rendered in the LMS in Youtube mode with HTML5 sources that doesn't supported by browser
Given youtube server is up and response time is 2 seconds
And the course has a Video component in Youtube_HTML5_Unsupported_Video mode
Then when I view the video it has rendered in Youtube mode
+ # 7
Scenario: Video component is rendered in the LMS in HTML5 mode with HTML5 sources that doesn't supported by browser
Given the course has a Video component in HTML5_Unsupported_Video mode
Then error message is shown
diff --git a/lms/templates/video.html b/lms/templates/video.html
index caf0aaa06f..d0e61a5f74 100644
--- a/lms/templates/video.html
+++ b/lms/templates/video.html
@@ -26,6 +26,19 @@
data-yt-test-timeout="${yt_test_timeout}"
data-yt-test-url="${yt_test_url}"
+ ## For now, the option "data-autohide-html5" is hard coded. This option
+ ## either enables or disables autohiding of controls and captions on mouse
+ ## inactivity. If set to true, controls and captions will autohide for
+ ## HTML5 sources (non-YouTube) after a period of mouse inactivity over the
+ ## whole video. When the mouse moves (or a key is pressed while any part of
+ ## the video player is focused), the captions and controls will be shown
+ ## once again.
+ ##
+ ## There is no option in the "Advanced Editor" to set this option. However,
+ ## this option will have an effect if changed to "True". The code on
+ ## front-end exists.
+ data-autohide-html5="False"
+
tabindex="-1"
>