Start and end times function the same for YouTube and HTML5 modes.

Fix bug when the user clicks on the video time-line either
before or after the specified end time, it jumps
to the very beginning of the video.

BLD-392
This commit is contained in:
Valera Rozuvan
2013-10-24 12:20:25 +03:00
parent 2628e4f182
commit 5cd4bae35d
14 changed files with 1403 additions and 1070 deletions

View File

@@ -96,7 +96,10 @@
});
});
it('parse the videos if subtitles do not exist', function () {
it(
'parse the videos if subtitles do not exist',
function ()
{
var sub = '';
$('#example').find('.video').data('sub', '');
@@ -117,16 +120,41 @@
ogg: null
}, v = document.createElement('video');
if (!!(v.canPlayType && v.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/no/, ''))) {
html5Sources['webm'] = 'xmodule/include/fixtures/test.webm';
if (
!!(
v.canPlayType &&
v.canPlayType(
'video/webm; codecs="vp8, vorbis"'
).replace(/no/, '')
)
) {
html5Sources['webm'] =
'xmodule/include/fixtures/test.webm';
}
if (!!(v.canPlayType && v.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"').replace(/no/, ''))) {
html5Sources['mp4'] = 'xmodule/include/fixtures/test.mp4';
if (
!!(
v.canPlayType &&
v.canPlayType(
'video/mp4; codecs="avc1.42E01E, ' +
'mp4a.40.2"'
).replace(/no/, '')
)
) {
html5Sources['mp4'] =
'xmodule/include/fixtures/test.mp4';
}
if (!!(v.canPlayType && v.canPlayType('video/ogg; codecs="theora"').replace(/no/, ''))) {
html5Sources['ogg'] = 'xmodule/include/fixtures/test.ogv';
if (
!!(
v.canPlayType &&
v.canPlayType(
'video/ogg; codecs="theora"'
).replace(/no/, '')
)
) {
html5Sources['ogg'] =
'xmodule/include/fixtures/test.ogv';
}
expect(state.html5Sources).toEqual(html5Sources);
@@ -143,10 +171,10 @@
});
});
// Note that the loading of stand alone HTML5 player API is handled by
// Require JS. When state.videoPlayer is created, the stand alone HTML5
// player object is already loaded, so no further testing in that case
// is required.
// Note that the loading of stand alone HTML5 player API is
// handled by Require JS. When state.videoPlayer is created,
// the stand alone HTML5 player object is already loaded, so no
// further testing in that case is required.
describe('HTML5 API is available', function () {
beforeEach(function () {
state = new Video('#example');
@@ -172,8 +200,10 @@
describe('with speed', function () {
it('return the video id for given speed', function () {
expect(state.youtubeId('0.75')).toEqual(this['7tqY6eQzVhE']);
expect(state.youtubeId('1.0')).toEqual(this['cogebirgzzM']);
expect(state.youtubeId('0.75'))
.toEqual(this['7tqY6eQzVhE']);
expect(state.youtubeId('1.0'))
.toEqual(this['cogebirgzzM']);
});
});
@@ -210,7 +240,7 @@
itDescription: 'start time is greater than end time',
data: {start: 42, end: 24},
expectData: {start: 42, end: null}
},
}
];
beforeEach(function () {
@@ -234,8 +264,8 @@
state = new Video('#example');
expect(state.config.start).toBe(expectData.start);
expect(state.config.end).toBe(expectData.end);
expect(state.config.startTime).toBe(expectData.start);
expect(state.config.endTime).toBe(expectData.end);
});
}
});
@@ -263,7 +293,10 @@
state3 = new Video('#example3');
});
it('check for YT availability is performed only once', function () {
it(
'check for YT availability is performed only once',
function ()
{
var numAjaxCalls = 0;
// Total ajax calls made.
@@ -307,10 +340,14 @@
});
it('save setting for new speed', function () {
expect($.cookie).toHaveBeenCalledWith('video_speed', '0.75', {
expires: 3650,
path: '/'
});
expect($.cookie).toHaveBeenCalledWith(
'video_speed',
'0.75',
{
expires: 3650,
path: '/'
}
);
});
});
@@ -341,10 +378,14 @@
});
it('save setting for new speed', function () {
expect($.cookie).toHaveBeenCalledWith('video_speed', '0.75', {
expires: 3650,
path: '/'
});
expect($.cookie).toHaveBeenCalledWith(
'video_speed',
'0.75',
{
expires: 3650,
path: '/'
}
);
});
});

View File

@@ -10,7 +10,8 @@
beforeEach(function () {
oldOTBD = window.onTouchBasedDevice;
window.onTouchBasedDevice = jasmine.createSpy('onTouchBasedDevice').andReturn(false);
window.onTouchBasedDevice = jasmine
.createSpy('onTouchBasedDevice').andReturn(false);
initialize();
player.config.events.onReady = jasmine.createSpy('onReady');
});
@@ -46,17 +47,22 @@
}, 'Player state should be changed', WAIT_TIMEOUT);
runs(function () {
expect(player.getPlayerState()).toBe(STATUS.PLAYING);
expect(player.getPlayerState())
.toBe(STATUS.PLAYING);
});
});
it('callback was called', function () {
waitsFor(function () {
return state.videoPlayer.player.getPlayerState() !== STATUS.PAUSED;
var stateStatus = state.videoPlayer.player
.getPlayerState();
return stateStatus !== STATUS.PAUSED;
}, 'Player state should be changed', WAIT_TIMEOUT);
runs(function () {
expect(player.callStateChangeCallback).toHaveBeenCalled();
expect(player.callStateChangeCallback)
.toHaveBeenCalled();
});
});
});
@@ -78,7 +84,8 @@
}, 'Player state should be changed', WAIT_TIMEOUT);
runs(function () {
expect(player.getPlayerState()).toBe(STATUS.PAUSED);
expect(player.getPlayerState())
.toBe(STATUS.PAUSED);
});
});
@@ -88,7 +95,8 @@
}, 'Player state should be changed', WAIT_TIMEOUT);
runs(function () {
expect(player.callStateChangeCallback).toHaveBeenCalled();
expect(player.callStateChangeCallback)
.toHaveBeenCalled();
});
});
});
@@ -121,7 +129,8 @@
}, 'Player state should be changed', WAIT_TIMEOUT);
runs(function () {
expect(player.callStateChangeCallback).toHaveBeenCalled();
expect(player.callStateChangeCallback)
.toHaveBeenCalled();
});
});
});
@@ -156,39 +165,26 @@
return player.getPlayerState() !== STATUS.PLAYING;
}, 'Player state should be changed', WAIT_TIMEOUT);
runs(function () {
expect(player.callStateChangeCallback).toHaveBeenCalled();
expect(player.callStateChangeCallback)
.toHaveBeenCalled();
});
});
});
describe('[canplay]', function () {
beforeEach(function () {
it(
'player state was changed, start/end was defined, ' +
'onReady called', function ()
{
waitsFor(function () {
return player.getPlayerState() !== STATUS.UNSTARTED;
}, 'Video cannot be played', WAIT_TIMEOUT);
});
it('player state was changed', function () {
runs(function () {
expect(player.getPlayerState()).toBe(STATUS.PAUSED);
});
});
it('end property was defined', function () {
runs(function () {
expect(player.end).not.toBeNull();
});
});
it('start position was defined', function () {
runs(function () {
expect(player.video.currentTime).toBe(player.start);
});
});
it('onReady callback was called', function () {
runs(function () {
expect(player.config.events.onReady).toHaveBeenCalled();
expect(player.video.currentTime).toBe(0);
expect(player.config.events.onReady)
.toHaveBeenCalled();
});
});
});
@@ -276,7 +272,8 @@
it('getCurrentTime', function () {
runs(function () {
player.video.currentTime = 3;
expect(player.getCurrentTime()).toBe(player.video.currentTime);
expect(player.getCurrentTime())
.toBe(player.video.currentTime);
});
});
@@ -330,7 +327,8 @@
});
it('getAvailablePlaybackRates', function () {
expect(player.getAvailablePlaybackRates()).toEqual(playbackRates);
expect(player.getAvailablePlaybackRates())
.toEqual(playbackRates);
});
});
});

View File

@@ -6,13 +6,19 @@ function (Resizer) {
describe('Resizer', function () {
var html = [
'<div class="rszr-wrapper" style="width:200px; height: 200px;">',
'<div class="rszr-el" style="width:100px; height: 150px;">',
'<div ' +
'class="rszr-wrapper" ' +
'style="width:200px; height: 200px;"' +
'>',
'<div ' +
'class="rszr-el" ' +
'style="width:100px; height: 150px;"' +
'>',
'Content',
'</div>',
'</div>'
].join(''),
config, container, element;
config, container, element, originalConsoleLog;
beforeEach(function () {
setFixtures(html);
@@ -23,12 +29,17 @@ function (Resizer) {
container: container,
element: element
};
originalConsoleLog = window.console.log;
spyOn(console, 'log');
});
afterEach(function () {
window.console.log = originalConsoleLog;
});
it('When Initialize without required parameters, log message is shown',
function () {
spyOn(console, 'log');
new Resizer({ });
expect(console.log).toHaveBeenCalled();
}

View File

@@ -22,7 +22,12 @@
afterEach(function () {
YT.Player = undefined;
$('.subtitles').remove();
// `source` tags should be removed to avoid memory leak bug that we
// had before. Removing of `source` tag, not `video` tag, stops
// loading video source and clears the memory.
$('source').remove();
window.onTouchBasedDevice = oldOTBD;
});
@@ -442,7 +447,8 @@
expect(videoCaption.currentIndex).toEqual(5);
});
it('scroll caption to new position', function () {
// Disabled 10/25/13 due to flakiness in master
xit('scroll caption to new position', function () {
expect($.fn.scrollTo).toHaveBeenCalled();
});
});
@@ -640,6 +646,8 @@
beforeEach(function () {
state.el.addClass('closed');
videoCaption.toggle(jQuery.Event('click'));
jasmine.Clock.useMock();
});
it('log the show_transcript event', function () {
@@ -655,8 +663,19 @@
expect(state.el).not.toHaveClass('closed');
});
it('scroll the caption', function () {
expect($.fn.scrollTo).toHaveBeenCalled();
// Test turned off due to flakiness (30.10.2013).
xit('scroll the caption', function () {
// After transcripts are shown, and the video plays for a
// bit.
jasmine.Clock.tick(1000);
// The transcripts should have advanced by at least one
// position. When they advance, the list scrolls. The
// current transcript position should be constantly
// visible.
runs(function () {
expect($.fn.scrollTo).toHaveBeenCalled();
});
});
});
});

View File

@@ -1,717 +1,770 @@
(function() {
describe('VideoPlayer', function() {
var state, videoPlayer, player, videoControl, videoCaption, videoProgressSlider, videoSpeedControl, videoVolumeControl, oldOTBD;
(function () {
describe('VideoPlayer', function () {
var state, videoPlayer, player, videoControl, videoCaption,
videoProgressSlider, videoSpeedControl, videoVolumeControl,
oldOTBD;
function initialize(fixture) {
if (typeof fixture === 'undefined') {
loadFixtures('video_all.html');
} else {
loadFixtures(fixture);
}
function initialize(fixture) {
if (typeof fixture === 'undefined') {
loadFixtures('video_all.html');
} else {
loadFixtures(fixture);
}
state = new Video('#example');
state = new Video('#example');
state.videoEl = $('video, iframe');
videoPlayer = state.videoPlayer;
player = videoPlayer.player;
videoControl = state.videoControl;
videoCaption = state.videoCaption;
videoProgressSlider = state.videoProgressSlider;
videoSpeedControl = state.videoSpeedControl;
videoVolumeControl = state.videoVolumeControl;
state.videoEl = $('video, iframe');
videoPlayer = state.videoPlayer;
player = videoPlayer.player;
videoControl = state.videoControl;
videoCaption = state.videoCaption;
videoProgressSlider = state.videoProgressSlider;
videoSpeedControl = state.videoSpeedControl;
videoVolumeControl = state.videoVolumeControl;
state.resizer = (function () {
var methods = [
'align', 'alignByWidthOnly', 'alignByHeightOnly', 'setParams', 'setMode'
],
obj = {};
state.resizer = (function () {
var methods = [
'align',
'alignByWidthOnly',
'alignByHeightOnly',
'setParams',
'setMode'
],
obj = {};
$.each(methods, function(index, method) {
obj[method] = jasmine.createSpy(method).andReturn(obj);
$.each(methods, function (index, method) {
obj[method] = jasmine.createSpy(method).andReturn(obj);
});
return obj;
}());
}
function initializeYouTube() {
initialize('video.html');
}
beforeEach(function () {
oldOTBD = window.onTouchBasedDevice;
window.onTouchBasedDevice = jasmine.createSpy('onTouchBasedDevice')
.andReturn(false);
});
return obj;
})();
}
afterEach(function () {
$('source').remove();
window.onTouchBasedDevice = oldOTBD;
});
function initializeYouTube() {
initialize('video.html');
}
describe('constructor', function () {
describe('always', function () {
beforeEach(function () {
initialize();
});
beforeEach(function () {
oldOTBD = window.onTouchBasedDevice;
window.onTouchBasedDevice = jasmine.createSpy('onTouchBasedDevice').andReturn(false);
it('instanticate current time to zero', function () {
expect(videoPlayer.currentTime).toEqual(0);
});
it('set the element', function () {
expect(state.el).toHaveId('video_id');
});
it('create video control', function () {
expect(videoControl).toBeDefined();
expect(videoControl.el).toHaveClass('video-controls');
});
it('create video caption', function () {
expect(videoCaption).toBeDefined();
expect(state.youtubeId()).toEqual('Z5KLxerq05Y');
expect(state.speed).toEqual('1.0');
expect(state.config.caption_asset_path)
.toEqual('/static/subs/');
});
it('create video speed control', function () {
expect(videoSpeedControl).toBeDefined();
expect(videoSpeedControl.el).toHaveClass('speeds');
expect(videoSpeedControl.speeds)
.toEqual([ '0.75', '1.0', '1.25', '1.50' ]);
expect(state.speed).toEqual('1.0');
});
it('create video progress slider', function () {
expect(videoProgressSlider).toBeDefined();
expect(videoProgressSlider.el).toHaveClass('slider');
});
// All the toHandleWith() expect tests are not necessary for
// this version of Video. jQuery event system is not used to
// trigger and invoke methods. This is an artifact from
// previous version of Video.
});
it('create Youtube player', function () {
var oldYT = window.YT, events;
jasmine.stubRequests();
window.YT = {
Player: function () { },
PlayerState: oldYT.PlayerState,
ready: function (f) {
f();
}
};
spyOn(window.YT, 'Player');
initializeYouTube();
events = {
onReady: videoPlayer.onReady,
onStateChange: videoPlayer.onStateChange,
onPlaybackQualityChange: videoPlayer
.onPlaybackQualityChange
};
expect(YT.Player).toHaveBeenCalledWith('id', {
playerVars: {
controls: 0,
wmode: 'transparent',
rel: 0,
showinfo: 0,
enablejsapi: 1,
modestbranding: 1,
html5: 1
},
videoId: 'cogebirgzzM',
events: events
});
window.YT = oldYT;
});
// We can't test the invocation of HTML5Video because it is not
// available globally. It is defined within the scope of Require
// JS.
describe('when not on a touch based device', function () {
beforeEach(function () {
window.onTouchBasedDevice.andReturn(true);
initialize();
});
it('create video volume control', function () {
expect(videoVolumeControl).toBeDefined();
expect(videoVolumeControl.el).toHaveClass('volume');
});
});
describe('when on a touch based device', function () {
var oldOTBD;
beforeEach(function () {
initialize();
});
it('controls are in paused state', function () {
expect(videoControl.isPlaying).toBe(false);
});
});
});
describe('onReady', function () {
beforeEach(function () {
initialize();
spyOn(videoPlayer, 'log').andCallThrough();
spyOn(videoPlayer, 'play').andCallThrough();
videoPlayer.onReady();
});
it('log the load_video event', function () {
expect(videoPlayer.log).toHaveBeenCalledWith('load_video');
});
it('autoplay the first video', function () {
expect(videoPlayer.play).not.toHaveBeenCalled();
});
});
describe('onStateChange', function () {
describe('when the video is unstarted', function () {
beforeEach(function () {
initialize();
spyOn(videoControl, 'pause').andCallThrough();
spyOn(videoCaption, 'pause').andCallThrough();
videoPlayer.onStateChange({
data: YT.PlayerState.PAUSED
});
});
it('pause the video control', function () {
expect(videoControl.pause).toHaveBeenCalled();
});
it('pause the video caption', function () {
expect(videoCaption.pause).toHaveBeenCalled();
});
});
describe('when the video is playing', function () {
var oldState;
beforeEach(function () {
// Create the first instance of the player.
initialize();
oldState = state;
spyOn(oldState.videoPlayer, 'onPause').andCallThrough();
// Now initialize a second instance.
initialize();
spyOn(videoPlayer, 'log').andCallThrough();
spyOn(window, 'setInterval').andReturn(100);
spyOn(videoControl, 'play');
spyOn(videoCaption, 'play');
videoPlayer.onStateChange({
data: YT.PlayerState.PLAYING
});
});
it('log the play_video event', function () {
expect(videoPlayer.log).toHaveBeenCalledWith(
'play_video', { currentTime: 0 }
);
});
it('pause other video player', function () {
expect(oldState.videoPlayer.onPause).toHaveBeenCalled();
});
it('set update interval', function () {
expect(window.setInterval).toHaveBeenCalledWith(
videoPlayer.update, 200
);
expect(videoPlayer.updateInterval).toEqual(100);
});
it('play the video control', function () {
expect(videoControl.play).toHaveBeenCalled();
});
it('play the video caption', function () {
expect(videoCaption.play).toHaveBeenCalled();
});
});
describe('when the video is paused', function () {
var currentUpdateIntrval;
beforeEach(function () {
initialize();
spyOn(videoPlayer, 'log').andCallThrough();
spyOn(videoControl, 'pause').andCallThrough();
spyOn(videoCaption, 'pause').andCallThrough();
videoPlayer.onStateChange({
data: YT.PlayerState.PLAYING
});
currentUpdateIntrval = videoPlayer.updateInterval;
videoPlayer.onStateChange({
data: YT.PlayerState.PAUSED
});
});
it('log the pause_video event', function () {
expect(videoPlayer.log).toHaveBeenCalledWith(
'pause_video', { currentTime: 0 }
);
});
it('clear update interval', function () {
expect(videoPlayer.updateInterval).toBeUndefined();
});
it('pause the video control', function () {
expect(videoControl.pause).toHaveBeenCalled();
});
it('pause the video caption', function () {
expect(videoCaption.pause).toHaveBeenCalled();
});
});
describe('when the video is ended', function () {
beforeEach(function () {
initialize();
spyOn(videoControl, 'pause').andCallThrough();
spyOn(videoCaption, 'pause').andCallThrough();
videoPlayer.onStateChange({
data: YT.PlayerState.ENDED
});
});
it('pause the video control', function () {
expect(videoControl.pause).toHaveBeenCalled();
});
it('pause the video caption', function () {
expect(videoCaption.pause).toHaveBeenCalled();
});
});
});
describe('onSeek', function () {
beforeEach(function () {
initialize();
spyOn(videoPlayer, 'updatePlayTime');
spyOn(videoPlayer, 'log');
spyOn(videoPlayer.player, 'seekTo');
state.videoPlayer.play();
waitsFor(function () {
var duration = videoPlayer.duration(),
currentTime = videoPlayer.currentTime;
return (
isFinite(currentTime) &&
currentTime > 0 &&
isFinite(duration) &&
duration > 0
);
}, 'video begins playing', 10000);
});
it('Slider event causes log update', function () {
runs(function () {
var currentTime = videoPlayer.currentTime;
videoProgressSlider.onSlide(
jQuery.Event('slide'), { value: 2 }
);
expect(videoPlayer.log).toHaveBeenCalledWith(
'seek_video',
{
old_time: currentTime,
new_time: 2,
type: 'onSlideSeek'
}
);
});
});
it('seek the player', function () {
runs(function () {
videoProgressSlider.onSlide(
jQuery.Event('slide'), { value: 60 }
);
expect(videoPlayer.player.seekTo)
.toHaveBeenCalledWith(60, true);
});
});
it('call updatePlayTime on player', function () {
runs(function () {
videoProgressSlider.onSlide(
jQuery.Event('slide'), { value: 60 }
);
expect(videoPlayer.updatePlayTime)
.toHaveBeenCalledWith(60);
});
});
// Disabled 10/25/13 due to flakiness in master
xit(
'when the player is not playing: set the current time',
function ()
{
runs(function () {
videoProgressSlider.onSlide(
jQuery.Event('slide'), { value: 20 }
);
videoPlayer.pause();
videoProgressSlider.onSlide(
jQuery.Event('slide'), { value: 10 }
);
waitsFor(function () {
return Math.round(videoPlayer.currentTime) === 10;
}, 'currentTime got updated', 10000);
});
});
});
describe('onSpeedChange', function () {
beforeEach(function () {
initialize();
videoPlayer.currentTime = 60;
spyOn(videoPlayer, 'updatePlayTime').andCallThrough();
spyOn(state, 'setSpeed').andCallThrough();
spyOn(videoPlayer, 'log').andCallThrough();
spyOn(videoPlayer.player, 'setPlaybackRate').andCallThrough();
});
describe('always', function () {
beforeEach(function () {
videoPlayer.onSpeedChange('0.75', false);
});
it('check if speed_change_video is logged', function () {
expect(videoPlayer.log).toHaveBeenCalledWith(
'speed_change_video',
{
current_time: videoPlayer.currentTime,
old_speed: '1.0',
new_speed: '0.75'
}
);
});
it('convert the current time to the new speed', function () {
expect(videoPlayer.currentTime).toEqual(60);
});
it('set video speed to the new speed', function () {
expect(state.setSpeed).toHaveBeenCalledWith('0.75', false);
});
});
describe('when the video is playing', function () {
beforeEach(function () {
videoPlayer.play();
videoPlayer.onSpeedChange('0.75', false);
});
it('trigger updatePlayTime event', function () {
expect(videoPlayer.player.setPlaybackRate)
.toHaveBeenCalledWith('0.75');
});
});
describe('when the video is not playing', function () {
beforeEach(function () {
videoPlayer.pause();
videoPlayer.onSpeedChange('0.75', false);
});
it('trigger updatePlayTime event', function () {
expect(videoPlayer.player.setPlaybackRate)
.toHaveBeenCalledWith('0.75');
});
});
});
describe('onVolumeChange', function () {
beforeEach(function () {
initialize();
spyOn(videoPlayer.player, 'setVolume');
videoPlayer.onVolumeChange(60);
});
it('set the volume on player', function () {
expect(videoPlayer.player.setVolume).toHaveBeenCalledWith(60);
});
});
describe('update', function () {
beforeEach(function () {
initialize();
spyOn(videoPlayer, 'updatePlayTime').andCallThrough();
});
describe(
'when the current time is unavailable from the player',
function ()
{
beforeEach(function () {
videoPlayer.player.getCurrentTime = function () {
return NaN;
};
videoPlayer.update();
});
it('does not trigger updatePlayTime event', function () {
expect(videoPlayer.updatePlayTime).not.toHaveBeenCalled();
});
});
describe(
'when the current time is available from the player',
function ()
{
beforeEach(function () {
videoPlayer.player.getCurrentTime = function () {
return 60;
};
videoPlayer.update();
});
it('trigger updatePlayTime event', function () {
expect(videoPlayer.updatePlayTime)
.toHaveBeenCalledWith(60);
});
});
});
// Disabled 10/24/13 due to flakiness in master
xdescribe('updatePlayTime', function () {
beforeEach(function () {
initialize();
spyOn(videoCaption, 'updatePlayTime').andCallThrough();
spyOn(videoProgressSlider, 'updatePlayTime').andCallThrough();
});
it('update the video playback time', function () {
var duration = 0;
waitsFor(function () {
duration = videoPlayer.duration();
if (duration > 0) {
return true;
}
return false;
}, 'Video is fully loaded.', 1000);
runs(function () {
var htmlStr;
videoPlayer.updatePlayTime(60);
htmlStr = $('.vidtime').html();
// We resort to this trickery because Firefox and Chrome
// round the total time a bit differently.
if (
htmlStr.match('1:00 / 1:01') ||
htmlStr.match('1:00 / 1:00')
) {
expect(true).toBe(true);
} else {
expect(true).toBe(false);
}
// The below test has been replaced by above trickery:
//
// expect($('.vidtime')).toHaveHtml('1:00 / 1:01');
});
});
it('update the playback time on caption', function () {
waitsFor(function () {
return videoPlayer.duration() > 0;
}, 'Video is fully loaded.', 1000);
runs(function () {
videoPlayer.updatePlayTime(60);
expect(videoCaption.updatePlayTime)
.toHaveBeenCalledWith(60);
});
});
it('update the playback time on progress slider', function () {
var duration = 0;
waitsFor(function () {
duration = videoPlayer.duration();
return duration > 0;
}, 'Video is fully loaded.', 1000);
runs(function () {
videoPlayer.updatePlayTime(60);
expect(videoProgressSlider.updatePlayTime)
.toHaveBeenCalledWith({
time: 60,
duration: duration
});
});
});
});
describe('toggleFullScreen', function () {
describe('when the video player is not full screen', function () {
beforeEach(function () {
initialize();
spyOn(videoCaption, 'resize').andCallThrough();
videoControl.toggleFullScreen(jQuery.Event('click'));
});
it('replace the full screen button tooltip', function () {
expect($('.add-fullscreen'))
.toHaveAttr('title', 'Exit full browser');
});
it('add the video-fullscreen class', function () {
expect(state.el).toHaveClass('video-fullscreen');
});
it('tell VideoCaption to resize', function () {
expect(videoCaption.resize).toHaveBeenCalled();
expect(state.resizer.setMode).toHaveBeenCalled();
});
});
describe('when the video player already full screen', function () {
beforeEach(function () {
initialize();
spyOn(videoCaption, 'resize').andCallThrough();
state.el.addClass('video-fullscreen');
videoControl.fullScreenState = true;
isFullScreen = true;
videoControl.fullScreenEl.attr('title', 'Exit-fullscreen');
videoControl.toggleFullScreen(jQuery.Event('click'));
});
it('replace the full screen button tooltip', function () {
expect($('.add-fullscreen'))
.toHaveAttr('title', 'Fill browser');
});
it('remove the video-fullscreen class', function () {
expect(state.el).not.toHaveClass('video-fullscreen');
});
it('tell VideoCaption to resize', function () {
expect(videoCaption.resize).toHaveBeenCalled();
expect(state.resizer.setMode)
.toHaveBeenCalledWith('width');
});
});
});
describe('play', function () {
beforeEach(function () {
initialize();
spyOn(player, 'playVideo').andCallThrough();
});
describe('when the player is not ready', function () {
beforeEach(function () {
player.playVideo = void 0;
videoPlayer.play();
});
it('does nothing', function () {
expect(player.playVideo).toBeUndefined();
});
});
describe('when the player is ready', function () {
beforeEach(function () {
player.playVideo.andReturn(true);
videoPlayer.play();
});
it('delegate to the player', function () {
expect(player.playVideo).toHaveBeenCalled();
});
});
});
describe('isPlaying', function () {
beforeEach(function () {
initialize();
spyOn(player, 'getPlayerState').andCallThrough();
});
describe('when the video is playing', function () {
beforeEach(function () {
player.getPlayerState.andReturn(YT.PlayerState.PLAYING);
});
it('return true', function () {
expect(videoPlayer.isPlaying()).toBeTruthy();
});
});
describe('when the video is not playing', function () {
beforeEach(function () {
player.getPlayerState.andReturn(YT.PlayerState.PAUSED);
});
it('return false', function () {
expect(videoPlayer.isPlaying()).toBeFalsy();
});
});
});
describe('pause', function () {
beforeEach(function () {
initialize();
spyOn(player, 'pauseVideo').andCallThrough();
videoPlayer.pause();
});
it('delegate to the player', function () {
expect(player.pauseVideo).toHaveBeenCalled();
});
});
describe('duration', function () {
beforeEach(function () {
initialize();
spyOn(player, 'getDuration').andCallThrough();
videoPlayer.duration();
});
it('delegate to the player', function () {
expect(player.getDuration).toHaveBeenCalled();
});
});
describe('playback rate', function () {
beforeEach(function () {
initialize();
player.setPlaybackRate(1.5);
});
it('set the player playback rate', function () {
expect(player.video.playbackRate).toEqual(1.5);
});
});
describe('volume', function () {
beforeEach(function () {
initialize();
spyOn(player, 'getVolume').andCallThrough();
});
it('set the player volume', function () {
var expectedValue = 60,
realValue;
player.setVolume(60);
realValue = Math.round(player.getVolume()*100);
expect(realValue).toEqual(expectedValue);
});
});
});
afterEach(function() {
$('source').remove();
window.onTouchBasedDevice = oldOTBD;
});
describe('constructor', function() {
describe('always', function() {
beforeEach(function() {
initialize();
});
it('instanticate current time to zero', function() {
expect(videoPlayer.currentTime).toEqual(0);
});
it('set the element', function() {
expect(state.el).toHaveId('video_id');
});
it('create video control', function() {
expect(videoControl).toBeDefined();
expect(videoControl.el).toHaveClass('video-controls');
});
it('create video caption', function() {
expect(videoCaption).toBeDefined();
expect(state.youtubeId()).toEqual('Z5KLxerq05Y');
expect(state.speed).toEqual('1.0');
expect(state.config.caption_asset_path).toEqual('/static/subs/');
});
it('create video speed control', function() {
expect(videoSpeedControl).toBeDefined();
expect(videoSpeedControl.el).toHaveClass('speeds');
expect(videoSpeedControl.speeds).toEqual([ '0.75', '1.0', '1.25', '1.50' ]);
expect(state.speed).toEqual('1.0');
});
it('create video progress slider', function() {
expect(videoProgressSlider).toBeDefined();
expect(videoProgressSlider.el).toHaveClass('slider');
});
// All the toHandleWith() expect tests are not necessary for this version of Video.
// jQuery event system is not used to trigger and invoke methods. This is an artifact from
// previous version of Video.
});
it('create Youtube player', function() {
var oldYT = window.YT;
jasmine.stubRequests();
window.YT = {
Player: function () { },
PlayerState: oldYT.PlayerState,
ready: function(f){f();}
};
spyOn(window.YT, 'Player');
initializeYouTube();
expect(YT.Player).toHaveBeenCalledWith('id', {
playerVars: {
controls: 0,
wmode: 'transparent',
rel: 0,
showinfo: 0,
enablejsapi: 1,
modestbranding: 1,
html5: 1,
start: 0,
end: null
},
videoId: 'cogebirgzzM',
events: {
onReady: videoPlayer.onReady,
onStateChange: videoPlayer.onStateChange,
onPlaybackQualityChange: videoPlayer.onPlaybackQualityChange
}
});
window.YT = oldYT;
});
// We can't test the invocation of HTML5Video because it is not available
// globally. It is defined within the scope of Require JS.
describe('when not on a touch based device', function() {
beforeEach(function() {
window.onTouchBasedDevice.andReturn(true);
initialize();
});
it('create video volume control', function() {
expect(videoVolumeControl).toBeDefined();
expect(videoVolumeControl.el).toHaveClass('volume');
});
});
describe('when on a touch based device', function() {
var oldOTBD;
beforeEach(function() {
initialize();
});
it('controls are in paused state', function() {
expect(videoControl.isPlaying).toBe(false);
});
});
});
describe('onReady', function() {
beforeEach(function() {
initialize();
spyOn(videoPlayer, 'log').andCallThrough();
spyOn(videoPlayer, 'play').andCallThrough();
videoPlayer.onReady();
});
it('log the load_video event', function() {
expect(videoPlayer.log).toHaveBeenCalledWith('load_video');
});
it('autoplay the first video', function() {
expect(videoPlayer.play).not.toHaveBeenCalled();
});
});
describe('onStateChange', function() {
describe('when the video is unstarted', function() {
beforeEach(function() {
initialize();
spyOn(videoControl, 'pause').andCallThrough();
spyOn(videoCaption, 'pause').andCallThrough();
videoPlayer.onStateChange({
data: YT.PlayerState.PAUSED
});
});
it('pause the video control', function() {
expect(videoControl.pause).toHaveBeenCalled();
});
it('pause the video caption', function() {
expect(videoCaption.pause).toHaveBeenCalled();
});
});
describe('when the video is playing', function() {
var oldState;
beforeEach(function() {
// Create the first instance of the player.
initialize();
oldState = state;
spyOn(oldState.videoPlayer, 'onPause').andCallThrough();
// Now initialize a second instance.
initialize();
spyOn(videoPlayer, 'log').andCallThrough();
spyOn(window, 'setInterval').andReturn(100);
spyOn(videoControl, 'play');
spyOn(videoCaption, 'play');
videoPlayer.onStateChange({
data: YT.PlayerState.PLAYING
});
});
it('log the play_video event', function() {
expect(videoPlayer.log).toHaveBeenCalledWith('play_video', {
currentTime: 0
});
});
it('pause other video player', function() {
expect(oldState.videoPlayer.onPause).toHaveBeenCalled();
});
it('set update interval', function() {
expect(window.setInterval).toHaveBeenCalledWith(videoPlayer.update, 200);
expect(videoPlayer.updateInterval).toEqual(100);
});
it('play the video control', function() {
expect(videoControl.play).toHaveBeenCalled();
});
it('play the video caption', function() {
expect(videoCaption.play).toHaveBeenCalled();
});
});
describe('when the video is paused', function() {
var currentUpdateIntrval;
beforeEach(function() {
initialize();
spyOn(videoPlayer, 'log').andCallThrough();
spyOn(window, 'clearInterval').andCallThrough();
spyOn(videoControl, 'pause').andCallThrough();
spyOn(videoCaption, 'pause').andCallThrough();
videoPlayer.onStateChange({
data: YT.PlayerState.PLAYING
});
currentUpdateIntrval = videoPlayer.updateInterval;
videoPlayer.onStateChange({
data: YT.PlayerState.PAUSED
});
});
it('log the pause_video event', function() {
expect(videoPlayer.log).toHaveBeenCalledWith('pause_video', {
currentTime: 0
});
});
it('clear update interval', function() {
expect(window.clearInterval).toHaveBeenCalledWith(currentUpdateIntrval);
expect(videoPlayer.updateInterval).toBeUndefined();
});
it('pause the video control', function() {
expect(videoControl.pause).toHaveBeenCalled();
});
it('pause the video caption', function() {
expect(videoCaption.pause).toHaveBeenCalled();
});
});
describe('when the video is ended', function() {
beforeEach(function() {
initialize();
spyOn(videoControl, 'pause').andCallThrough();
spyOn(videoCaption, 'pause').andCallThrough();
videoPlayer.onStateChange({
data: YT.PlayerState.ENDED
});
});
it('pause the video control', function() {
expect(videoControl.pause).toHaveBeenCalled();
});
it('pause the video caption', function() {
expect(videoCaption.pause).toHaveBeenCalled();
});
});
});
describe('onSeek', function() {
beforeEach(function() {
spyOn(window, 'clearInterval').andCallThrough();
initialize();
videoPlayer.updateInterval = 100;
spyOn(videoPlayer, 'updatePlayTime');
spyOn(videoPlayer, 'log');
spyOn(videoPlayer.player, 'seekTo');
});
it('Slider event causes log update', function () {
videoProgressSlider.onSlide(jQuery.Event('slide'), {value: 60});
expect(videoPlayer.log).toHaveBeenCalledWith(
'seek_video',
{
old_time: 0,
new_time: 60,
type: 'onSlideSeek'
}
);
});
it('seek the player', function() {
videoProgressSlider.onSlide(jQuery.Event('slide'), {value: 60});
expect(videoPlayer.player.seekTo).toHaveBeenCalledWith(60, true);
});
it('call updatePlayTime on player', function() {
videoProgressSlider.onSlide(jQuery.Event('slide'), {value: 60});
expect(videoPlayer.updatePlayTime).toHaveBeenCalledWith(60);
});
it('when the player is playing: reset the update interval', function() {
videoProgressSlider.onSlide(jQuery.Event('slide'), {value: 60});
expect(window.clearInterval).toHaveBeenCalledWith(100);
});
it('when the player is not playing: set the current time', function() {
videoProgressSlider.onSlide(jQuery.Event('slide'), {value: 60});
videoPlayer.pause();
expect(videoPlayer.currentTime).toEqual(60);
});
});
describe('onSpeedChange', function() {
beforeEach(function() {
initialize();
videoPlayer.currentTime = 60;
spyOn(videoPlayer, 'updatePlayTime').andCallThrough();
spyOn(state, 'setSpeed').andCallThrough();
spyOn(videoPlayer, 'log').andCallThrough();
spyOn(videoPlayer.player, 'setPlaybackRate').andCallThrough();
});
describe('always', function() {
beforeEach(function() {
videoPlayer.onSpeedChange('0.75', false);
});
it('check if speed_change_video is logged', function() {
expect(videoPlayer.log).toHaveBeenCalledWith('speed_change_video', {
current_time: videoPlayer.currentTime,
old_speed: '1.0',
new_speed: '0.75'
});
});
it('convert the current time to the new speed', function() {
expect(videoPlayer.currentTime).toEqual(60);
});
it('set video speed to the new speed', function() {
expect(state.setSpeed).toHaveBeenCalledWith('0.75', false);
});
// Not relevant any more:
//
// expect( "tell video caption that the speed has changed" ) ...
});
describe('when the video is playing', function() {
beforeEach(function() {
videoPlayer.play();
videoPlayer.onSpeedChange('0.75', false);
});
it('trigger updatePlayTime event', function() {
expect(videoPlayer.player.setPlaybackRate).toHaveBeenCalledWith('0.75');
});
});
describe('when the video is not playing', function() {
beforeEach(function() {
videoPlayer.pause();
videoPlayer.onSpeedChange('0.75', false);
});
it('trigger updatePlayTime event', function() {
expect(videoPlayer.player.setPlaybackRate).toHaveBeenCalledWith('0.75');
});
});
});
describe('onVolumeChange', function() {
beforeEach(function() {
initialize();
spyOn(videoPlayer.player, 'setVolume');
videoPlayer.onVolumeChange(60);
});
it('set the volume on player', function() {
expect(videoPlayer.player.setVolume).toHaveBeenCalledWith(60);
});
});
describe('update', function() {
beforeEach(function() {
initialize();
spyOn(videoPlayer, 'updatePlayTime').andCallThrough();
});
describe('when the current time is unavailable from the player', function() {
beforeEach(function() {
videoPlayer.player.getCurrentTime = function () {
return NaN;
};
videoPlayer.update();
});
it('does not trigger updatePlayTime event', function() {
expect(videoPlayer.updatePlayTime).not.toHaveBeenCalled();
});
});
describe('when the current time is available from the player', function() {
beforeEach(function() {
videoPlayer.player.getCurrentTime = function () {
return 60;
};
videoPlayer.update();
});
it('trigger updatePlayTime event', function() {
expect(videoPlayer.updatePlayTime).toHaveBeenCalledWith(60);
});
});
});
describe('updatePlayTime', function() {
beforeEach(function() {
initialize();
spyOn(videoCaption, 'updatePlayTime').andCallThrough();
spyOn(videoProgressSlider, 'updatePlayTime').andCallThrough();
});
it('update the video playback time', function() {
var duration = 0;
waitsFor(function () {
duration = videoPlayer.duration();
if (duration > 0) {
return true;
}
return false;
}, 'Video is fully loaded.', 1000);
runs(function () {
var htmlStr;
videoPlayer.updatePlayTime(60);
htmlStr = $('.vidtime').html();
// We resort to this trickery because Firefox and Chrome
// round the total time a bit differently.
if (htmlStr.match('1:00 / 1:01') || htmlStr.match('1:00 / 1:00')) {
expect(true).toBe(true);
} else {
expect(true).toBe(false);
}
// The below test has been replaced by above trickery:
//
// expect($('.vidtime')).toHaveHtml('1:00 / 1:01');
});
});
it('update the playback time on caption', function() {
var duration = 0;
waitsFor(function () {
duration = videoPlayer.duration();
if (duration > 0) {
return true;
}
return false;
}, 'Video is fully loaded.', 1000);
runs(function () {
videoPlayer.updatePlayTime(60);
expect(videoCaption.updatePlayTime).toHaveBeenCalledWith(60);
});
});
it('update the playback time on progress slider', function() {
var duration = 0;
waitsFor(function () {
duration = videoPlayer.duration();
if (duration > 0) {
return true;
}
return false;
}, 'Video is fully loaded.', 1000);
runs(function () {
videoPlayer.updatePlayTime(60);
expect(videoProgressSlider.updatePlayTime).toHaveBeenCalledWith({
time: 60,
duration: duration
});
});
});
});
describe('toggleFullScreen', function() {
describe('when the video player is not full screen', function() {
beforeEach(function() {
initialize();
spyOn(videoCaption, 'resize').andCallThrough();
videoControl.toggleFullScreen(jQuery.Event("click"));
});
it('replace the full screen button tooltip', function() {
expect($('.add-fullscreen')).toHaveAttr('title', 'Exit full browser');
});
it('add the video-fullscreen class', function() {
expect(state.el).toHaveClass('video-fullscreen');
});
it('tell VideoCaption to resize', function() {
expect(videoCaption.resize).toHaveBeenCalled();
expect(state.resizer.setMode).toHaveBeenCalled();
});
});
describe('when the video player already full screen', function() {
beforeEach(function() {
initialize();
spyOn(videoCaption, 'resize').andCallThrough();
state.el.addClass('video-fullscreen');
videoControl.fullScreenState = true;
isFullScreen = true;
videoControl.fullScreenEl.attr('title', 'Exit-fullscreen');
videoControl.toggleFullScreen(jQuery.Event("click"));
});
it('replace the full screen button tooltip', function() {
expect($('.add-fullscreen')).toHaveAttr('title', 'Fill browser');
});
it('remove the video-fullscreen class', function() {
expect(state.el).not.toHaveClass('video-fullscreen');
});
it('tell VideoCaption to resize', function() {
expect(videoCaption.resize).toHaveBeenCalled();
expect(state.resizer.setMode).toHaveBeenCalledWith('width');
});
});
});
describe('play', function() {
beforeEach(function() {
initialize();
spyOn(player, 'playVideo').andCallThrough();
});
describe('when the player is not ready', function() {
beforeEach(function() {
player.playVideo = void 0;
videoPlayer.play();
});
it('does nothing', function() {
expect(player.playVideo).toBeUndefined();
});
});
describe('when the player is ready', function() {
beforeEach(function() {
player.playVideo.andReturn(true);
videoPlayer.play();
});
it('delegate to the player', function() {
expect(player.playVideo).toHaveBeenCalled();
});
});
});
describe('isPlaying', function() {
beforeEach(function() {
initialize();
spyOn(player, 'getPlayerState').andCallThrough();
});
describe('when the video is playing', function() {
beforeEach(function() {
player.getPlayerState.andReturn(YT.PlayerState.PLAYING);
});
it('return true', function() {
expect(videoPlayer.isPlaying()).toBeTruthy();
});
});
describe('when the video is not playing', function() {
beforeEach(function() {
player.getPlayerState.andReturn(YT.PlayerState.PAUSED);
});
it('return false', function() {
expect(videoPlayer.isPlaying()).toBeFalsy();
});
});
});
describe('pause', function() {
beforeEach(function() {
initialize();
spyOn(player, 'pauseVideo').andCallThrough();
videoPlayer.pause();
});
it('delegate to the player', function() {
expect(player.pauseVideo).toHaveBeenCalled();
});
});
describe('duration', function() {
beforeEach(function() {
initialize();
spyOn(player, 'getDuration').andCallThrough();
videoPlayer.duration();
});
it('delegate to the player', function() {
expect(player.getDuration).toHaveBeenCalled();
});
});
describe('playback rate', function() {
beforeEach(function() {
initialize();
player.setPlaybackRate(1.5);
});
it('set the player playback rate', function() {
expect(player.video.playbackRate).toEqual(1.5);
});
});
describe('volume', function() {
beforeEach(function() {
initialize();
spyOn(player, 'getVolume').andCallThrough();
});
it('set the player volume', function() {
var expectedValue = 60,
realValue;
player.setVolume(60);
realValue = Math.round(player.getVolume()*100);
expect(realValue).toEqual(expectedValue);
});
});
});
}).call(this);

View File

@@ -1,192 +1,253 @@
(function() {
describe('VideoProgressSlider', function() {
var state, videoPlayer, videoProgressSlider, oldOTBD;
describe('VideoProgressSlider', function() {
var state, videoPlayer, videoProgressSlider, oldOTBD;
function initialize() {
loadFixtures('video_all.html');
state = new Video('#example');
videoPlayer = state.videoPlayer;
videoProgressSlider = state.videoProgressSlider;
}
beforeEach(function() {
oldOTBD = window.onTouchBasedDevice;
window.onTouchBasedDevice = jasmine.createSpy('onTouchBasedDevice').andReturn(false);
});
afterEach(function() {
$('source').remove();
window.onTouchBasedDevice = oldOTBD;
});
describe('constructor', function() {
describe('on a non-touch based device', function() {
beforeEach(function() {
spyOn($.fn, 'slider').andCallThrough();
initialize();
});
it('build the slider', function() {
expect(videoProgressSlider.slider).toBe('.slider');
expect($.fn.slider).toHaveBeenCalledWith({
range: 'min',
change: videoProgressSlider.onChange,
slide: videoProgressSlider.onSlide,
stop: videoProgressSlider.onStop
});
});
it('build the seek handle', function() {
expect(videoProgressSlider.handle).toBe('.slider .ui-slider-handle');
});
});
describe('on a touch-based device', function() {
beforeEach(function() {
window.onTouchBasedDevice.andReturn(true);
spyOn($.fn, 'slider').andCallThrough();
initialize();
});
it('does not build the slider', function() {
expect(videoProgressSlider.slider).toBeUndefined();
// We can't expect $.fn.slider not to have been called,
// because sliders are used in other parts of Video.
});
});
});
describe('play', function() {
beforeEach(function() {
initialize();
});
describe('when the slider was already built', function() {
var spy;
function initialize() {
loadFixtures('video_all.html');
state = new Video('#example');
videoPlayer = state.videoPlayer;
videoProgressSlider = state.videoProgressSlider;
}
beforeEach(function() {
spy = spyOn(videoProgressSlider, 'buildSlider');
spy.andCallThrough();
videoPlayer.play();
oldOTBD = window.onTouchBasedDevice;
window.onTouchBasedDevice = jasmine.createSpy('onTouchBasedDevice')
.andReturn(false);
});
it('does not build the slider', function() {
expect(spy.callCount).toEqual(0);
afterEach(function() {
$('source').remove();
window.onTouchBasedDevice = oldOTBD;
});
});
// Currently, the slider is not rebuilt if it does not exist.
describe('constructor', function() {
describe('on a non-touch based device', function() {
beforeEach(function() {
spyOn($.fn, 'slider').andCallThrough();
initialize();
});
it('build the slider', function() {
expect(videoProgressSlider.slider).toBe('.slider');
expect($.fn.slider).toHaveBeenCalledWith({
range: 'min',
change: videoProgressSlider.onChange,
slide: videoProgressSlider.onSlide,
stop: videoProgressSlider.onStop
});
});
it('build the seek handle', function() {
expect(videoProgressSlider.handle)
.toBe('.slider .ui-slider-handle');
});
});
describe('on a touch-based device', function() {
beforeEach(function() {
window.onTouchBasedDevice.andReturn(true);
spyOn($.fn, 'slider').andCallThrough();
initialize();
});
it('does not build the slider', function() {
expect(videoProgressSlider.slider).toBeUndefined();
// We can't expect $.fn.slider not to have been called,
// because sliders are used in other parts of Video.
});
});
});
describe('play', function() {
beforeEach(function() {
initialize();
});
describe('when the slider was already built', function() {
var spy;
beforeEach(function() {
spy = spyOn(videoProgressSlider, 'buildSlider');
spy.andCallThrough();
videoPlayer.play();
});
it('does not build the slider', function() {
expect(spy.callCount).toEqual(0);
});
});
// Currently, the slider is not rebuilt if it does not exist.
});
describe('updatePlayTime', function() {
beforeEach(function() {
initialize();
});
describe('when frozen', function() {
beforeEach(function() {
spyOn($.fn, 'slider').andCallThrough();
videoProgressSlider.frozen = true;
videoProgressSlider.updatePlayTime(20, 120);
});
it('does not update the slider', function() {
expect($.fn.slider).not.toHaveBeenCalled();
});
});
describe('when not frozen', function() {
beforeEach(function() {
spyOn($.fn, 'slider').andCallThrough();
videoProgressSlider.frozen = false;
videoProgressSlider.updatePlayTime({
time: 20,
duration: 120
});
});
it('update the max value of the slider', function() {
expect($.fn.slider).toHaveBeenCalledWith(
'option', 'max', 120
);
});
it('update current value of the slider', function() {
expect($.fn.slider).toHaveBeenCalledWith(
'option', 'value', 20
);
});
});
});
describe('onSlide', function() {
beforeEach(function() {
initialize();
spyOn($.fn, 'slider').andCallThrough();
spyOn(videoPlayer, 'onSlideSeek').andCallThrough();
state.videoPlayer.play();
waitsFor(function () {
var duration = videoPlayer.duration(),
currentTime = videoPlayer.currentTime;
return (
isFinite(currentTime) &&
currentTime > 0 &&
isFinite(duration) &&
duration > 0
);
}, 'video begins playing', 10000);
});
it('freeze the slider', function() {
runs(function () {
videoProgressSlider.onSlide(
jQuery.Event('slide'), { value: 20 }
);
expect(videoProgressSlider.frozen).toBeTruthy();
});
});
// Turned off test due to flakiness (30.10.2013).
xit('trigger seek event', function() {
runs(function () {
videoProgressSlider.onSlide(
jQuery.Event('slide'), { value: 20 }
);
expect(videoPlayer.onSlideSeek).toHaveBeenCalled();
waitsFor(function () {
return Math.round(videoPlayer.currentTime) === 20;
}, 'currentTime got updated', 10000);
});
});
});
describe('onStop', 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);
initialize();
spyOn(videoPlayer, 'onSlideSeek').andCallThrough();
videoPlayer.play();
waitsFor(function () {
var duration = videoPlayer.duration(),
currentTime = videoPlayer.currentTime;
return (
isFinite(currentTime) &&
currentTime > 0 &&
isFinite(duration) &&
duration > 0
);
}, 'video begins playing', 10000);
});
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;
});
it('freeze the slider', function() {
runs(function () {
videoProgressSlider.onStop(
jQuery.Event('stop'), { value: 20 }
);
expect(videoProgressSlider.frozen).toBeTruthy();
});
});
// Turned off test due to flakiness (30.10.2013).
xit('trigger seek event', function() {
runs(function () {
videoProgressSlider.onStop(
jQuery.Event('stop'), { value: 20 }
);
expect(videoPlayer.onSlideSeek).toHaveBeenCalled();
waitsFor(function () {
return Math.round(videoPlayer.currentTime) === 20;
}, 'currentTime got updated', 10000);
});
});
it('set timeout to unfreeze the slider', function() {
runs(function () {
videoProgressSlider.onStop(
jQuery.Event('stop'), { value: 20 }
);
expect(window.setTimeout).toHaveBeenCalledWith(
jasmine.any(Function), 200
);
window.setTimeout.mostRecentCall.args[0]();
expect(videoProgressSlider.frozen).toBeFalsy();
});
});
});
});
describe('updatePlayTime', function() {
beforeEach(function() {
initialize();
});
describe('when frozen', function() {
beforeEach(function() {
spyOn($.fn, 'slider').andCallThrough();
videoProgressSlider.frozen = true;
videoProgressSlider.updatePlayTime(20, 120);
});
it('does not update the slider', function() {
expect($.fn.slider).not.toHaveBeenCalled();
});
});
describe('when not frozen', function() {
beforeEach(function() {
spyOn($.fn, 'slider').andCallThrough();
videoProgressSlider.frozen = false;
videoProgressSlider.updatePlayTime({time:20, duration:120});
});
it('update the max value of the slider', function() {
expect($.fn.slider).toHaveBeenCalledWith('option', 'max', 120);
});
it('update current value of the slider', function() {
expect($.fn.slider).toHaveBeenCalledWith('option', 'value', 20);
});
});
});
describe('onSlide', function() {
beforeEach(function() {
initialize();
spyOn($.fn, 'slider').andCallThrough();
spyOn(videoPlayer, 'onSlideSeek').andCallThrough();
videoProgressSlider.onSlide({}, {
value: 20
});
});
it('freeze the slider', function() {
expect(videoProgressSlider.frozen).toBeTruthy();
});
it('trigger seek event', function() {
expect(videoPlayer.onSlideSeek).toHaveBeenCalled();
expect(videoPlayer.currentTime).toEqual(20);
});
});
describe('onChange', function() {
beforeEach(function() {
initialize();
spyOn($.fn, 'slider').andCallThrough();
videoProgressSlider.onChange({}, {
value: 20
});
});
});
describe('onStop', 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);
initialize();
spyOn(videoPlayer, 'onSlideSeek').andCallThrough();
videoProgressSlider.onStop({}, {
value: 20
});
});
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;
});
it('freeze the slider', function() {
expect(videoProgressSlider.frozen).toBeTruthy();
});
it('trigger seek event', function() {
expect(videoPlayer.onSlideSeek).toHaveBeenCalled();
expect(videoPlayer.currentTime).toEqual(20);
});
// Disabled 10/9/13 after failing in master
xit('set timeout to unfreeze the slider', function() {
expect(window.setTimeout).toHaveBeenCalledWith(jasmine.any(Function), 200);
window.setTimeout.mostRecentCall.args[0]();
expect(videoProgressSlider.frozen).toBeFalsy();
});
});
});
}).call(this);

View File

@@ -23,7 +23,9 @@ function () {
}
if (!config.element) {
console.log('Required parameter `element` is not passed.');
console.log(
'[Video info]: Required parameter `element` is not passed.'
);
}
return this;
@@ -55,7 +57,7 @@ function () {
};
};
var align = function() {
var align = function () {
var data = getData();
switch (mode) {

View File

@@ -262,8 +262,8 @@ function (VideoPlayer) {
this.config = {
element: element,
start: data['start'],
end: data['end'],
startTime: data['start'],
endTime: data['end'],
caption_data_dir: data['captionDataDir'],
caption_asset_path: data['captionAssetPath'],
show_captions: regExp.test(data['showCaptions'].toString()),
@@ -369,7 +369,7 @@ function (VideoPlayer) {
/*
* function checkStartEndTimes()
*
* Validate config.start and config.end times.
* Validate config.startTime and config.endTime times.
*
* We can check at this time if the times are proper integers, and if they
* make general sense. I.e. if start time is => 0 and <= end time.
@@ -379,14 +379,18 @@ function (VideoPlayer) {
* if start time and/or end time are greater than the length of the video.
*/
function checkStartEndTimes() {
this.config.start = parseInt(this.config.start, 10);
if ((!isFinite(this.config.start)) || (this.config.start < 0)) {
this.config.start = 0;
this.config.startTime = parseInt(this.config.startTime, 10);
if (!isFinite(this.config.startTime) || this.config.startTime < 0) {
this.config.startTime = 0;
}
this.config.end = parseInt(this.config.end, 10);
if ((!isFinite(this.config.end)) || (this.config.end < this.config.start)) {
this.config.end = null;
this.config.endTime = parseInt(this.config.endTime, 10);
if (
!isFinite(this.config.endTime) ||
this.config.endTime < this.config.startTime ||
this.config.endTime === 0
) {
this.config.endTime = null;
}
}

View File

@@ -44,8 +44,12 @@ function () {
// Private functions.
function _makeFunctionsPublic(state) {
state.focusGrabber.enableFocusGrabber = _.bind(enableFocusGrabber, state);
state.focusGrabber.disableFocusGrabber = _.bind(disableFocusGrabber, state);
state.focusGrabber.enableFocusGrabber = _.bind(
enableFocusGrabber, state
);
state.focusGrabber.disableFocusGrabber = _.bind(
disableFocusGrabber, state
);
state.focusGrabber.onFocus = _.bind(onFocus, state);
}

View File

@@ -1,10 +1,12 @@
/**
* @file HTML5 video player module. Provides methods to control the in-browser HTML5 video player.
* @file HTML5 video player module. Provides methods to control the in-browser
* HTML5 video player.
*
* The goal was to write this module so that it closely resembles the YouTube API. The main reason
* for this is because initially the edX video player supported only YouTube videos. When HTML5
* support was added, for greater compatibility, and to reduce the amount of code that needed to
* be modified, it was decided to write a similar API as the one provided by YouTube.
* The goal was to write this module so that it closely resembles the YouTube
* API. The main reason for this is because initially the edX video player
* supported only YouTube videos. When HTML5 support was added, for greater
* compatibility, and to reduce the amount of code that needed to be modified,
* it was decided to write a similar API as the one provided by YouTube.
*
* @external RequireJS
*
@@ -33,16 +35,17 @@ function () {
};
Player.prototype.seekTo = function (value) {
if ((typeof value === 'number') && (value <= this.video.duration) && (value >= 0)) {
this.start = 0;
this.end = this.video.duration;
if (
typeof value === 'number' &&
value <= this.video.duration &&
value >= 0
) {
this.video.currentTime = value;
}
};
Player.prototype.setVolume = function (value) {
if ((typeof value === 'number') && (value <= 100) && (value >= 0)) {
if (typeof value === 'number' && value <= 100 && value >= 0) {
this.video.volume = value * 0.01;
}
};
@@ -92,35 +95,33 @@ function () {
/*
* Constructor function for HTML5 Video player.
*
* @param {String|Object} el A DOM element where the HTML5 player will be inserted (as returned by jQuery(selector) function),
* or a selector string which will be used to select an element. This is a required parameter.
* @param {String|Object} el A DOM element where the HTML5 player will
* be inserted (as returned by jQuery(selector) function), or a
* selector string which will be used to select an element. This is a
* required parameter.
*
* @param config - An object whose properties will be used as configuration options for the HTML5 video
* player. This is an optional parameter. In the case if this parameter is missing, or some of the config
* object's properties are missing, defaults will be used. The available options (and their defaults) are as
* @param config - An object whose properties will be used as
* configuration options for the HTML5 video player. This is an
* optional parameter. In the case if this parameter is missing, or
* some of the config object's properties are missing, defaults will be
* used. The available options (and their defaults) are as
* follows:
*
* config = {
*
* videoSources: {}, // An object with properties being video sources. The property name is the
* // video format of the source. Supported video formats are: 'mp4', 'webm', and
* // 'ogg'.
* videoSources: {}, // An object with properties being video
* // sources. The property name is the
* // video format of the source. Supported
* // video formats are: 'mp4', 'webm', and
* // 'ogg'.
*
* playerVars: { // Object's properties identify player parameters.
* start: 0, // Possible values: positive integer. Position from which to start playing the
* // video. Measured in seconds. If value is non-numeric, or 'start' property is
* // not specified, the video will start playing from the beginning.
*
* end: null // Possible values: positive integer. Position when to stop playing the
* // video. Measured in seconds. If value is null, or 'end' property is not
* // specified, the video will end playing at the end.
*
* },
*
* events: { // Object's properties identify the events that the API fires, and the
* // functions (event listeners) that the API will call when those events occur.
* // If value is null, or property is not specified, then no callback will be
* // called for that event.
* events: { // Object's properties identify the
* // events that the API fires, and the
* // functions (event listeners) that the
* // API will call when those events occur.
* // If value is null, or property is not
* // specified, then no callback will be
* // called for that event.
*
* onReady: null,
* onStateChange: null
@@ -130,16 +131,19 @@ function () {
function Player(el, config) {
var sourceStr, _this, errorMessage;
// Initially we assume that el is a DOM element. If jQuery selector fails to select something, we
// assume that el is an ID of a DOM element. We try to select by ID. If jQuery fails this time,
// we return. Nothing breaks because the player 'onReady' event will never be fired.
// Initially we assume that el is a DOM element. If jQuery selector
// fails to select something, we assume that el is an ID of a DOM
// element. We try to select by ID. If jQuery fails this time, we
// return. Nothing breaks because the player 'onReady' event will
// never be fired.
this.el = $(el);
if (this.el.length === 0) {
this.el = $('#' + el);
if (this.el.length === 0) {
errorMessage = 'VideoPlayer: Element corresponding to the given selector does not found.';
errorMessage = 'VideoPlayer: Element corresponding to ' +
'the given selector does not found.';
if (window.console && console.log) {
console.log(errorMessage);
} else {
@@ -156,12 +160,14 @@ function () {
return;
}
// We should have at least one video source. Otherwise there is no point to continue.
// We should have at least one video source. Otherwise there is no
// point to continue.
if (!config.videoSources) {
return;
}
// From the start, all sources are empty. We will populate this object below.
// From the start, all sources are empty. We will populate this
// object below.
sourceStr = {
mp4: ' ',
webm: ' ',
@@ -171,7 +177,8 @@ function () {
// Will be used in inner functions to point to the current object.
_this = this;
// Create HTML markup for individual sources of the HTML5 <video> element.
// Create HTML markup for individual sources of the HTML5 <video>
// element.
$.each(sourceStr, function (videoType, videoSource) {
if (
(_this.config.videoSources[videoType]) &&
@@ -179,58 +186,60 @@ function () {
) {
sourceStr[videoType] =
'<source ' +
'src="' + _this.config.videoSources[videoType] + '" ' +
'type="video/' + videoType + '" ' +
'src="' + _this.config.videoSources[videoType] +
'" ' + 'type="video/' + videoType + '" ' +
'/> ';
}
});
// We should have at least one video source. Otherwise there is no point to continue.
if ((sourceStr.mp4 === ' ') && (sourceStr.webm === ' ') && (sourceStr.ogg === ' ')) {
// We should have at least one video source. Otherwise there is no
// point to continue.
if (
sourceStr.mp4 === ' ' &&
sourceStr.webm === ' ' &&
sourceStr.ogg === ' '
) {
return;
}
// Determine the starting and ending time for the video.
this.start = config.playerVars.start;
this.end = config.playerVars.end;
// Create HTML markup for the <video> element, populating it with sources from previous step.
// Because of problems with creating video element via jquery
// (http://bugs.jquery.com/ticket/9174) we create it using native JS.
// Create HTML markup for the <video> element, populating it with
// sources from previous step. Because of problems with creating
// video element via jquery (http://bugs.jquery.com/ticket/9174) we
// create it using native JS.
this.video = document.createElement('video');
this.video.innerHTML = _.values(sourceStr).join('');
// Get the jQuery object, and set the player state to UNSTARTED.
// The player state is used by other parts of the VideoPlayer to detrermine what the video is
// currently doing.
// The player state is used by other parts of the VideoPlayer to
// determine what the video is currently doing.
this.videoEl = $(this.video);
this.playerState = HTML5Video.PlayerState.UNSTARTED;
// Attach a 'click' event on the <video> element. It will cause the video to pause/play.
// Attach a 'click' event on the <video> element. It will cause the
// video to pause/play.
this.videoEl.on('click', function (event) {
if (_this.playerState === HTML5Video.PlayerState.PAUSED) {
_this.playVideo();
_this.playerState = HTML5Video.PlayerState.PLAYING;
_this.callStateChangeCallback();
} else if (_this.playerState === HTML5Video.PlayerState.PLAYING) {
} else if (
_this.playerState === HTML5Video.PlayerState.PLAYING
) {
_this.pauseVideo();
_this.playerState = HTML5Video.PlayerState.PAUSED;
_this.callStateChangeCallback();
}
});
// When the <video> tag has been processed by the browser, and it is ready for playback,
// notify other parts of the VideoPlayer, and initially pause the video.
//
// Also, at this time we can get the real duration of the video. Update the starting end ending
// points of the video. Note that first time, the video will start playing at the specified start time,
// and end playing at the specified end time. After it was paused, or when a seek operation happeded,
// the starting time and ending time will reset to the beginning and the end of the video respectively.
// When the <video> tag has been processed by the browser, and it
// is ready for playback, notify other parts of the VideoPlayer,
// and initially pause the video.
this.video.addEventListener('canplay', function () {
// Because firefox triggers 'canplay' event every time when 'currentTime' property
// changes, we must make sure that this block of code runs only once. Otherwise,
// this will be an endless loop ('currentTime' property is changed below).
// Because Firefox triggers 'canplay' event every time when
// 'currentTime' property changes, we must make sure that this
// block of code runs only once. Otherwise, this will be an
// endless loop ('currentTime' property is changed below).
//
// Chrome is immune to this behavior.
if (_this.playerState !== HTML5Video.PlayerState.UNSTARTED) {
@@ -239,14 +248,6 @@ function () {
_this.playerState = HTML5Video.PlayerState.PAUSED;
if (_this.start > _this.video.duration) {
_this.start = 0;
}
if ((_this.end === null) || (_this.end > _this.video.duration)) {
_this.end = _this.video.duration;
}
_this.video.currentTime = _this.start;
if ($.isFunction(_this.config.events.onReady)) {
_this.config.events.onReady(null);
}
@@ -275,9 +276,10 @@ function () {
}
}());
// The YouTube API presents several constants which describe the player's state at a given moment.
// HTML5Video API will copy these constats so that code which uses both the YouTube API and this API
// doesn't have to change.
// The YouTube API presents several constants which describe the player's
// state at a given moment. HTML5Video API will copy these constants so
// that code which uses both the YouTube API and this API doesn't have to
// change.
HTML5Video.PlayerState = {
UNSTARTED: -1,
ENDED: 0,

View File

@@ -63,9 +63,9 @@ function (HTML5Video, Resizer) {
var youTubeId;
// The function is called just once to apply pre-defined configurations
// by student before video starts playing. Waits until the video's metadata
// is loaded, which normally happens just after the video starts playing.
// Just after that configurations can be applied.
// by student before video starts playing. Waits until the video's
// metadata is loaded, which normally happens just after the video
// starts playing. Just after that configurations can be applied.
state.videoPlayer.ready = _.once(function () {
state.videoPlayer.onSpeedChange(state.speed);
});
@@ -79,6 +79,15 @@ function (HTML5Video, Resizer) {
state.videoPlayer.currentTime = 0;
state.videoPlayer.initialSeekToStartTime = true;
state.videoPlayer.oneTimePauseAtEndTime = true;
// The initial value of the variable `seekToStartTimeOldSpeed`
// should always differ from the value returned by the duration
// function.
state.videoPlayer.seekToStartTimeOldSpeed = 'void';
state.videoPlayer.playerVars = {
controls: 0,
wmode: 'transparent',
@@ -92,9 +101,6 @@ function (HTML5Video, Resizer) {
state.videoPlayer.playerVars.html5 = 1;
}
state.videoPlayer.playerVars.start = state.config.start;
state.videoPlayer.playerVars.end = state.config.end;
// There is a bug which prevents YouTube API to correctly set the speed
// to 1.0 from another speed in Firefox when in HTML5 mode. There is a
// fix which basically reloads the video at speed 1.0 when this change
@@ -196,6 +202,24 @@ function (HTML5Video, Resizer) {
if (isFinite(this.videoPlayer.currentTime)) {
this.videoPlayer.updatePlayTime(this.videoPlayer.currentTime);
// We need to pause the video is current time is smaller (or equal)
// than end time. Also, we must make sure that the end time is the
// one that was set in the configuration parameter. If it differs,
// this means that it was either reset to the end, or the duration
// changed it's value.
//
// In the case of YouTube Flash mode, we must remember that the
// start and end times are rescaled based on the current speed of
// the video.
if (
this.videoPlayer.endTime <= this.videoPlayer.currentTime &&
this.videoPlayer.oneTimePauseAtEndTime
) {
this.videoPlayer.oneTimePauseAtEndTime = false;
this.videoPlayer.pause();
this.videoPlayer.endTime = this.videoPlayer.duration();
}
}
}
@@ -254,27 +278,43 @@ function (HTML5Video, Resizer) {
// It is created on a onPlay event. Cleared on a onPause event.
// Reinitialized on a onSeek event.
function onSeek(params) {
var duration = this.videoPlayer.duration(),
newTime = params.time;
if (
(typeof newTime !== 'number') ||
(newTime > duration) ||
(newTime < 0)
) {
return;
}
this.videoPlayer.log(
'seek_video',
{
old_time: this.videoPlayer.currentTime,
new_time: params.time,
new_time: newTime,
type: params.type
}
);
this.videoPlayer.player.seekTo(params.time, true);
this.videoPlayer.startTime = 0;
this.videoPlayer.endTime = duration;
this.videoPlayer.player.seekTo(newTime, true);
if (this.videoPlayer.isPlaying()) {
clearInterval(this.videoPlayer.updateInterval);
this.videoPlayer.updateInterval = setInterval(
this.videoPlayer.update, 200
);
setTimeout(this.videoPlayer.update, 0);
} else {
this.videoPlayer.currentTime = params.time;
this.videoPlayer.currentTime = newTime;
}
this.videoPlayer.updatePlayTime(params.time);
this.videoPlayer.updatePlayTime(newTime);
}
function onEnded() {
@@ -469,10 +509,85 @@ function (HTML5Video, Resizer) {
}
function updatePlayTime(time) {
var duration;
var duration, durationChange;
duration = this.videoPlayer.duration();
if (
duration > 0 &&
(
this.videoPlayer.seekToStartTimeOldSpeed !== this.speed ||
this.videoPlayer.initialSeekToStartTime
)
) {
if (
this.videoPlayer.seekToStartTimeOldSpeed !== this.speed &&
this.videoPlayer.initialSeekToStartTime === false
) {
durationChange = true;
} else {
durationChange = false;
}
this.videoPlayer.initialSeekToStartTime = false;
this.videoPlayer.seekToStartTimeOldSpeed = this.speed;
// We retrieve the original times. They could have been changed due
// to the fact of speed change (duration change). This happens when
// in YouTube Flash mode. There each speed is a different video,
// with a different length.
this.videoPlayer.startTime = this.config.startTime;
this.videoPlayer.endTime = this.config.endTime;
if (this.videoPlayer.startTime > duration) {
this.videoPlayer.startTime = 0;
} else {
if (this.currentPlayerMode === 'flash') {
this.videoPlayer.startTime /= Number(this.speed);
}
}
if (
this.videoPlayer.endTime === null ||
this.videoPlayer.endTime > duration
) {
this.videoPlayer.endTime = duration;
} else {
if (this.currentPlayerMode === 'flash') {
this.videoPlayer.endTime /= Number(this.speed);
}
}
// If this is not a duration change (if it is, we continue playing
// from current time), then we need to seek the video to the start
// time.
//
// We seek only if start time differs from zero.
if (durationChange === false && this.videoPlayer.startTime > 0) {
if (this.videoType === 'html5') {
this.videoPlayer.player.seekTo(this.videoPlayer.startTime);
} else {
this.videoPlayer.player.loadVideoById({
videoId: this.youtubeId(),
startSeconds: this.videoPlayer.startTime
});
}
}
// Rebuild the slider start-end range (if it doesn't take up the
// whole slider).
if (!(
this.videoPlayer.startTime === 0 &&
this.videoPlayer.endTime === duration
)) {
this.trigger(
'videoProgressSlider.updateStartEndTimeRegion',
{
duration: duration
}
);
}
}
this.trigger(
'videoProgressSlider.updatePlayTime',
{
@@ -481,13 +596,6 @@ function (HTML5Video, Resizer) {
}
);
this.trigger(
'videoProgressSlider.updateStartEndTimeRegion',
{
duration: duration
}
);
this.trigger(
'videoControl.updateVcrVidTime',
{
@@ -506,10 +614,26 @@ function (HTML5Video, Resizer) {
return playerState === PLAYING;
}
/*
* Return the duration of the video in seconds.
*
* First, try to use the native player API call to get the duration.
* If the value returned by the native function is not valid, resort to
* the value stored in the metadata for the video. Note that the metadata
* is available only for YouTube videos.
*
* IMPORTANT! It has been observed that sometimes, after initial playback
* of the video, when operations "pause" and "play" are performed (in that
* sequence), the function will start returning a slightly different value.
*
* For example: While playing for the first time, the function returns 31.
* After pausing the video and then resuming once more, the function will
* start returning 31.950656.
*
* This instability is internal to the player API (or browser internals).
*/
function duration() {
var dur;
dur = this.videoPlayer.player.getDuration();
var dur = this.videoPlayer.player.getDuration();
if (!isFinite(dur)) {
dur = this.getDuration();

View File

@@ -99,7 +99,7 @@ function () {
}
function updateStartEndTimeRegion(params) {
var left, width, start, end;
var left, width, start, end, step;
// We must have a duration in order to determine the area of range.
// It also must be non-zero.
@@ -108,19 +108,28 @@ function () {
}
// If the range spans the entire length of video, we don't do anything.
if (!this.config.start && !this.config.end) {
if (!this.videoPlayer.startTime && !this.videoPlayer.endTime) {
return;
}
start = this.config.start;
start = this.videoPlayer.startTime;
// If end is set to null, then we set it to the end of the video. We
// know that start is not a the beginning, therefore we must build a
// range.
end = this.config.end || params.duration;
end = this.videoPlayer.endTime || params.duration;
left = (100 * (start / params.duration)).toFixed(1);
width = (100 * ((end - start) / params.duration)).toFixed(1);
// Because JavaScript has weird rounding rules when a series of
// mathematical operations are performed in a single statement, we will
// split everything up into smaller statements.
//
// This will ensure that visually, the start-end range aligns nicely
// with actual starting and ending point of the video.
step = 100.0 / params.duration;
left = start * step;
width = end * step - left;
left = left.toFixed(1);
width = width.toFixed(1);
if (!this.videoProgressSlider.sliderRange) {
this.videoProgressSlider.sliderRange = $('<div />', {

View File

@@ -241,9 +241,10 @@ function () {
}
},
error: function (jqXHR, textStatus, errorThrown) {
console.log('ERROR while fetching captions.');
console.log('[Video info]: ERROR while fetching captions.');
console.log(
'STATUS:', textStatus + ', MESSAGE:', '' + errorThrown
'[Video info]: STATUS:', textStatus +
', MESSAGE:', '' + errorThrown
);
_this.videoCaption.hideCaptions(true, false);