diff --git a/common/lib/xmodule/xmodule/js/src/video/03_video_player.js b/common/lib/xmodule/xmodule/js/src/video/03_video_player.js index c5dab0af85..03c319fd71 100644 --- a/common/lib/xmodule/xmodule/js/src/video/03_video_player.js +++ b/common/lib/xmodule/xmodule/js/src/video/03_video_player.js @@ -3,7 +3,7 @@ // VideoPlayer module. define( 'video/03_video_player.js', -['video/02_html5_video.js', 'video/00_resizer.js' ], +['video/02_html5_video.js', 'video/00_resizer.js'], function (HTML5Video, Resizer) { var dfd = $.Deferred(); @@ -83,8 +83,6 @@ function (HTML5Video, Resizer) { 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. @@ -215,8 +213,7 @@ function (HTML5Video, Resizer) { // This function gets the video's current play position in time // (currentTime) and its duration. - // It is called at a regular interval when the video is playing (see - // below). + // It is called at a regular interval when the video is playing. function update() { this.videoPlayer.currentTime = this.videoPlayer.player .getCurrentTime(); @@ -224,22 +221,28 @@ 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. + // 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 + // once. // - // 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 `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 ( - this.videoPlayer.endTime <= this.videoPlayer.currentTime && - this.videoPlayer.oneTimePauseAtEndTime + this.videoPlayer.endTime != null && + this.videoPlayer.endTime <= this.videoPlayer.currentTime ) { - this.videoPlayer.oneTimePauseAtEndTime = false; this.videoPlayer.pause(); - this.videoPlayer.endTime = this.videoPlayer.duration(); + + // After the first time the video reached the `endTime`, + // `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', { + end: true + }); } } } @@ -321,8 +324,10 @@ function (HTML5Video, Resizer) { } ); + // After the user seeks, startTime and endTime are disabled. The video + // will play from start to the end on subsequent runs. this.videoPlayer.startTime = 0; - this.videoPlayer.endTime = duration; + this.videoPlayer.endTime = null; this.videoPlayer.player.seekTo(newTime, true); @@ -344,11 +349,21 @@ function (HTML5Video, Resizer) { var time = this.videoPlayer.duration(); this.trigger('videoControl.pause', null); + this.trigger('videoProgressSlider.notifyThroughHandleEnd', { + end: true + }); if (this.config.show_captions) { this.trigger('videoCaption.pause', null); } + // 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 // `duration`. In this case, slider doesn't reach the end point of // timeline. @@ -391,6 +406,10 @@ function (HTML5Video, Resizer) { this.trigger('videoControl.play', null); + this.trigger('videoProgressSlider.notifyThroughHandleEnd', { + end: false + }); + if (this.config.show_captions) { this.trigger('videoCaption.play', null); } @@ -531,7 +550,7 @@ function (HTML5Video, Resizer) { function updatePlayTime(time) { var duration = this.videoPlayer.duration(), - durationChange; + durationChange, tempStartTime, tempEndTime; if ( duration > 0 && @@ -545,13 +564,23 @@ function (HTML5Video, Resizer) { this.videoPlayer.initialSeekToStartTime === false ) { durationChange = true; - } else { + } else { // this.videoPlayer.initialSeekToStartTime === true + this.videoPlayer.initialSeekToStartTime = false; + durationChange = false; } - this.videoPlayer.initialSeekToStartTime = false; this.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 = this.videoPlayer.startTime; + tempEndTime = this.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, @@ -566,12 +595,22 @@ function (HTML5Video, Resizer) { this.videoPlayer.startTime /= Number(this.speed); } } + + // An `endTime` of `null` means that either the user didn't set + // and `endTime`, or it was set to a value greater than the + // duration of the video. + // + // If `endTime` is `null`, the video will play to the end. We do + // not set the `endTime` to the duration of the video because + // sometimes in YouTube mode the duration changes slightly during + // the course of playback. This would cause the video to pause just + // before the actual end of the video. if ( - this.videoPlayer.endTime === null || + this.videoPlayer.endTime !== null && this.videoPlayer.endTime > duration ) { - this.videoPlayer.endTime = duration; - } else { + this.videoPlayer.endTime = null; + } else if (this.videoPlayer.endTime !== null) { if (this.currentPlayerMode === 'flash') { this.videoPlayer.endTime /= Number(this.speed); } @@ -581,16 +620,22 @@ function (HTML5Video, Resizer) { // 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) { + // We seek only if start time differs from zero, and we haven't + // performed already such a seek. + if ( + durationChange === false && + this.videoPlayer.startTime > 0 && + !(tempStartTime === 0 && tempEndTime === null) + ) { this.videoPlayer.player.seekTo(this.videoPlayer.startTime); } // Rebuild the slider start-end range (if it doesn't take up the - // whole slider). + // whole slider). Remember that endTime === null means the end time + // is set to the end of video by default. if (!( this.videoPlayer.startTime === 0 && - this.videoPlayer.endTime === duration + this.videoPlayer.endTime === null )) { this.trigger( 'videoProgressSlider.updateStartEndTimeRegion', @@ -599,6 +644,14 @@ function (HTML5Video, Resizer) { } ); } + + // 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) { + this.videoPlayer.startTime = 0; + this.videoPlayer.endTime = null; + } } this.trigger( diff --git a/common/lib/xmodule/xmodule/js/src/video/06_video_progress_slider.js b/common/lib/xmodule/xmodule/js/src/video/06_video_progress_slider.js index 59750a0f11..6a3deff974 100644 --- a/common/lib/xmodule/xmodule/js/src/video/06_video_progress_slider.js +++ b/common/lib/xmodule/xmodule/js/src/video/06_video_progress_slider.js @@ -41,7 +41,8 @@ function () { onSlide: onSlide, onStop: onStop, updatePlayTime: updatePlayTime, - updateStartEndTimeRegion: updateStartEndTimeRegion + updateStartEndTimeRegion: updateStartEndTimeRegion, + notifyThroughHandleEnd: notifyThroughHandleEnd }; state.bindTo(methodsDict, state.videoProgressSlider, state); @@ -111,11 +112,6 @@ function () { duration = params.duration; } - // If the range spans the entire length of video, we don't do anything. - if (!this.videoPlayer.startTime && !this.videoPlayer.endTime) { - return; - } - start = this.videoPlayer.startTime; // If end is set to null, then we set it to the end of the video. We @@ -199,8 +195,6 @@ function () { }, 200); } - // Changed for tests -- JM: Check if it is the cause of Chrome Bug Valera - // noticed function updatePlayTime(params) { var time = Math.floor(params.time), duration = Math.floor(params.duration); @@ -215,6 +209,32 @@ function () { } } + // When the video stops playing (either because the end was reached, or + // because endTime was reached), the screen reader must be notified that + // the video is no longer playing. We do this by a little trick. Setting + // the title attribute of the slider know to "video ended", and focusing + // on it. The screen reader will read the attr text. + // + // The user can then tab his way forward, landing on the next control + // element, the Play button. + // + // @param params - object with property `end`. If set to true, the + // function must set the title attribute to + // `video ended`; + // if set to false, the function must reset the attr to + // it's original state. + // + // This function will be triggered from VideoPlayer methods onEnded(), + // onPlay(), and update() (update method handles endTime). + function notifyThroughHandleEnd(params) { + if (params.end) { + this.videoProgressSlider.handle.attr('title', 'video ended'); + this.videoProgressSlider.handle.focus(); + } else { + this.videoProgressSlider.handle.attr('title', 'video position'); + } + } + // Returns a string describing the current time of video in hh:mm:ss // format. function getTimeDescription(time) {