Commit 7fca5e8b by brianhw

Merge pull request #1360 from edx/valera/carry_over_of_add_option_transcripts_remove_onhover_2

Valera/carry over of add option transcripts remove onhover 2
parents 5c883993 4b371d47
......@@ -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,6 +36,35 @@ 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
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"
......@@ -5,6 +5,11 @@ from terrain.steps import reload_the_page
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):
......@@ -17,8 +22,13 @@ def i_created_a_video_component(step):
@step('I have created a Video component with subtitles$')
def i_created_a_video_component_subtitles(step):
step.given('I have created a Video component')
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')
# Store the current URL so we can return here
video_url = world.browser.url
......@@ -108,3 +118,37 @@ def the_youtube_video_is_shown(_step):
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')
......@@ -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;
......@@ -610,6 +610,8 @@ div.video {
ol.subtitles {
width: 0;
height: 0;
visibility: hidden;
}
ol.subtitles.html5 {
......@@ -643,6 +645,8 @@ div.video {
ol.subtitles {
right: -(flex-grid(4));
width: auto;
visibility: hidden;
}
}
......
......@@ -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"
>
<div class="focus_grabber first"></div>
......
......@@ -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"
>
<div class="focus_grabber first"></div>
......
......@@ -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"
>
<div class="focus_grabber first"></div>
......
......@@ -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"
>
<div class="focus_grabber first"></div>
......
......@@ -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"
>
<div class="focus_grabber first"></div>
......@@ -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"
>
<div class="tc-wrapper">
<article class="video-wrapper">
......@@ -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"
>
<div class="tc-wrapper">
<article class="video-wrapper">
......
(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;
}
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);
});
});
(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;
}
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);
});
});
});
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();
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();
});
});
});
});
});
});
}).call(this);
......@@ -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;
}
......
......@@ -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);
}
......
......@@ -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.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.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);
}
// ***************************************************************
// 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 = $('<ol>'),
_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,38 +401,47 @@ 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
// through them as it interferes with that action. Initially, have this
// flag enabled as we assume mouse use. Then, if the first caption
// flag enabled as we assume mouse use. Then, if the first caption
// (through forward tabbing) or the last caption (through backwards
// tabbing) gets the focus, disable that feature. Renable it if tabbing
// then cycles out of the the captions.
// then cycles out of the the captions.
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
// 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($('<li class="spacing">').height(this.videoCaption.topSpacingHeight()));
this.videoCaption.subtitlesEl.append($('<li class="spacing">').height(this.videoCaption.bottomSpacingHeight()));
this.videoCaption.subtitlesEl
.prepend(
$('<li class="spacing">')
.height(this.videoCaption.topSpacingHeight())
)
.append(
$('<li class="spacing">')
.height(this.videoCaption.bottomSpacingHeight())
);
this.videoCaption.rendered = true;
}
......@@ -362,7 +450,7 @@ function () {
// On mouseOut, show the outline of a caption that has been tabbed to.
function captionMouseOverOut(event) {
var caption = $(event.target),
captionIndex = parseInt(caption.attr('data-index'), 10);
captionIndex = parseInt(caption.attr('data-index'), 10);
if (captionIndex === this.videoCaption.currentCaptionIndex) {
if (event.type === 'mouseover') {
caption.removeClass('focused');
......@@ -370,7 +458,7 @@ function () {
else { // mouseout
caption.addClass('focused');
}
}
}
}
function captionMouseDown(event) {
......@@ -390,36 +478,41 @@ function () {
captionIndex = parseInt(caption.attr('data-index'), 10);
// If the focus comes from a mouse click, hide the outline, turn on
// automatic scrolling and set currentCaptionIndex to point outside of
// caption list (ie -1) to disable mouseenter, mouseleave behavior.
// caption list (ie -1) to disable mouseenter, mouseleave behavior.
if (this.videoCaption.isMouseFocus) {
this.videoCaption.autoScrolling = true;
caption.removeClass('focused');
this.videoCaption.currentCaptionIndex = -1;
}
// If the focus comes from tabbing, show the outline and turn off
// If the focus comes from tabbing, show the outline and turn off
// automatic scrolling.
else {
this.videoCaption.currentCaptionIndex = captionIndex;
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) {
// off again as it may have been enabled in captionBlur.
if (
captionIndex <= 1 ||
captionIndex >= this.videoCaption.captions.length - 2
) {
this.videoCaption.autoScrolling = false;
}
}
}
function captionBlur(event) {
var caption = $(event.target),
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 (captionIndex === 0 ||
// 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,23 +680,45 @@ 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) {
var type;
function hideCaptions(hide_captions, update_cookie) {
var hideSubtitlesEl = this.videoCaption.hideSubtitlesEl,
type;
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();
}
......@@ -615,27 +738,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({
......
......@@ -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
......
......@@ -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"
>
<div class="focus_grabber first"></div>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment