Commit ec6388b8 by Anton Stupak

Merge pull request #3153 from edx/anton/caption_refactor

Refactor video caption module.
parents dd7bae43 2f572a86
...@@ -123,28 +123,16 @@ ...@@ -123,28 +123,16 @@
it('bind the hide caption button', function () { it('bind the hide caption button', function () {
state = jasmine.initializePlayer(); state = jasmine.initializePlayer();
expect($('.hide-subtitles')).toHandleWith( expect($('.hide-subtitles')).toHandle('click');
'click', state.videoCaption.toggle
);
}); });
it('bind the mouse movement', function () { it('bind the mouse movement', function () {
state = jasmine.initializePlayer(); state = jasmine.initializePlayer();
expect($('.subtitles')).toHandleWith( expect($('.subtitles')).toHandle('mouseover');
'mouseover', state.videoCaption.onMouseEnter expect($('.subtitles')).toHandle('mouseout');
); expect($('.subtitles')).toHandle('mousemove');
expect($('.subtitles')).toHandleWith( expect($('.subtitles')).toHandle('mousewheel');
'mouseout', state.videoCaption.onMouseLeave expect($('.subtitles')).toHandle('DOMMouseScroll');
);
expect($('.subtitles')).toHandleWith(
'mousemove', state.videoCaption.onMovement
);
expect($('.subtitles')).toHandleWith(
'mousewheel', state.videoCaption.onMovement
);
expect($('.subtitles')).toHandleWith(
'DOMMouseScroll', state.videoCaption.onMovement
);
}); });
it('bind the scroll', function () { it('bind the scroll', function () {
...@@ -859,7 +847,7 @@ ...@@ -859,7 +847,7 @@
runs(function () { runs(function () {
videoControl = state.videoControl; videoControl = state.videoControl;
$('.subtitles li[data-index=1]').addClass('current'); $('.subtitles li[data-index=1]').addClass('current');
state.videoCaption.resize(); state.videoCaption.onResize();
}); });
}); });
......
...@@ -26,7 +26,6 @@ function (VideoPlayer) { ...@@ -26,7 +26,6 @@ function (VideoPlayer) {
describe('always', function () { describe('always', function () {
beforeEach(function () { beforeEach(function () {
state = jasmine.initializePlayer(); state = jasmine.initializePlayer();
state.videoEl = $('video, iframe'); state.videoEl = $('video, iframe');
}); });
...@@ -211,7 +210,7 @@ function (VideoPlayer) { ...@@ -211,7 +210,7 @@ function (VideoPlayer) {
state.videoEl = $('video, iframe'); state.videoEl = $('video, iframe');
spyOn(state.videoControl, 'pause').andCallThrough(); spyOn(state.videoControl, 'pause').andCallThrough();
spyOn(state.videoCaption, 'pause').andCallThrough(); spyOn($.fn, 'trigger').andCallThrough();
state.videoPlayer.onStateChange({ state.videoPlayer.onStateChange({
data: YT.PlayerState.PAUSED data: YT.PlayerState.PAUSED
...@@ -223,7 +222,7 @@ function (VideoPlayer) { ...@@ -223,7 +222,7 @@ function (VideoPlayer) {
}); });
it('pause the video caption', function () { it('pause the video caption', function () {
expect(state.videoCaption.pause).toHaveBeenCalled(); expect($.fn.trigger).toHaveBeenCalledWith('pause', {});
}); });
}); });
...@@ -245,7 +244,7 @@ function (VideoPlayer) { ...@@ -245,7 +244,7 @@ function (VideoPlayer) {
spyOn(state.videoPlayer, 'log').andCallThrough(); spyOn(state.videoPlayer, 'log').andCallThrough();
spyOn(window, 'setInterval').andReturn(100); spyOn(window, 'setInterval').andReturn(100);
spyOn(state.videoControl, 'play'); spyOn(state.videoControl, 'play');
spyOn(state.videoCaption, 'play'); spyOn($.fn, 'trigger').andCallThrough();
state.videoPlayer.onStateChange({ state.videoPlayer.onStateChange({
data: YT.PlayerState.PLAYING data: YT.PlayerState.PLAYING
...@@ -281,7 +280,7 @@ function (VideoPlayer) { ...@@ -281,7 +280,7 @@ function (VideoPlayer) {
}); });
it('play the video caption', function () { it('play the video caption', function () {
expect(state.videoCaption.play).toHaveBeenCalled(); expect($.fn.trigger).toHaveBeenCalledWith('play', {});
}); });
}); });
...@@ -295,7 +294,7 @@ function (VideoPlayer) { ...@@ -295,7 +294,7 @@ function (VideoPlayer) {
spyOn(state.videoPlayer, 'log').andCallThrough(); spyOn(state.videoPlayer, 'log').andCallThrough();
spyOn(state.videoControl, 'pause').andCallThrough(); spyOn(state.videoControl, 'pause').andCallThrough();
spyOn(state.videoCaption, 'pause').andCallThrough(); spyOn($.fn, 'trigger').andCallThrough();
state.videoPlayer.onStateChange({ state.videoPlayer.onStateChange({
data: YT.PlayerState.PLAYING data: YT.PlayerState.PLAYING
...@@ -323,7 +322,7 @@ function (VideoPlayer) { ...@@ -323,7 +322,7 @@ function (VideoPlayer) {
}); });
it('pause the video caption', function () { it('pause the video caption', function () {
expect(state.videoCaption.pause).toHaveBeenCalled(); expect($.fn.trigger).toHaveBeenCalledWith('pause', {});
}); });
}); });
...@@ -334,7 +333,7 @@ function (VideoPlayer) { ...@@ -334,7 +333,7 @@ function (VideoPlayer) {
state.videoEl = $('video, iframe'); state.videoEl = $('video, iframe');
spyOn(state.videoControl, 'pause').andCallThrough(); spyOn(state.videoControl, 'pause').andCallThrough();
spyOn(state.videoCaption, 'pause').andCallThrough(); spyOn($.fn, 'trigger').andCallThrough();
state.videoPlayer.onStateChange({ state.videoPlayer.onStateChange({
data: YT.PlayerState.ENDED data: YT.PlayerState.ENDED
...@@ -346,7 +345,7 @@ function (VideoPlayer) { ...@@ -346,7 +345,7 @@ function (VideoPlayer) {
}); });
it('pause the video caption', function () { it('pause the video caption', function () {
expect(state.videoCaption.pause).toHaveBeenCalled(); expect($.fn.trigger).toHaveBeenCalledWith('ended', {});
}); });
}); });
}); });
...@@ -709,6 +708,7 @@ function (VideoPlayer) { ...@@ -709,6 +708,7 @@ function (VideoPlayer) {
describe('updatePlayTime with invalid endTime', function () { describe('updatePlayTime with invalid endTime', function () {
beforeEach(function () { beforeEach(function () {
state = { state = {
el: $('#video_id'),
videoPlayer: { videoPlayer: {
duration: function () { duration: function () {
// The video will be 60 seconds long. // The video will be 60 seconds long.
...@@ -756,10 +756,7 @@ function (VideoPlayer) { ...@@ -756,10 +756,7 @@ function (VideoPlayer) {
describe('when the video player is not full screen', function () { describe('when the video player is not full screen', function () {
beforeEach(function () { beforeEach(function () {
state = jasmine.initializePlayer(); state = jasmine.initializePlayer();
state.videoEl = $('video, iframe'); state.videoEl = $('video, iframe');
spyOn(state.videoCaption, 'resize').andCallThrough();
spyOn($.fn, 'trigger').andCallThrough(); spyOn($.fn, 'trigger').andCallThrough();
state.videoControl.toggleFullScreen(jQuery.Event('click')); state.videoControl.toggleFullScreen(jQuery.Event('click'));
}); });
...@@ -774,7 +771,7 @@ function (VideoPlayer) { ...@@ -774,7 +771,7 @@ function (VideoPlayer) {
}); });
it('tell VideoCaption to resize', function () { it('tell VideoCaption to resize', function () {
expect(state.videoCaption.resize).toHaveBeenCalled(); expect($.fn.trigger).toHaveBeenCalledWith('fullscreen', [true]);
expect(state.resizer.setMode).toHaveBeenCalledWith('both'); expect(state.resizer.setMode).toHaveBeenCalledWith('both');
expect(state.resizer.delta.substract).toHaveBeenCalled(); expect(state.resizer.delta.substract).toHaveBeenCalled();
}); });
...@@ -783,11 +780,8 @@ function (VideoPlayer) { ...@@ -783,11 +780,8 @@ function (VideoPlayer) {
describe('when the video player already full screen', function () { describe('when the video player already full screen', function () {
beforeEach(function () { beforeEach(function () {
state = jasmine.initializePlayer(); state = jasmine.initializePlayer();
state.videoEl = $('video, iframe'); state.videoEl = $('video, iframe');
spyOn($.fn, 'trigger').andCallThrough();
spyOn(state.videoCaption, 'resize').andCallThrough();
state.el.addClass('video-fullscreen'); state.el.addClass('video-fullscreen');
state.videoControl.fullScreenState = true; state.videoControl.fullScreenState = true;
state.videoControl.isFullScreen = true; state.videoControl.isFullScreen = true;
...@@ -806,7 +800,7 @@ function (VideoPlayer) { ...@@ -806,7 +800,7 @@ function (VideoPlayer) {
}); });
it('tell VideoCaption to resize', function () { it('tell VideoCaption to resize', function () {
expect(state.videoCaption.resize).toHaveBeenCalled(); expect($.fn.trigger).toHaveBeenCalledWith('fullscreen', [false]);
expect(state.resizer.setMode) expect(state.resizer.setMode)
.toHaveBeenCalledWith('width'); .toHaveBeenCalledWith('width');
expect(state.resizer.delta.reset).toHaveBeenCalled(); expect(state.resizer.delta.reset).toHaveBeenCalled();
......
...@@ -223,20 +223,20 @@ function (HTML5Video, Resizer) { ...@@ -223,20 +223,20 @@ function (HTML5Video, Resizer) {
container: state.container container: state.container
}) })
.callbacks.once(function() { .callbacks.once(function() {
state.trigger('videoCaption.resize', null); state.el.trigger('caption:resize');
}) })
.setMode('width'); .setMode('width');
// Update captions size when controls becomes visible on iPad or Android // Update captions size when controls becomes visible on iPad or Android
if (/iPad|Android/i.test(state.isTouch[0])) { if (/iPad|Android/i.test(state.isTouch[0])) {
state.el.on('controls:show', function () { state.el.on('controls:show', function () {
state.trigger('videoCaption.resize', null); state.el.trigger('caption:resize');
}); });
} }
$(window).on('resize', _.debounce(function () { $(window).on('resize', _.debounce(function () {
state.trigger('videoControl.updateControlsHeight', null); state.trigger('videoControl.updateControlsHeight', null);
state.trigger('videoCaption.resize', null); state.el.trigger('caption:resize');
state.resizer.align(); state.resizer.align();
}, 100)); }, 100));
} }
...@@ -271,7 +271,7 @@ function (HTML5Video, Resizer) { ...@@ -271,7 +271,7 @@ function (HTML5Video, Resizer) {
}); });
_updateVcrAndRegion(state, true); _updateVcrAndRegion(state, true);
state.trigger('videoCaption.fetchCaption', null); state.el.trigger('caption:fetch');
state.resizer.setElement(state.el.find('iframe')).align(); state.resizer.setElement(state.el.find('iframe')).align();
} }
...@@ -447,10 +447,6 @@ function (HTML5Video, Resizer) { ...@@ -447,10 +447,6 @@ function (HTML5Video, Resizer) {
end: true end: true
}); });
if (this.config.showCaptions) {
this.trigger('videoCaption.pause', null);
}
if (this.videoPlayer.skipOnEndedStartEndReset) { if (this.videoPlayer.skipOnEndedStartEndReset) {
this.videoPlayer.skipOnEndedStartEndReset = undefined; this.videoPlayer.skipOnEndedStartEndReset = undefined;
} }
...@@ -475,11 +471,6 @@ function (HTML5Video, Resizer) { ...@@ -475,11 +471,6 @@ function (HTML5Video, Resizer) {
delete this.videoPlayer.updateInterval; delete this.videoPlayer.updateInterval;
this.trigger('videoControl.pause', null); this.trigger('videoControl.pause', null);
if (this.config.showCaptions) {
this.trigger('videoCaption.pause', null);
}
this.saveState(true); this.saveState(true);
this.el.trigger('pause', arguments); this.el.trigger('pause', arguments);
} }
...@@ -501,17 +492,10 @@ function (HTML5Video, Resizer) { ...@@ -501,17 +492,10 @@ function (HTML5Video, Resizer) {
} }
this.trigger('videoControl.play', null); this.trigger('videoControl.play', null);
this.trigger('videoProgressSlider.notifyThroughHandleEnd', { this.trigger('videoProgressSlider.notifyThroughHandleEnd', {
end: false end: false
}); });
if (this.config.showCaptions) {
this.trigger('videoCaption.play', null);
}
this.videoPlayer.ready(); this.videoPlayer.ready();
this.el.trigger('play', arguments); this.el.trigger('play', arguments);
} }
...@@ -803,7 +787,7 @@ function (HTML5Video, Resizer) { ...@@ -803,7 +787,7 @@ function (HTML5Video, Resizer) {
} }
); );
this.trigger('videoCaption.updatePlayTime', time); this.el.trigger('caption:update', [time]);
} }
function isEnded() { function isEnded() {
......
...@@ -277,7 +277,6 @@ function () { ...@@ -277,7 +277,6 @@ function () {
.attr('title', text) .attr('title', text)
.text(text); .text(text);
this.trigger('videoCaption.resize', null);
this.el.trigger('fullscreen', [this.isFullScreen]); this.el.trigger('fullscreen', [this.isFullScreen]);
} }
......
...@@ -5,734 +5,858 @@ define( ...@@ -5,734 +5,858 @@ define(
'video/09_video_caption.js', 'video/09_video_caption.js',
['video/00_sjson.js', 'video/00_async_process.js'], ['video/00_sjson.js', 'video/00_async_process.js'],
function (Sjson, AsyncProcess) { function (Sjson, AsyncProcess) {
/** /**
* @desc VideoCaption module exports a function. * @desc VideoCaption module exports a function.
* *
* @type {function} * @type {function}
* @access public * @access public
* *
* @param {object} state - The object containg the state of the video * @param {object} state - The object containing the state of the video
* player. All other modules, their parameters, public variables, etc. * player. All other modules, their parameters, public variables, etc.
* are available via this object. * are available via this object.
* *
* @this {object} The global window object. * @this {object} The global window object.
* *
* @returns {undefined} * @returns {jquery Promise}
*/ */
return function (state) { var VideoCaption = function (state) {
state.videoCaption = {}; if (!(this instanceof VideoCaption)) {
_makeFunctionsPublic(state); return new VideoCaption(state);
state.videoCaption.renderElements(); }
this.state = state;
this.state.videoCaption = this;
this.renderElements();
return $.Deferred().resolve().promise(); return $.Deferred().resolve().promise();
}; };
// *************************************************************** VideoCaption.prototype = {
// Private functions start here. /**
// *************************************************************** * @desc Initiate rendering of elements, and set their initial configuration.
*
// function _makeFunctionsPublic(state) */
// renderElements: function () {
// Functions which will be accessible via 'state' object. When called, var state = this.state,
// these functions will get the 'state' object as a context. languages = this.state.config.transcriptLanguages;
function _makeFunctionsPublic(state) {
var methodsDict = { this.loaded = false;
addPaddings: addPaddings, this.subtitlesEl = state.el.find('ol.subtitles');
bindHandlers: bindHandlers, this.container = state.el.find('.lang');
bottomSpacingHeight: bottomSpacingHeight, this.hideSubtitlesEl = state.el.find('a.hide-subtitles');
calculateOffset: calculateOffset,
captionBlur: captionBlur, if (_.keys(languages).length) {
captionClick: captionClick, this.renderLanguageMenu(languages);
captionFocus: captionFocus,
captionHeight: captionHeight, if (!this.fetchCaption()) {
captionKeyDown: captionKeyDown, this.hideCaptions(true);
captionMouseDown: captionMouseDown, this.hideSubtitlesEl.hide();
captionMouseOverOut: captionMouseOverOut,
fetchCaption: fetchCaption,
fetchAvailableTranslations: fetchAvailableTranslations,
hideCaptions: hideCaptions,
onMouseEnter: onMouseEnter,
onMouseLeave: onMouseLeave,
onMovement: onMovement,
pause: pause,
play: play,
renderCaption: renderCaption,
renderElements: renderElements,
renderLanguageMenu: renderLanguageMenu,
resize: resize,
scrollCaption: scrollCaption,
seekPlayer: seekPlayer,
setSubtitlesHeight: setSubtitlesHeight,
toggle: toggle,
topSpacingHeight: topSpacingHeight,
updatePlayTime: updatePlayTime
};
state.bindTo(methodsDict, state.videoCaption, 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().
// ***************************************************************
/**
* @desc Create any necessary DOM elements, attach them, and set their
* initial configuration. Also make the created DOM elements available
* via the 'state' object. Much easier to work this way - you don't
* have to do repeated jQuery element selects.
*
* @type {function}
* @access public
*
* @this {object} - The object containg the state of the video
* player. All other modules, their parameters, public variables, etc.
* are available via this object.
*
* @returns {boolean}
* true: The function fethched captions successfully, and compltely
* rendered everything related to captions.
* false: The captions were not fetched. Nothing will be rendered,
* and the CC button will be hidden.
*/
function renderElements() {
var Caption = this.videoCaption,
languages = this.config.transcriptLanguages;
Caption.loaded = false;
Caption.subtitlesEl = this.el.find('ol.subtitles');
Caption.container = this.el.find('.lang');
Caption.hideSubtitlesEl = this.el.find('a.hide-subtitles');
if (_.keys(languages).length) {
Caption.renderLanguageMenu(languages);
if (!Caption.fetchCaption()) {
Caption.hideCaptions(true);
Caption.hideSubtitlesEl.hide();
}
} else {
Caption.hideCaptions(true, false);
Caption.hideSubtitlesEl.hide();
}
}
// function bindHandlers()
//
// Bind any necessary function callbacks to DOM events (click,
// mousemove, etc.).
function bindHandlers() {
var self = this,
Caption = this.videoCaption,
events = [
'mouseover', 'mouseout', 'mousedown', 'click', 'focus', 'blur',
'keydown'
].join(' ');
Caption.hideSubtitlesEl.on({
'click': Caption.toggle
});
Caption.subtitlesEl
.on({
mouseenter: Caption.onMouseEnter,
mouseleave: Caption.onMouseLeave,
mousemove: Caption.onMovement,
mousewheel: Caption.onMovement,
DOMMouseScroll: Caption.onMovement
})
.on(events, 'li[data-index]', function (event) {
switch (event.type) {
case 'mouseover':
case 'mouseout':
Caption.captionMouseOverOut(event);
break;
case 'mousedown':
Caption.captionMouseDown(event);
break;
case 'click':
Caption.captionClick(event);
break;
case 'focusin':
Caption.captionFocus(event);
break;
case 'focusout':
Caption.captionBlur(event);
break;
case 'keydown':
Caption.captionKeyDown(event);
break;
} }
}); } else {
this.hideCaptions(true, false);
if (Caption.showLanguageMenu) { this.hideSubtitlesEl.hide();
Caption.container.on({ }
mouseenter: onContainerMouseEnter, },
mouseleave: onContainerMouseLeave
}); /**
} * @desc Bind any necessary function callbacks to DOM events (click,
* mousemove, etc.).
if ((this.videoType === 'html5') && (this.config.autohideHtml5)) { *
Caption.subtitlesEl.on('scroll', this.videoControl.showControls); */
} bindHandlers: function () {
} var self = this,
state = this.state,
function onContainerMouseEnter(event) { events = [
event.preventDefault(); 'mouseover', 'mouseout', 'mousedown', 'click', 'focus', 'blur',
'keydown'
$(event.currentTarget).addClass('open'); ].join(' ');
}
// Change context to VideoCaption of event handlers using `bind`.
function onContainerMouseLeave(event) { this.hideSubtitlesEl.on('click', this.toggle.bind(this));
event.preventDefault(); this.subtitlesEl
.on({
$(event.currentTarget).removeClass('open'); mouseenter: this.onMouseEnter.bind(this),
} mouseleave: this.onMouseLeave.bind(this),
mousemove: this.onMovement.bind(this),
mousewheel: this.onMovement.bind(this),
DOMMouseScroll: this.onMovement.bind(this)
})
.on(events, 'li[data-index]', function (event) {
switch (event.type) {
case 'mouseover':
case 'mouseout':
self.captionMouseOverOut(event);
break;
case 'mousedown':
self.captionMouseDown(event);
break;
case 'click':
self.captionClick(event);
break;
case 'focusin':
self.captionFocus(event);
break;
case 'focusout':
self.captionBlur(event);
break;
case 'keydown':
self.captionKeyDown(event);
break;
}
});
function onMouseEnter() { if (this.showLanguageMenu) {
if (this.videoCaption.frozen) { this.container.on({
clearTimeout(this.videoCaption.frozen); mouseenter: this.onContainerMouseEnter,
} mouseleave: this.onContainerMouseLeave
});
}
this.videoCaption.frozen = setTimeout( state.el
this.videoCaption.onMouseLeave, .on({
this.config.captionsFreezeTime 'caption:fetch': this.fetchCaption.bind(this),
); 'caption:resize': this.onResize.bind(this),
} 'caption:update': function (event, time) {
self.updatePlayTime(time);
},
'ended': this.pause,
'fullscreen': this.onResize.bind(this),
'pause': this.pause,
'play': this.play,
});
if ((state.videoType === 'html5') && (state.config.autohideHtml5)) {
this.subtitlesEl.on('scroll', state.videoControl.showControls);
}
},
/**
* @desc Opens language menu.
*
* @param {jquery Event} event
*/
onContainerMouseEnter: function (event) {
event.preventDefault();
$(event.currentTarget).addClass('open');
},
/**
* @desc Closes language menu.
*
* @param {jquery Event} event
*/
onContainerMouseLeave: function (event) {
event.preventDefault();
$(event.currentTarget).removeClass('open');
},
/**
* @desc Freezes moving of captions when mouse is over them.
*
* @param {jquery Event} event
*/
onMouseEnter: function (event) {
if (this.frozen) {
clearTimeout(this.frozen);
}
function onMouseLeave() { this.frozen = setTimeout(
if (this.videoCaption.frozen) { this.onMouseLeave,
clearTimeout(this.videoCaption.frozen); this.state.config.captionsFreezeTime
} );
},
/**
* @desc Unfreezes moving of captions when mouse go out.
*
* @param {jquery Event} event
*/
onMouseLeave: function (event) {
if (this.frozen) {
clearTimeout(this.frozen);
}
this.videoCaption.frozen = null; this.frozen = null;
if (this.videoCaption.playing) { if (this.playing) {
this.videoCaption.scrollCaption(); this.scrollCaption();
} }
} },
/**
* @desc Freezes moving of captions when mouse is moving over them.
*
* @param {jquery Event} event
*/
onMovement: function (event) {
this.onMouseEnter();
},
/**
* @desc Fetch the caption file specified by the user. Upon successful
* receipt of the file, the captions will be rendered.
*
* @returns {boolean}
* true: The user specified a caption file. NOTE: if an error happens
* while the specified file is being retrieved (for example the
* file is missing on the server), this function will still return
* true.
* false: No caption file was specified, or an empty string was
* specified for the Youtube type player.
*/
fetchCaption: function () {
var self = this,
state = this.state,
language = state.getCurrentLanguage(),
data, youtubeId;
if (this.loaded) {
this.hideCaptions(false);
} else {
this.hideCaptions(state.hide_captions, false);
}
function onMovement() { if (this.fetchXHR && this.fetchXHR.abort) {
this.videoCaption.onMouseEnter(); this.fetchXHR.abort();
} }
/** if (state.videoType === 'youtube') {
* @desc Fetch the caption file specified by the user. Upn successful youtubeId = state.youtubeId('1.0');
* receival of the file, the captions will be rendered.
*
* @type {function}
* @access public
*
* @this {object} - The object containg the state of the video
* player. All other modules, their parameters, public variables, etc.
* are available via this object.
*
* @returns {boolean}
* true: The user specified a caption file. NOTE: if an error happens
* while the specified file is being retrieved (for example the
* file is missing on the server), this function will still return
* true.
* false: No caption file was specified, or an empty string was
* specified.
*/
function fetchCaption() {
var self = this,
Caption = self.videoCaption,
language = this.getCurrentLanguage(),
data;
if (Caption.loaded) {
Caption.hideCaptions(false);
} else {
Caption.hideCaptions(this.hide_captions, false);
}
if (Caption.fetchXHR && Caption.fetchXHR.abort) { if (!youtubeId) {
Caption.fetchXHR.abort(); return false;
} }
if (this.videoType === 'youtube') { data = {
data = { videoId: youtubeId
videoId: this.youtubeId('1.0') };
}; }
}
// Fetch the captions file. If no file was specified, or if an error // Fetch the captions file. If no file was specified, or if an error
// occurred, then we hide the captions panel, and the "CC" button // occurred, then we hide the captions panel, and the "CC" button
Caption.fetchXHR = $.ajaxWithPrefix({ this.fetchXHR = $.ajaxWithPrefix({
url: self.config.transcriptTranslationUrl + '/' + language, url: state.config.transcriptTranslationUrl + '/' + language,
notifyOnError: false, notifyOnError: false,
data: data, data: data,
success: function (response) { success: function (sjson) {
Caption.sjson = new Sjson(response); self.sjson = new Sjson(sjson);
var start = Caption.sjson.getStartTimes(), var start = self.sjson.getStartTimes(),
captions = Caption.sjson.getCaptions(); captions = self.sjson.getCaptions();
if (Caption.loaded) { if (self.loaded) {
if (Caption.rendered) { if (self.rendered) {
Caption.renderCaption(start, captions); self.renderCaption(start, captions);
Caption.updatePlayTime(self.videoPlayer.currentTime); self.updatePlayTime(state.videoPlayer.currentTime);
} }
} else {
if (self.isTouch) {
Caption.subtitlesEl.find('li').html(
gettext(
'Caption will be displayed when ' +
'you start playing the video.'
)
);
} else { } else {
Caption.renderCaption(start, captions); if (state.isTouch) {
self.subtitlesEl.find('li').html(
gettext(
'Caption will be displayed when ' +
'you start playing the video.'
)
);
} else {
self.renderCaption(start, captions);
}
self.bindHandlers();
} }
Caption.bindHandlers(); self.loaded = true;
},
error: function (jqXHR, textStatus, errorThrown) {
console.log('[Video info]: ERROR while fetching captions.');
console.log(
'[Video info]: STATUS:', textStatus +
', MESSAGE:', '' + errorThrown
);
// If initial list of languages has more than 1 item, check
// for availability other transcripts.
if (_.keys(state.config.transcriptLanguages).length > 1) {
self.fetchAvailableTranslations();
} else {
self.hideCaptions(true, false);
self.hideSubtitlesEl.hide();
}
} }
});
Caption.loaded = true; return true;
}, },
error: function (jqXHR, textStatus, errorThrown) {
console.log('[Video info]: ERROR while fetching captions.'); /**
console.log( * @desc Fetch the list of available translations. Upon successful receipt,
'[Video info]: STATUS:', textStatus + * the list of available translations will be updated.
', MESSAGE:', '' + errorThrown *
); * @returns {jquery Promise}
// If initial list of languages has more than 1 item, check */
// for availability other transcripts. fetchAvailableTranslations: function () {
if (_.keys(self.config.transcriptLanguages).length > 1) { var self = this,
Caption.fetchAvailableTranslations(); state = this.state;
} else {
Caption.hideCaptions(true, false); return $.ajaxWithPrefix({
Caption.hideSubtitlesEl.hide(); url: state.config.transcriptAvailableTranslationsUrl,
} notifyOnError: false,
} success: function (response) {
}); var currentLanguages = state.config.transcriptLanguages,
newLanguages = _.pick(currentLanguages, response);
return true;
} // Update property with available currently translations.
state.config.transcriptLanguages = newLanguages;
function fetchAvailableTranslations() { // Remove an old language menu.
var self = this, self.container.find('.langs-list').remove();
Caption = this.videoCaption;
if (_.keys(newLanguages).length) {
return $.ajaxWithPrefix({ // And try again to fetch transcript.
url: self.config.transcriptAvailableTranslationsUrl, self.fetchCaption();
notifyOnError: false, self.renderLanguageMenu(newLanguages);
success: function (response) { }
var currentLanguages = self.config.transcriptLanguages, },
newLanguages = _.pick(currentLanguages, response); error: function (jqXHR, textStatus, errorThrown) {
self.hideCaptions(true, false);
// Update property with available currently translations. self.hideSubtitlesEl.hide();
self.config.transcriptLanguages = newLanguages;
// Remove an old language menu.
Caption.container.find('.langs-list').remove();
if (_.keys(newLanguages).length) {
// And try again to fetch transcript.
Caption.fetchCaption();
Caption.renderLanguageMenu(newLanguages);
} }
}, });
error: function (jqXHR, textStatus, errorThrown) { },
Caption.hideCaptions(true, false);
Caption.hideSubtitlesEl.hide(); /**
* @desc Recalculates and updates the height of the container of captions.
*
*/
onResize: function () {
this.subtitlesEl
.find('.spacing').first()
.height(this.topSpacingHeight()).end()
.find('.spacing').last()
.height(this.bottomSpacingHeight());
this.scrollCaption();
this.setSubtitlesHeight();
},
/**
* @desc Create any necessary DOM elements, attach them, and set their
* initial configuration for the Language menu.
*
* @param {object} languages Dictionary where key is language code,
* value - language label
*
*/
renderLanguageMenu: function (languages) {
var self = this,
state = this.state,
menu = $('<ol class="langs-list menu">'),
currentLang = state.getCurrentLanguage();
if (_.keys(languages).length < 2) {
return false;
} }
});
}
function resize() {
this.videoCaption.subtitlesEl
.find('.spacing:first')
.height(this.videoCaption.topSpacingHeight())
.find('.spacing:last')
.height(this.videoCaption.bottomSpacingHeight());
this.videoCaption.scrollCaption();
this.videoCaption.setSubtitlesHeight();
}
function renderLanguageMenu(languages) {
var self = this,
menu = $('<ol class="langs-list menu">'),
currentLang = this.getCurrentLanguage();
if (_.keys(languages).length < 2) {
return false;
}
this.videoCaption.showLanguageMenu = true; this.showLanguageMenu = true;
$.each(languages, function(code, label) { $.each(languages, function(code, label) {
var li = $('<li data-lang-code="' + code + '" />'), var li = $('<li data-lang-code="' + code + '" />'),
link = $('<a href="javascript:void(0);">' + label + '</a>'); link = $('<a href="javascript:void(0);">' + label + '</a>');
if (currentLang === code) { if (currentLang === code) {
li.addClass('active'); li.addClass('active');
} }
li.append(link); li.append(link);
menu.append(li); menu.append(li);
}); });
this.videoCaption.container.append(menu); this.container.append(menu);
menu.on('click', 'a', function (e) { menu.on('click', 'a', function (e) {
var el = $(e.currentTarget).parent(), var el = $(e.currentTarget).parent(),
Caption = self.videoCaption, state = self.state,
langCode = el.data('lang-code'); langCode = el.data('lang-code');
if (self.lang !== langCode) { if (state.lang !== langCode) {
self.lang = langCode; state.lang = langCode;
self.storage.setItem('language', langCode); state.storage.setItem('language', langCode);
el .addClass('active') el .addClass('active')
.siblings('li') .siblings('li')
.removeClass('active'); .removeClass('active');
Caption.fetchCaption(); self.fetchCaption();
} }
}); });
} },
function buildCaptions (container, start, captions) { /**
var process = function(text, index) { * @desc Create any necessary DOM elements, attach them, and set their
var liEl = $('<li>', { * initial configuration.
'data-index': index, *
'data-start': start[index], * @param {jQuery element} container Element in which captions will be
'tabindex': 0 * inserted.
}).html(text); * @param {array} start List of start times for the video.
* @param {array} captions List of captions for the video.
return liEl[0]; * @returns {object} jQuery's Promise object
*
*/
buildCaptions: function (container, start, captions) {
var process = function(text, index) {
var liEl = $('<li>', {
'data-index': index,
'data-start': start[index],
'tabindex': 0
}).html(text);
return liEl[0];
};
return AsyncProcess.array(captions, process).done(function (list) {
container.append(list);
});
},
/**
* @desc Initiates creating of captions and set their initial configuration.
*
* @param {array} start List of start times for the video.
* @param {array} captions List of captions for the video.
*
*/
renderCaption: function (start, captions) {
var self = this;
var onRender = function () {
self.addPaddings();
// 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 (through forward tabbing) or the last caption (through
// backwards tabbing) gets the focus, disable that feature.
// Re-enable it if tabbing then cycles out of the the captions.
self.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.
self.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).
self.isMouseFocus = false;
self.rendered = true;
}; };
return AsyncProcess.array(captions, process).done(function (list) {
container.append(list); this.rendered = false;
}); this.subtitlesEl.empty();
} this.setSubtitlesHeight();
this.buildCaptions(this.subtitlesEl, start, captions).done(onRender);
function renderCaption(start, captions) { },
var Caption = this.videoCaption;
/**
var onRender = function () { * @desc Sets top and bottom spacing height and make sure they are taken
Caption.addPaddings(); * out of the tabbing order.
// 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 addPaddings: function () {
// this flag enabled as we assume mouse use. Then, if the first
// caption (through forward tabbing) or the last caption (through this.subtitlesEl
// backwards tabbing) gets the focus, disable that feature. .prepend(
// Re-enable it if tabbing then cycles out of the the captions. $('<li class="spacing">')
Caption.autoScrolling = true; .height(this.topSpacingHeight())
// Keeps track of where the focus is situated in the array of .attr('tabindex', -1)
// captions. Used to implement the automatic scrolling behavior and )
// decide if the outline around a caption has to be hidden or shown .append(
// on a mouseenter or mouseleave. Initially, no caption has the $('<li class="spacing">')
// focus, set the index to -1. .height(this.bottomSpacingHeight())
Caption.currentCaptionIndex = -1; .attr('tabindex', -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).
Caption.isMouseFocus = false; /**
Caption.rendered = true; * @desc
}; * On mouseOver: Hides the outline of a caption that has been tabbed to.
* On mouseOut: Shows the outline of a caption that has been tabbed to.
*
Caption.rendered = false; * @param {jquery Event} event
Caption.subtitlesEl.empty(); *
Caption.setSubtitlesHeight(); */
buildCaptions(Caption.subtitlesEl, start, captions).done(onRender); captionMouseOverOut: function (event) {
} var caption = $(event.target),
captionIndex = parseInt(caption.attr('data-index'), 10);
function addPaddings() {
// Set top and bottom spacing height and make sure they are taken out of if (captionIndex === this.currentCaptionIndex) {
// the tabbing order. if (event.type === 'mouseover') {
this.videoCaption.subtitlesEl caption.removeClass('focused');
.prepend( }
$('<li class="spacing">') else { // mouseout
.height(this.videoCaption.topSpacingHeight()) caption.addClass('focused');
.attr('tabindex', -1) }
)
.append(
$('<li class="spacing">')
.height(this.videoCaption.bottomSpacingHeight())
.attr('tabindex', -1)
);
}
// On mouseOver, hide the outline of a caption that has been tabbed to.
// 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);
if (captionIndex === this.videoCaption.currentCaptionIndex) {
if (event.type === 'mouseover') {
caption.removeClass('focused');
}
else { // mouseout
caption.addClass('focused');
} }
} },
}
/**
function captionMouseDown(event) { * @desc Handles mousedown event on concrete caption.
var caption = $(event.target); *
this.videoCaption.isMouseFocus = true; * @param {jquery Event} event
this.videoCaption.autoScrolling = true; *
caption.removeClass('focused'); */
this.videoCaption.currentCaptionIndex = -1; captionMouseDown: function (event) {
} var caption = $(event.target);
function captionClick(event) { this.isMouseFocus = true;
this.videoCaption.seekPlayer(event); this.autoScrolling = true;
}
function captionFocus(event) {
var caption = $(event.target),
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.
if (this.videoCaption.isMouseFocus) {
this.videoCaption.autoScrolling = true;
caption.removeClass('focused'); caption.removeClass('focused');
this.videoCaption.currentCaptionIndex = -1; this.currentCaptionIndex = -1;
} },
// If the focus comes from tabbing, show the outline and turn off
// automatic scrolling. /**
else { * @desc Handles click event on concrete caption.
this.videoCaption.currentCaptionIndex = captionIndex; *
caption.addClass('focused'); * @param {jquery Event} event
// The second and second to last elements turn automatic scrolling *
// off again as it may have been enabled in captionBlur. */
if ( captionClick: function (event) {
captionIndex <= 1 || this.seekPlayer(event);
captionIndex >= this.videoCaption.sjson.getSize() - 2 },
) {
this.videoCaption.autoScrolling = false; /**
* @desc Handles focus event on concrete caption.
*
* @param {jquery Event} event
*
*/
captionFocus: function (event) {
var caption = $(event.target),
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.
if (this.isMouseFocus) {
this.autoScrolling = true;
caption.removeClass('focused');
this.currentCaptionIndex = -1;
} }
} // If the focus comes from tabbing, show the outline and turn off
} // automatic scrolling.
else {
function captionBlur(event) { this.currentCaptionIndex = captionIndex;
var caption = $(event.target), caption.addClass('focused');
captionIndex = parseInt(caption.attr('data-index'), 10); // The second and second to last elements turn automatic scrolling
// off again as it may have been enabled in captionBlur.
caption.removeClass('focused'); if (
// If we are on first or last index, we have to turn automatic scroll captionIndex <= 1 ||
// on again when losing focus. There is no way to know in what captionIndex >= this.sjson.getSize() - 2
// 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 this.autoScrolling = false;
// forward out of the captions.
if (captionIndex === 0 ||
captionIndex === this.videoCaption.sjson.getSize() - 1) {
this.videoCaption.autoScrolling = true;
}
}
function captionKeyDown(event) {
this.videoCaption.isMouseFocus = false;
if (event.which === 13) { //Enter key
this.videoCaption.seekPlayer(event);
}
}
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
) {
this.videoCaption.subtitlesEl.scrollTo(
el,
{
offset: -this.videoCaption.calculateOffset(el)
} }
);
}
}
function play() {
if (this.videoCaption.loaded) {
if (!this.videoCaption.rendered) {
var start = this.videoCaption.sjson.getStartTimes(),
captions = this.videoCaption.sjson.getCaptions();
this.videoCaption.renderCaption(start, captions);
} }
},
/**
* @desc Handles blur event on concrete caption.
*
* @param {jquery Event} event
*
*/
captionBlur: function (event) {
var caption = $(event.target),
captionIndex = parseInt(caption.attr('data-index'), 10);
this.videoCaption.playing = true; 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
function pause() { // tabbing back out of the captions or on the last element and tabbing
if (this.videoCaption.loaded) { // forward out of the captions.
this.videoCaption.playing = false; if (captionIndex === 0 ||
} captionIndex === this.sjson.getSize() - 1) {
}
this.autoScrolling = true;
function updatePlayTime(time) {
var newIndex;
if (this.videoCaption.loaded) {
if (this.isFlashMode()) {
time = Time.convert(time, this.speed, '1.0');
} }
},
/**
* @desc Handles keydown event on concrete caption.
*
* @param {jquery Event} event
*
*/
captionKeyDown: function (event) {
this.isMouseFocus = false;
if (event.which === 13) { //Enter key
this.seekPlayer(event);
}
},
time = Math.round(time * 1000 + 100); /**
newIndex = this.videoCaption.sjson.search(time); * @desc Scrolls caption container to make active caption visible.
*
*/
scrollCaption: function () {
var el = this.subtitlesEl.find('.current:first');
// Automatic scrolling gets disabled if one of the captions has
// received focus through tabbing.
if ( if (
typeof newIndex !== 'undefined' && !this.frozen &&
newIndex !== -1 && el.length &&
this.videoCaption.currentIndex !== newIndex this.autoScrolling
) { ) {
if (typeof this.videoCaption.currentIndex !== 'undefined') { this.subtitlesEl.scrollTo(
this.videoCaption.subtitlesEl el,
.find('li.current') {
.removeClass('current'); offset: -1 * this.calculateOffset(el)
}
);
}
},
/**
* @desc Updates flags on play
*
*/
play: function () {
if (this.loaded) {
if (!this.rendered) {
var start = this.sjson.getStartTimes(),
captions = this.sjson.getCaptions();
this.renderCaption(start, captions);
} }
this.videoCaption.subtitlesEl this.playing = true;
.find("li[data-index='" + newIndex + "']")
.addClass('current');
this.videoCaption.currentIndex = newIndex;
this.videoCaption.scrollCaption();
} }
} },
}
/**
function seekPlayer(event) { * @desc Updates flags on pause
var time = parseInt($(event.target).data('start'), 10); *
*/
if (this.isFlashMode()) { pause: function () {
time = Math.round(Time.convert(time, '1.0', this.speed)); if (this.loaded) {
} this.playing = false;
this.trigger(
'videoPlayer.onCaptionSeek',
{
'type': 'onCaptionSeek',
'time': time/1000
} }
); },
event.preventDefault(); /**
} * @desc Updates captions UI on paying.
*
function calculateOffset(element) { * @param {number} time Time in seconds.
return this.videoCaption.captionHeight() / 2 - element.height() / 2; *
} */
updatePlayTime: function (time) {
function topSpacingHeight() { var state = this.state,
return this.videoCaption.calculateOffset( newIndex;
this.videoCaption.subtitlesEl.find('li:not(.spacing):first')
); if (this.loaded) {
} if (state.isFlashMode()) {
time = Time.convert(time, state.speed, '1.0');
function bottomSpacingHeight() { }
return this.videoCaption.calculateOffset(
this.videoCaption.subtitlesEl.find('li:not(.spacing):last')
);
}
function toggle(event) {
event.preventDefault();
if (this.el.hasClass('closed')) {
this.videoCaption.hideCaptions(false);
} else {
this.videoCaption.hideCaptions(true);
}
}
function hideCaptions(hide_captions, update_cookie) { time = Math.round(time * 1000 + 100);
var hideSubtitlesEl = this.videoCaption.hideSubtitlesEl, newIndex = this.sjson.search(time);
type, text;
if (
typeof newIndex !== 'undefined' &&
newIndex !== -1 &&
this.currentIndex !== newIndex
) {
if (typeof this.currentIndex !== 'undefined') {
this.subtitlesEl
.find('li.current')
.removeClass('current');
}
if (typeof update_cookie === 'undefined') { this.subtitlesEl
update_cookie = true; .find("li[data-index='" + newIndex + "']")
} .addClass('current');
if (hide_captions) { this.currentIndex = newIndex;
type = 'hide_transcript'; this.scrollCaption();
this.captionsHidden = true; }
}
},
/**
* @desc Sends log to the server on caption seek.
*
* @param {jquery Event} event
*
*/
seekPlayer: function (event) {
var state = this.state,
time = parseInt($(event.target).data('start'), 10);
if (state.isFlashMode()) {
time = Math.round(Time.convert(time, '1.0', state.speed));
}
this.el.addClass('closed'); state.trigger(
'videoPlayer.onCaptionSeek',
{
'type': 'onCaptionSeek',
'time': time/1000
}
);
text = gettext('Turn on captions'); event.preventDefault();
} else { },
type = 'show_transcript';
this.captionsHidden = false; /**
* @desc Calculates offset for paddings.
*
* @param {jquery element} element Top or bottom padding element.
* @returns {number} Offset for the passed padding element.
*
*/
calculateOffset: function (element) {
return this.captionHeight() / 2 - element.height() / 2;
},
/**
* @desc Calculates offset for the top padding element.
*
* @returns {number} Offset for the passed top padding element.
*
*/
topSpacingHeight: function () {
return this.calculateOffset(
this.subtitlesEl.find('li:not(.spacing)').first()
);
},
/**
* @desc Calculates offset for the bottom padding element.
*
* @returns {number} Offset for the passed bottom padding element.
*
*/
bottomSpacingHeight: function () {
return this.calculateOffset(
this.subtitlesEl.find('li:not(.spacing)').last()
);
},
/**
* @desc Shows/Hides captions on click `CC` button
*
* @param {jquery Event} event
*
*/
toggle: function (event) {
event.preventDefault();
if (this.state.el.hasClass('closed')) {
this.hideCaptions(false);
} else {
this.hideCaptions(true);
}
},
/**
* @desc Shows/Hides captions and updates the cookie.
*
* @param {boolean} hide_captions if `true` hides the caption,
* otherwise - show.
* @param {boolean} update_cookie Flag to update or not the cookie.
*
*/
hideCaptions: function (hide_captions, update_cookie) {
var hideSubtitlesEl = this.hideSubtitlesEl,
state = this.state,
type, text;
if (typeof update_cookie === 'undefined') {
update_cookie = true;
}
this.el.removeClass('closed'); if (hide_captions) {
this.videoCaption.scrollCaption(); type = 'hide_transcript';
state.captionsHidden = true;
state.el.addClass('closed');
text = gettext('Turn on captions');
} else {
type = 'show_transcript';
state.captionsHidden = false;
state.el.removeClass('closed');
this.scrollCaption();
text = gettext('Turn off captions');
}
text = gettext('Turn off captions'); hideSubtitlesEl
} .attr('title', text)
.text(gettext(text));
hideSubtitlesEl if (state.videoPlayer) {
.attr('title', text) state.videoPlayer.log(type, {
.text(gettext(text)); currentTime: state.videoPlayer.currentTime
});
}
if (this.videoPlayer) { if (state.resizer) {
this.videoPlayer.log(type, { if (state.isFullScreen) {
currentTime: this.videoPlayer.currentTime state.resizer.setMode('both');
}); } else {
} state.resizer.alignByWidthOnly();
}
}
if (this.resizer) { this.setSubtitlesHeight();
if (this.isFullScreen) { if (update_cookie) {
this.resizer.setMode('both'); $.cookie('hide_captions', hide_captions, {
expires: 3650,
path: '/'
});
}
},
/**
* @desc Return the caption container height.
*
* @returns {number} event Height of the container in pixels.
*
*/
captionHeight: function () {
var state = this.state;
if (state.isFullScreen) {
return state.container.height() - state.videoControl.height;
} else { } else {
this.resizer.alignByWidthOnly(); return state.container.height();
}
},
/**
* @desc Sets the height of the caption container element.
*
*/
setSubtitlesHeight: function () {
var height = 0,
state = this.state;
// on page load captionHidden = undefined
if ((state.captionsHidden === undefined && state.hide_captions) ||
state.captionsHidden === true
) {
// In case of html5 autoshowing subtitles, we adjust height of
// subs, by height of scrollbar.
height = state.videoControl.el.height() +
0.5 * state.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.setSubtitlesHeight();
if (update_cookie) { this.subtitlesEl.css({
$.cookie('hide_captions', hide_captions, { maxHeight: this.captionHeight() - height
expires: 3650,
path: '/'
}); });
} }
} };
function captionHeight() {
if (this.isFullScreen) {
return this.container.height() - this.videoControl.height;
} else {
return this.container.height();
}
}
function setSubtitlesHeight() {
var height = 0;
// on page load captionHidden = undefined
if ((this.captionsHidden === undefined && this.hide_captions) ||
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({ return VideoCaption;
maxHeight: this.videoCaption.captionHeight() - height
});
}
}); });
}(RequireJS.define)); }(RequireJS.define));
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