Commit 074e947e by Valera Rozuvan

Merge pull request #2690 from edx/valera/add_start_time_end_time_functionality

Fix start-time functionality in the video player.
parents 348ce15f f8e8a9a3
...@@ -221,7 +221,7 @@ ...@@ -221,7 +221,7 @@
expect(state.videoPlayer.play).toHaveBeenCalled(); expect(state.videoPlayer.play).toHaveBeenCalled();
}); });
it('when cued, onEnded resets start and end time only the second time', function () { it('even when cued, onEnded does not resets start and end time', function () {
state.videoPlayer.skipOnEndedStartEndReset = true; state.videoPlayer.skipOnEndedStartEndReset = true;
state.videoPlayer.onEnded(); state.videoPlayer.onEnded();
expect(state.videoPlayer.startTime).toBe(10); expect(state.videoPlayer.startTime).toBe(10);
...@@ -229,8 +229,8 @@ ...@@ -229,8 +229,8 @@
state.videoPlayer.skipOnEndedStartEndReset = undefined; state.videoPlayer.skipOnEndedStartEndReset = undefined;
state.videoPlayer.onEnded(); state.videoPlayer.onEnded();
expect(state.videoPlayer.startTime).toBe(0); expect(state.videoPlayer.startTime).toBe(10);
expect(state.videoPlayer.endTime).toBe(null); expect(state.videoPlayer.endTime).toBe(30);
}); });
}); });
......
...@@ -562,6 +562,7 @@ function (VideoPlayer) { ...@@ -562,6 +562,7 @@ function (VideoPlayer) {
runs(function () { runs(function () {
var htmlStr; var htmlStr;
state.videoPlayer.goToStartTime = false;
state.videoPlayer.updatePlayTime(60); state.videoPlayer.updatePlayTime(60);
htmlStr = $('.vidtime').html(); htmlStr = $('.vidtime').html();
...@@ -589,6 +590,7 @@ function (VideoPlayer) { ...@@ -589,6 +590,7 @@ function (VideoPlayer) {
}, 'Video is fully loaded.', WAIT_TIMEOUT); }, 'Video is fully loaded.', WAIT_TIMEOUT);
runs(function () { runs(function () {
state.videoPlayer.goToStartTime = false;
state.videoPlayer.updatePlayTime(60); state.videoPlayer.updatePlayTime(60);
expect(state.videoCaption.updatePlayTime) expect(state.videoCaption.updatePlayTime)
...@@ -606,6 +608,7 @@ function (VideoPlayer) { ...@@ -606,6 +608,7 @@ function (VideoPlayer) {
}, 'Video is fully loaded.', WAIT_TIMEOUT); }, 'Video is fully loaded.', WAIT_TIMEOUT);
runs(function () { runs(function () {
state.videoPlayer.goToStartTime = false;
state.videoPlayer.updatePlayTime(60); state.videoPlayer.updatePlayTime(60);
expect(state.videoProgressSlider.updatePlayTime) expect(state.videoProgressSlider.updatePlayTime)
...@@ -677,35 +680,39 @@ function (VideoPlayer) { ...@@ -677,35 +680,39 @@ function (VideoPlayer) {
describe('updatePlayTime with invalid endTime', function () { describe('updatePlayTime with invalid endTime', function () {
beforeEach(function () { beforeEach(function () {
state = jasmine.initializePlayer( state = {
{ videoPlayer: {
end: 100000 duration: function () {
} // The video will be 60 seconds long.
); return 60;
},
state.videoEl = $('video, iframe'); goToStartTime: true,
startTime: undefined,
spyOn(state.videoPlayer, 'updatePlayTime').andCallThrough(); endTime: undefined,
player: {
seekTo: function () {}
}
},
config: {
savedVideoPosition: 0,
startTime: 0,
// We are setting the end-time to 10000 seconds. The
// video will be less than this, the code will reset
// the end time to `null` - i.e. to the end of the video.
// This is the expected behavior we will test for.
endTime: 10000
},
currentPlayerMode: 'html5',
trigger: function () {},
browserIsFirefox: false
};
}); });
it('invalid endTime is reset to null', function () { it('invalid endTime is reset to null', function () {
var duration; VideoPlayer.prototype.updatePlayTime.call(state, 0);
state.videoPlayer.updatePlayTime.reset(); expect(state.videoPlayer.endTime).toBe(null);
state.videoPlayer.play();
waitsFor(
function () {
return state.videoPlayer.isPlaying() &&
state.videoPlayer.initialSeekToStartTime === false;
},
'updatePlayTime was invoked and duration is set',
WAIT_TIMEOUT
);
runs(function () {
expect(state.videoPlayer.endTime).toBe(null);
});
}); });
}); });
......
...@@ -17,6 +17,7 @@ function (HTML5Video, Resizer) { ...@@ -17,6 +17,7 @@ function (HTML5Video, Resizer) {
methodsDict = { methodsDict = {
duration: duration, duration: duration,
handlePlaybackQualityChange: handlePlaybackQualityChange, handlePlaybackQualityChange: handlePlaybackQualityChange,
isEnded: isEnded,
isPlaying: isPlaying, isPlaying: isPlaying,
log: log, log: log,
onCaptionSeek: onSeek, onCaptionSeek: onSeek,
...@@ -85,12 +86,8 @@ function (HTML5Video, Resizer) { ...@@ -85,12 +86,8 @@ function (HTML5Video, Resizer) {
state.videoPlayer.currentTime = 0; state.videoPlayer.currentTime = 0;
state.videoPlayer.initialSeekToStartTime = true; state.videoPlayer.goToStartTime = true;
state.videoPlayer.stopAtEndTime = true;
// At the start, the initial value of the variable
// `seekToStartTimeOldSpeed` should always differ from the value
// of `state.speed` variable.
state.videoPlayer.seekToStartTimeOldSpeed = 'void';
state.videoPlayer.playerVars = { state.videoPlayer.playerVars = {
controls: 0, controls: 0,
...@@ -287,6 +284,13 @@ function (HTML5Video, Resizer) { ...@@ -287,6 +284,13 @@ function (HTML5Video, Resizer) {
function play() { function play() {
if (this.videoPlayer.player.playVideo) { if (this.videoPlayer.player.playVideo) {
if (this.videoPlayer.isEnded()) {
// When the video will start playing again from the start, the
// start-time and end-time will come back into effect.
this.videoPlayer.goToStartTime = true;
this.videoPlayer.stopAtEndTime = true;
}
this.videoPlayer.player.playVideo(); this.videoPlayer.player.playVideo();
} }
} }
...@@ -301,23 +305,16 @@ function (HTML5Video, Resizer) { ...@@ -301,23 +305,16 @@ function (HTML5Video, Resizer) {
this.videoPlayer.updatePlayTime(this.videoPlayer.currentTime); this.videoPlayer.updatePlayTime(this.videoPlayer.currentTime);
// We need to pause the video if current time is smaller (or equal) // We need to pause the video if current time is smaller (or equal)
// than end time. Also, we must make sure that this is only done // than end-time. Also, we must make sure that this is only done
// once. // once per video playing from start to end.
//
// If `endTime` is not `null`, then we are safe to pause the
// video. `endTime` will be set to `null`, and this if statement
// will not be executed on next runs.
if ( if (
this.videoPlayer.stopAtEndTime &&
this.videoPlayer.endTime !== null && this.videoPlayer.endTime !== null &&
this.videoPlayer.endTime <= this.videoPlayer.currentTime this.videoPlayer.endTime <= this.videoPlayer.currentTime
) { ) {
this.videoPlayer.pause(); this.videoPlayer.stopAtEndTime = false;
// After the first time the video reached the `endTime`, this.videoPlayer.pause();
// `startTime` and `endTime` are disabled. The video will play
// from start to the end on subsequent runs.
this.videoPlayer.startTime = 0;
this.videoPlayer.endTime = null;
this.trigger('videoProgressSlider.notifyThroughHandleEnd', { this.trigger('videoProgressSlider.notifyThroughHandleEnd', {
end: true end: true
...@@ -412,10 +409,12 @@ function (HTML5Video, Resizer) { ...@@ -412,10 +409,12 @@ function (HTML5Video, Resizer) {
} }
); );
// After the user seeks, startTime and endTime are disabled. The video // After the user seeks, the video will start playing from
// will play from start to the end on subsequent runs. // the sought point, and stop playing at the end.
this.videoPlayer.startTime = 0; this.videoPlayer.goToStartTime = false;
this.videoPlayer.endTime = null; if (newTime > this.videoPlayer.endTime || this.videoPlayer.endTime === null) {
this.videoPlayer.stopAtEndTime = false;
}
this.videoPlayer.player.seekTo(newTime, true); this.videoPlayer.player.seekTo(newTime, true);
...@@ -449,13 +448,6 @@ function (HTML5Video, Resizer) { ...@@ -449,13 +448,6 @@ function (HTML5Video, Resizer) {
if (this.videoPlayer.skipOnEndedStartEndReset) { if (this.videoPlayer.skipOnEndedStartEndReset) {
this.videoPlayer.skipOnEndedStartEndReset = undefined; this.videoPlayer.skipOnEndedStartEndReset = undefined;
} else {
// When only `startTime` is set, the video will play to the end
// starting at `startTime`. After the first time the video reaches the
// end, `startTime` and `endTime` are disabled. The video will play
// from start to the end on subsequent runs.
this.videoPlayer.startTime = 0;
this.videoPlayer.endTime = null;
} }
// Sometimes `onEnded` events fires when `currentTime` not equal // Sometimes `onEnded` events fires when `currentTime` not equal
...@@ -654,61 +646,27 @@ function (HTML5Video, Resizer) { ...@@ -654,61 +646,27 @@ function (HTML5Video, Resizer) {
var videoPlayer = this.videoPlayer, var videoPlayer = this.videoPlayer,
duration = this.videoPlayer.duration(), duration = this.videoPlayer.duration(),
savedVideoPosition = this.config.savedVideoPosition, savedVideoPosition = this.config.savedVideoPosition,
isNewSpeed = videoPlayer.seekToStartTimeOldSpeed !== this.speed, youTubeId, startTime, endTime;
durationChange, tempStartTime, tempEndTime, youTubeId,
startTime, endTime;
if (
duration > 0 &&
(isNewSpeed || videoPlayer.initialSeekToStartTime)
) {
if (isNewSpeed && videoPlayer.initialSeekToStartTime === false) {
durationChange = true;
} else { // this.videoPlayer.initialSeekToStartTime === true
videoPlayer.initialSeekToStartTime = false;
durationChange = false; if (duration > 0 && videoPlayer.goToStartTime) {
} videoPlayer.goToStartTime = false;
videoPlayer.seekToStartTimeOldSpeed = this.speed;
// Current startTime and endTime could have already been reset.
// We will remember their current values, and reset them at the
// end. We need to perform the below calculations on start and end
// times so that the range on the slider gets correctly updated in
// the case of speed change in Flash player mode (for YouTube
// videos).
tempStartTime = videoPlayer.startTime;
tempEndTime = videoPlayer.endTime;
// 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.
videoPlayer.startTime = this.config.startTime; videoPlayer.startTime = this.config.startTime;
videoPlayer.endTime = this.config.endTime; if (videoPlayer.startTime >= duration) {
if (videoPlayer.startTime > duration) {
videoPlayer.startTime = 0; videoPlayer.startTime = 0;
} else if (this.currentPlayerMode === 'flash') { } else if (this.currentPlayerMode === 'flash') {
videoPlayer.startTime /= Number(this.speed); videoPlayer.startTime /= Number(this.speed);
} }
// An `endTime` of `null` means that either the user didn't set videoPlayer.endTime = this.config.endTime;
// and `endTime`, or it was set to a value greater than the if (
// duration of the video. videoPlayer.endTime <= videoPlayer.startTime ||
// videoPlayer.endTime >= duration
// If `endTime` is `null`, the video will play to the end. We do ) {
// not set the `endTime` to the duration of the video because videoPlayer.stopAtEndTime = false;
// sometimes in YouTube mode the duration changes slightly during videoPlayer.endTime = null;
// the course of playback. This would cause the video to pause just } else if (this.currentPlayerMode === 'flash') {
// before the actual end of the video. videoPlayer.endTime /= Number(this.speed);
if (videoPlayer.endTime !== null) {
if (videoPlayer.endTime > duration) {
this.videoPlayer.endTime = null;
} else if (this.currentPlayerMode === 'flash') {
this.videoPlayer.endTime /= Number(this.speed);
}
} }
this.trigger( this.trigger(
...@@ -718,39 +676,54 @@ function (HTML5Video, Resizer) { ...@@ -718,39 +676,54 @@ function (HTML5Video, Resizer) {
} }
); );
// If this is not a duration change (if it is, we continue playing startTime = videoPlayer.startTime;
// from current time), then we need to seek the video to the start endTime = videoPlayer.endTime;
// time.
//
// We seek only if start time differs from zero, and we haven't
// performed already such a seek.
if (
durationChange === false &&
(videoPlayer.startTime > 0 || savedVideoPosition !== 0) &&
!(tempStartTime === 0 && tempEndTime === null)
) {
startTime = this.videoPlayer.startTime;
endTime = this.videoPlayer.endTime;
if (startTime) { if (startTime) {
if (startTime < savedVideoPosition && endTime > savedVideoPosition) { if (
time = savedVideoPosition; startTime < savedVideoPosition &&
} else { (endTime > savedVideoPosition || endTime === null) &&
time = startTime;
} // We do not want to jump to the end of the video.
} else { // We subtract 1 from the duration for a 1 second
// safety net.
savedVideoPosition < duration - 1
) {
time = savedVideoPosition; time = savedVideoPosition;
// When the video finishes playing, we will start from the
// start-time, rather than from the remembered position
this.config.savedVideoPosition = 0;
} else {
time = startTime;
} }
} else if (
(endTime > savedVideoPosition || endTime === null) &&
// We do not want to jump to the end of the video.
// We subtract 1 from the duration for a 1 second
// safety net.
savedVideoPosition < duration - 1
) {
time = savedVideoPosition;
// When the video finishes playing, we will start from the
// start-time, rather than from the remembered position
this.config.savedVideoPosition = 0;
} else {
time = 0;
}
if (time > 0) {
// After a bug came up (BLD-708: "In Firefox YouTube video with // After a bug came up (BLD-708: "In Firefox YouTube video with
// start time plays from 00:00:00") the video refused to play // start-time plays from 00:00:00") the video refused to play
// from start time, and only played from the beginning. // from start-time, and only played from the beginning.
// //
// It turned out that for some reason if Firefox you couldn't // It turned out that for some reason if Firefox you couldn't
// seek beyond some amount of time before the video loaded. // seek beyond some amount of time before the video loaded.
// Very strange, but in Chrome there is no such bug. // Very strange, but in Chrome there is no such bug.
// //
// HTML5 video sources play fine from start time in both Chrome // HTML5 video sources play fine from start-time in both Chrome
// and Firefox. // and Firefox.
if (this.browserIsFirefox && this.videoType === 'youtube') { if (this.browserIsFirefox && this.videoType === 'youtube') {
if (this.currentPlayerMode === 'flash') { if (this.currentPlayerMode === 'flash') {
...@@ -761,29 +734,20 @@ function (HTML5Video, Resizer) { ...@@ -761,29 +734,20 @@ function (HTML5Video, Resizer) {
// When we will call cueVideoById() for some strange reason // When we will call cueVideoById() for some strange reason
// an ENDED event will be fired. It really does no damage // an ENDED event will be fired. It really does no damage
// except for the fact that the end time is reset to null. // except for the fact that the end-time is reset to null.
// We do not want this. // We do not want this.
// //
// The flag `skipOnEndedStartEndReset` will notify the // The flag `skipOnEndedStartEndReset` will notify the
// onEnded() callback for the ENDED event that just this // onEnded() callback for the ENDED event that there
// once there is no need in resetting the start and end // is no need in resetting the start-time and end-time.
// times videoPlayer.skipOnEndedStartEndReset = true;
this.videoPlayer.skipOnEndedStartEndReset = true;
this.videoPlayer.seekToTimeOnCued = time; videoPlayer.seekToTimeOnCued = time;
this.videoPlayer.player.cueVideoById(youTubeId, time); videoPlayer.player.cueVideoById(youTubeId, time);
} else { } else {
this.videoPlayer.player.seekTo(time); videoPlayer.player.seekTo(time);
} }
} }
// Reset back the actual startTime and endTime if they have been
// already reset (a seek event happened, the video already ended
// once, or endTime has already been reached once).
if (tempStartTime === 0 && tempEndTime === null) {
videoPlayer.startTime = 0;
videoPlayer.endTime = null;
}
} }
this.trigger( this.trigger(
...@@ -805,6 +769,13 @@ function (HTML5Video, Resizer) { ...@@ -805,6 +769,13 @@ function (HTML5Video, Resizer) {
this.trigger('videoCaption.updatePlayTime', time); this.trigger('videoCaption.updatePlayTime', time);
} }
function isEnded() {
var playerState = this.videoPlayer.player.getPlayerState(),
ENDED = this.videoPlayer.PlayerState.ENDED;
return playerState === ENDED;
}
function isPlaying() { function isPlaying() {
var playerState = this.videoPlayer.player.getPlayerState(), var playerState = this.videoPlayer.player.getPlayerState(),
PLAYING = this.videoPlayer.PlayerState.PLAYING; PLAYING = this.videoPlayer.PlayerState.PLAYING;
...@@ -836,7 +807,7 @@ function (HTML5Video, Resizer) { ...@@ -836,7 +807,7 @@ function (HTML5Video, Resizer) {
// Sometimes the YouTube API doesn't finish instantiating all of it's // Sometimes the YouTube API doesn't finish instantiating all of it's
// methods, but the execution point arrives here. // methods, but the execution point arrives here.
// //
// This happens when you have start and end times set, and click "Edit" // This happens when you have start-time and end-time set, and click "Edit"
// in Studio, and then "Save". The Video editor dialog closes, the // in Studio, and then "Save". The Video editor dialog closes, the
// video reloads, but the start-end range is not visible. // video reloads, but the start-end range is not visible.
if (this.videoPlayer.player.getDuration) { if (this.videoPlayer.player.getDuration) {
......
...@@ -101,7 +101,7 @@ function () { ...@@ -101,7 +101,7 @@ function () {
} }
// Rebuild the slider start-end range (if it doesn't take up the // Rebuild the slider start-end range (if it doesn't take up the
// whole slider). Remember that endTime === null means the end time // whole slider). Remember that endTime === null means the end-time
// is set to the end of video by default. // is set to the end of video by default.
function updateStartEndTimeRegion(params) { function updateStartEndTimeRegion(params) {
var isFlashMode = this.currentPlayerMode === 'flash', var isFlashMode = this.currentPlayerMode === 'flash',
...@@ -180,6 +180,10 @@ function () { ...@@ -180,6 +180,10 @@ function () {
function onSlide(event, ui) { function onSlide(event, ui) {
this.videoProgressSlider.frozen = true; this.videoProgressSlider.frozen = true;
// Remember the seek to value so that we don't repeat ourselves on the
// 'stop' slider event.
this.videoProgressSlider.lastSeekValue = ui.value;
this.trigger( this.trigger(
'videoPlayer.onSlideSeek', 'videoPlayer.onSlideSeek',
{'type': 'onSlideSeek', 'time': ui.value} {'type': 'onSlideSeek', 'time': ui.value}
...@@ -196,10 +200,16 @@ function () { ...@@ -196,10 +200,16 @@ function () {
this.videoProgressSlider.frozen = true; this.videoProgressSlider.frozen = true;
this.trigger( // Only perform a seek if we haven't made a seek for the new slider value.
'videoPlayer.onSlideSeek', // This is necessary so that if the user only clicks on the slider, without
{'type': 'onSlideSeek', 'time': ui.value} // dragging it, then only one seek is made, even when a 'slide' and a 'stop'
); // events are triggered on the slider.
if (this.videoProgressSlider.lastSeekValue !== ui.value) {
this.trigger(
'videoPlayer.onSlideSeek',
{'type': 'onSlideSeek', 'time': ui.value}
);
}
// ARIA // ARIA
this.videoProgressSlider.handle.attr( this.videoProgressSlider.handle.attr(
...@@ -216,8 +226,8 @@ function () { ...@@ -216,8 +226,8 @@ function () {
duration = Math.floor(params.duration); duration = Math.floor(params.duration);
if ( if (
(this.videoProgressSlider.slider) && this.videoProgressSlider.slider &&
(!this.videoProgressSlider.frozen) !this.videoProgressSlider.frozen
) { ) {
this.videoProgressSlider.slider this.videoProgressSlider.slider
.slider('option', 'max', duration) .slider('option', 'max', duration)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment