Merge pull request #1480 from edx/valera/bugfix_start_end_time_correct_navigation
Bug fix: video end time proper seek beyond.
This commit is contained in:
@@ -5,6 +5,10 @@ These are notable changes in edx-platform. This is a rolling list of changes,
|
||||
in roughly chronological order, most recent first. Add your entries at or near
|
||||
the top. Include a label indicating the component affected.
|
||||
|
||||
Blades: Video start and end times now function the same for both YouTube and
|
||||
HTML5 videos. If end time is set, the video can still play until the end, after
|
||||
it pauses on the end time.
|
||||
|
||||
Blades: Disallow users to enter video url's in http.
|
||||
|
||||
Blades: Fix bug when the speed can only be changed when the video is playing.
|
||||
@@ -48,7 +52,7 @@ on the request instead of overwriting the POST attr
|
||||
|
||||
---------- split mongo backend refactoring changelog section ------------
|
||||
|
||||
Studio: course catalog, assets, checklists, course outline pages now use course
|
||||
Studio: course catalog, assets, checklists, course outline pages now use course
|
||||
id syntax w/ restful api style
|
||||
|
||||
Common:
|
||||
@@ -57,7 +61,7 @@ Common:
|
||||
|
||||
Common: location mapper: % encode periods and dollar signs when used as key in the mapping dict
|
||||
|
||||
Common: location mapper: added a bunch of new helper functions for generating
|
||||
Common: location mapper: added a bunch of new helper functions for generating
|
||||
old location style info from a CourseLocator
|
||||
|
||||
Common: locators: allow - ~ and . in course, branch, and block ids.
|
||||
|
||||
@@ -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: '/'
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 />', {
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user