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($('
  1. ').height(this.videoCaption.topSpacingHeight())); - this.videoCaption.subtitlesEl.append($('
  2. ').height(this.videoCaption.bottomSpacingHeight())); + this.videoCaption.subtitlesEl + .prepend( + $('
  3. ') + .height(this.videoCaption.topSpacingHeight()) + ) + .append( + $('
  4. ') + .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" >