Commit adddd75f by polesye

Merge pull request #1564 from edx/anton/fix-video-bugs

Video player: fix start-end time bugs.
parents 64758673 af71c780
...@@ -60,23 +60,23 @@ function (VideoPlayer) { ...@@ -60,23 +60,23 @@ function (VideoPlayer) {
* methods, modules) of the Video player. * methods, modules) of the Video player.
*/ */
function _makeFunctionsPublic(state) { function _makeFunctionsPublic(state) {
state.setSpeed = _.bind(setSpeed, state); var methodsDict = {
state.youtubeId = _.bind(youtubeId, state); bindTo: bindTo,
state.getDuration = _.bind(getDuration, state); checkStartEndTimes: checkStartEndTimes,
state.trigger = _.bind(trigger, state); fetchMetadata: fetchMetadata,
state.stopBuffering = _.bind(stopBuffering, state); getDuration: getDuration,
getVideoMetadata: getVideoMetadata,
// Old private functions. Now also public so that can be initialize: initialize,
// tested by Jasmine. parseSpeed: parseSpeed,
parseVideoSources: parseVideoSources,
state.initialize = _.bind(initialize, state); parseYoutubeStreams: parseYoutubeStreams,
state.parseSpeed = _.bind(parseSpeed, state); setSpeed: setSpeed,
state.fetchMetadata = _.bind(fetchMetadata, state); stopBuffering: stopBuffering,
state.parseYoutubeStreams = _.bind(parseYoutubeStreams, state); trigger: trigger,
state.parseVideoSources = _.bind(parseVideoSources, state); youtubeId: youtubeId
state.getVideoMetadata = _.bind(getVideoMetadata, state); };
state.checkStartEndTimes = _.bind(checkStartEndTimes, state); bindTo(methodsDict, state, state);
} }
// function _renderElements(state) // function _renderElements(state)
...@@ -97,7 +97,7 @@ function (VideoPlayer) { ...@@ -97,7 +97,7 @@ function (VideoPlayer) {
if(state.videoType === 'youtube') { if(state.videoType === 'youtube') {
YT.ready(function() { YT.ready(function() {
VideoPlayer(state); VideoPlayer(state);
}) });
} else { } else {
VideoPlayer(state); VideoPlayer(state);
} }
...@@ -233,6 +233,25 @@ function (VideoPlayer) { ...@@ -233,6 +233,25 @@ function (VideoPlayer) {
// them available and sets up their context is makeFunctionsPublic(). // them available and sets up their context is makeFunctionsPublic().
// *************************************************************** // ***************************************************************
// function bindTo(methodsDict, obj, context, rewrite)
// Creates a new function with specific context and assigns it to the provided
// object.
function bindTo(methodsDict, obj, context, rewrite) {
$.each(methodsDict, function(name, method) {
if (_.isFunction(method)) {
if (_.isUndefined(rewrite)) {
rewrite = true;
}
if (_.isUndefined(obj[name]) || rewrite) {
obj[name] = _.bind(method, context);
}
}
});
}
// function initialize(element) // function initialize(element)
// The function set initial configuration and preparation. // The function set initial configuration and preparation.
......
...@@ -44,14 +44,13 @@ function () { ...@@ -44,14 +44,13 @@ function () {
// Private functions. // Private functions.
function _makeFunctionsPublic(state) { function _makeFunctionsPublic(state) {
state.focusGrabber.enableFocusGrabber = _.bind( var methodsDict = {
enableFocusGrabber, state disableFocusGrabber: disableFocusGrabber,
); enableFocusGrabber: enableFocusGrabber,
state.focusGrabber.disableFocusGrabber = _.bind( onFocus: onFocus
disableFocusGrabber, state };
);
state.bindTo(methodsDict, state.focusGrabber, state);
state.focusGrabber.onFocus = _.bind(onFocus, state);
} }
function _renderElements(state) { function _renderElements(state) {
......
...@@ -24,33 +24,29 @@ function (HTML5Video, Resizer) { ...@@ -24,33 +24,29 @@ function (HTML5Video, Resizer) {
// Functions which will be accessible via 'state' object. When called, // Functions which will be accessible via 'state' object. When called,
// these functions will get the 'state' object as a context. // these functions will get the 'state' object as a context.
function _makeFunctionsPublic(state) { function _makeFunctionsPublic(state) {
state.videoPlayer.pause = _.bind(pause, state); var methodsDict = {
state.videoPlayer.play = _.bind(play, state); duration: duration,
state.videoPlayer.update = _.bind(update, state); handlePlaybackQualityChange: handlePlaybackQualityChange,
state.videoPlayer.onSpeedChange = _.bind(onSpeedChange, state); isPlaying: isPlaying,
state.videoPlayer.onCaptionSeek = _.bind(onSeek, state); log: log,
state.videoPlayer.onSlideSeek = _.bind(onSeek, state); onCaptionSeek: onSeek,
state.videoPlayer.onEnded = _.bind(onEnded, state); onEnded: onEnded,
state.videoPlayer.onPause = _.bind(onPause, state); onPause: onPause,
state.videoPlayer.onPlay = _.bind(onPlay, state); onPlay: onPlay,
onPlaybackQualityChange: onPlaybackQualityChange,
state.videoPlayer.onUnstarted = _.bind(onUnstarted, state); onReady: onReady,
onSlideSeek: onSeek,
state.videoPlayer.handlePlaybackQualityChange = _.bind( onSpeedChange: onSpeedChange,
handlePlaybackQualityChange, state onStateChange: onStateChange,
); onUnstarted: onUnstarted,
onVolumeChange: onVolumeChange,
state.videoPlayer.onPlaybackQualityChange = _.bind( pause: pause,
onPlaybackQualityChange, state play: play,
); update: update,
updatePlayTime: updatePlayTime
};
state.videoPlayer.onStateChange = _.bind(onStateChange, state); state.bindTo(methodsDict, state.videoPlayer, state);
state.videoPlayer.onReady = _.bind(onReady, state);
state.videoPlayer.updatePlayTime = _.bind(updatePlayTime, state);
state.videoPlayer.isPlaying = _.bind(isPlaying, state);
state.videoPlayer.log = _.bind(log, state);
state.videoPlayer.duration = _.bind(duration, state);
state.videoPlayer.onVolumeChange = _.bind(onVolumeChange, state);
} }
// function _initialize(state) // function _initialize(state)
...@@ -67,7 +63,9 @@ function (HTML5Video, Resizer) { ...@@ -67,7 +63,9 @@ function (HTML5Video, Resizer) {
// metadata is loaded, which normally happens just after the video // metadata is loaded, which normally happens just after the video
// starts playing. Just after that configurations can be applied. // starts playing. Just after that configurations can be applied.
state.videoPlayer.ready = _.once(function () { state.videoPlayer.ready = _.once(function () {
if (state.currentPlayerMode !== 'flash') {
state.videoPlayer.onSpeedChange(state.speed); state.videoPlayer.onSpeedChange(state.speed);
}
}); });
if (state.videoType === 'youtube') { if (state.videoType === 'youtube') {
...@@ -224,19 +222,23 @@ function (HTML5Video, Resizer) { ...@@ -224,19 +222,23 @@ function (HTML5Video, Resizer) {
} }
function onSpeedChange(newSpeed, updateCookie) { function onSpeedChange(newSpeed, updateCookie) {
var time = this.videoPlayer.currentTime,
methodName, youtubeId;
if (this.currentPlayerMode === 'flash') { if (this.currentPlayerMode === 'flash') {
this.videoPlayer.currentTime = Time.convert( this.videoPlayer.currentTime = Time.convert(
this.videoPlayer.currentTime, time,
parseFloat(this.speed), parseFloat(this.speed),
newSpeed newSpeed
); );
} }
newSpeed = parseFloat(newSpeed).toFixed(2).replace(/\.00$/, '.0'); newSpeed = parseFloat(newSpeed).toFixed(2).replace(/\.00$/, '.0');
this.videoPlayer.log( this.videoPlayer.log(
'speed_change_video', 'speed_change_video',
{ {
current_time: this.videoPlayer.currentTime, current_time: time,
old_speed: this.speed, old_speed: this.speed,
new_speed: newSpeed new_speed: newSpeed
} }
...@@ -259,17 +261,15 @@ function (HTML5Video, Resizer) { ...@@ -259,17 +261,15 @@ function (HTML5Video, Resizer) {
// speed is 1.0. The second case is necessary to avoid the bug // speed is 1.0. The second case is necessary to avoid the bug
// where in Firefox speed switching to 1.0 in HTML5 player mode is // where in Firefox speed switching to 1.0 in HTML5 player mode is
// handled incorrectly by YouTube API. // handled incorrectly by YouTube API.
methodName = 'cueVideoById';
youtubeId = this.youtubeId();
if (this.videoPlayer.isPlaying()) { if (this.videoPlayer.isPlaying()) {
this.videoPlayer.player.loadVideoById( methodName = 'loadVideoById';
this.youtubeId(), this.videoPlayer.currentTime
);
} else {
this.videoPlayer.player.cueVideoById(
this.youtubeId(), this.videoPlayer.currentTime
);
} }
this.videoPlayer.updatePlayTime(this.videoPlayer.currentTime); this.videoPlayer.player[methodName](youtubeId, time);
this.videoPlayer.updatePlayTime(time);
} }
} }
...@@ -318,11 +318,18 @@ function (HTML5Video, Resizer) { ...@@ -318,11 +318,18 @@ function (HTML5Video, Resizer) {
} }
function onEnded() { function onEnded() {
var time = this.videoPlayer.duration();
this.trigger('videoControl.pause', null); this.trigger('videoControl.pause', null);
if (this.config.show_captions) { if (this.config.show_captions) {
this.trigger('videoCaption.pause', null); this.trigger('videoCaption.pause', null);
} }
// Sometimes `onEnded` events fires when `currentTime` not equal
// `duration`. In this case, slider doesn't reach the end point of
// timeline.
this.videoPlayer.updatePlayTime(time);
} }
function onPause() { function onPause() {
...@@ -355,6 +362,8 @@ function (HTML5Video, Resizer) { ...@@ -355,6 +362,8 @@ function (HTML5Video, Resizer) {
this.videoPlayer.updateInterval = setInterval( this.videoPlayer.updateInterval = setInterval(
this.videoPlayer.update, 200 this.videoPlayer.update, 200
); );
this.videoPlayer.update();
} }
this.trigger('videoControl.play', null); this.trigger('videoControl.play', null);
...@@ -509,9 +518,8 @@ function (HTML5Video, Resizer) { ...@@ -509,9 +518,8 @@ function (HTML5Video, Resizer) {
} }
function updatePlayTime(time) { function updatePlayTime(time) {
var duration, durationChange; var duration = this.videoPlayer.duration(),
durationChange;
duration = this.videoPlayer.duration();
if ( if (
duration > 0 && duration > 0 &&
...@@ -563,14 +571,7 @@ function (HTML5Video, Resizer) { ...@@ -563,14 +571,7 @@ function (HTML5Video, Resizer) {
// //
// We seek only if start time differs from zero. // We seek only if start time differs from zero.
if (durationChange === false && this.videoPlayer.startTime > 0) { if (durationChange === false && this.videoPlayer.startTime > 0) {
if (this.videoType === 'html5') {
this.videoPlayer.player.seekTo(this.videoPlayer.startTime); 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 // Rebuild the slider start-end range (if it doesn't take up the
...@@ -639,7 +640,7 @@ function (HTML5Video, Resizer) { ...@@ -639,7 +640,7 @@ function (HTML5Video, Resizer) {
dur = this.getDuration(); dur = this.getDuration();
} }
return dur; return Math.floor(dur);
} }
function log(eventName, data) { function log(eventName, data) {
......
...@@ -24,14 +24,18 @@ function () { ...@@ -24,14 +24,18 @@ function () {
// Functions which will be accessible via 'state' object. When called, these functions will // Functions which will be accessible via 'state' object. When called, these functions will
// get the 'state' object as a context. // get the 'state' object as a context.
function _makeFunctionsPublic(state) { function _makeFunctionsPublic(state) {
state.videoControl.showControls = _.bind(showControls,state); var methodsDict = {
state.videoControl.hideControls = _.bind(hideControls,state); exitFullScreen: exitFullScreen,
state.videoControl.play = _.bind(play,state); hideControls: hideControls,
state.videoControl.pause = _.bind(pause,state); pause: pause,
state.videoControl.togglePlayback = _.bind(togglePlayback,state); play: play,
state.videoControl.toggleFullScreen = _.bind(toggleFullScreen,state); showControls: showControls,
state.videoControl.exitFullScreen = _.bind(exitFullScreen,state); toggleFullScreen: toggleFullScreen,
state.videoControl.updateVcrVidTime = _.bind(updateVcrVidTime,state); togglePlayback: togglePlayback,
updateVcrVidTime: updateVcrVidTime
};
state.bindTo(methodsDict, state.videoControl, state);
} }
// function _renderElements(state) // function _renderElements(state)
......
...@@ -29,8 +29,12 @@ function () { ...@@ -29,8 +29,12 @@ function () {
// Functions which will be accessible via 'state' object. When called, these functions will // Functions which will be accessible via 'state' object. When called, these functions will
// get the 'state' object as a context. // get the 'state' object as a context.
function _makeFunctionsPublic(state) { function _makeFunctionsPublic(state) {
state.videoQualityControl.onQualityChange = _.bind(onQualityChange, state); var methodsDict = {
state.videoQualityControl.toggleQuality = _.bind(toggleQuality, state); onQualityChange: onQualityChange,
toggleQuality: toggleQuality
};
state.bindTo(methodsDict, state.videoQualityControl, state);
} }
// function _renderElements(state) // function _renderElements(state)
......
...@@ -31,18 +31,15 @@ function () { ...@@ -31,18 +31,15 @@ function () {
// Functions which will be accessible via 'state' object. When called, // Functions which will be accessible via 'state' object. When called,
// these functions will get the 'state' object as a context. // these functions will get the 'state' object as a context.
function _makeFunctionsPublic(state) { function _makeFunctionsPublic(state) {
state.videoProgressSlider.onSlide = _.bind(onSlide, state); var methodsDict = {
state.videoProgressSlider.onStop = _.bind(onStop, state); buildSlider: buildSlider,
state.videoProgressSlider.updatePlayTime = _.bind( onSlide: onSlide,
updatePlayTime, state onStop: onStop,
); updatePlayTime: updatePlayTime,
updateStartEndTimeRegion: updateStartEndTimeRegion
//Added for tests -- JM };
state.videoProgressSlider.buildSlider = _.bind(buildSlider, state);
state.videoProgressSlider.updateStartEndTimeRegion = _.bind( state.bindTo(methodsDict, state.videoProgressSlider, state);
updateStartEndTimeRegion, state
);
} }
// function _renderElements(state) // function _renderElements(state)
...@@ -99,12 +96,14 @@ function () { ...@@ -99,12 +96,14 @@ function () {
} }
function updateStartEndTimeRegion(params) { function updateStartEndTimeRegion(params) {
var left, width, start, end, step; var left, width, start, end, step, duration;
// We must have a duration in order to determine the area of range. // We must have a duration in order to determine the area of range.
// It also must be non-zero. // It also must be non-zero.
if (!params.duration) { if (!params.duration) {
return; return;
} else {
duration = params.duration;
} }
// If the range spans the entire length of video, we don't do anything. // If the range spans the entire length of video, we don't do anything.
...@@ -117,7 +116,7 @@ function () { ...@@ -117,7 +116,7 @@ function () {
// If end is set to null, then we set it to the end of the video. We // 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 // know that start is not a the beginning, therefore we must build a
// range. // range.
end = this.videoPlayer.endTime || params.duration; end = this.videoPlayer.endTime || duration;
// Because JavaScript has weird rounding rules when a series of // Because JavaScript has weird rounding rules when a series of
// mathematical operations are performed in a single statement, we will // mathematical operations are performed in a single statement, we will
...@@ -125,11 +124,9 @@ function () { ...@@ -125,11 +124,9 @@ function () {
// //
// This will ensure that visually, the start-end range aligns nicely // This will ensure that visually, the start-end range aligns nicely
// with actual starting and ending point of the video. // with actual starting and ending point of the video.
step = 100.0 / params.duration; step = 100.0 / duration;
left = start * step; left = start * step;
width = end * step - left; width = end * step - left;
left = left.toFixed(1);
width = width.toFixed(1);
if (!this.videoProgressSlider.sliderRange) { if (!this.videoProgressSlider.sliderRange) {
this.videoProgressSlider.sliderRange = $('<div />', { this.videoProgressSlider.sliderRange = $('<div />', {
......
...@@ -24,8 +24,12 @@ function () { ...@@ -24,8 +24,12 @@ function () {
// Functions which will be accessible via 'state' object. When called, these functions will // Functions which will be accessible via 'state' object. When called, these functions will
// get the 'state' object as a context. // get the 'state' object as a context.
function _makeFunctionsPublic(state) { function _makeFunctionsPublic(state) {
state.videoVolumeControl.onChange = _.bind(onChange, state); var methodsDict = {
state.videoVolumeControl.toggleMute = _.bind(toggleMute, state); onChange: onChange,
toggleMute: toggleMute
};
state.bindTo(methodsDict, state.videoVolumeControl, state);
} }
// function _renderElements(state) // function _renderElements(state)
...@@ -67,14 +71,11 @@ function () { ...@@ -67,14 +71,11 @@ function () {
// Let screen readers know that: // Let screen readers know that:
// This anchor behaves as a button named 'Volume'. // This anchor behaves as a button named 'Volume'.
var buttonStr = gettext( var currentVolume = state.videoVolumeControl.currentVolume,
state.videoVolumeControl.currentVolume === 0 buttonStr = (currentVolume === 0) ? 'Volume muted' : 'Volume';
? 'Volume muted'
: 'Volume'
);
// We add the aria-label attribute because the title attribute cannot be // We add the aria-label attribute because the title attribute cannot be
// read. // read.
state.videoVolumeControl.buttonEl.attr('aria-label', buttonStr); state.videoVolumeControl.buttonEl.attr('aria-label', gettext(buttonStr));
// Let screen readers know that this anchor, representing the slider // Let screen readers know that this anchor, representing the slider
// handle, behaves as a slider named 'volume'. // handle, behaves as a slider named 'volume'.
...@@ -167,8 +168,11 @@ function () { ...@@ -167,8 +168,11 @@ function () {
// *************************************************************** // ***************************************************************
function onChange(event, ui) { function onChange(event, ui) {
this.videoVolumeControl.currentVolume = ui.value; var currentVolume = ui.value,
this.videoVolumeControl.el.toggleClass('muted', this.videoVolumeControl.currentVolume === 0); ariaLabelText = (currentVolume === 0) ? 'Volume muted' : 'Volume';
this.videoVolumeControl.currentVolume = currentVolume;
this.videoVolumeControl.el.toggleClass('muted', currentVolume === 0);
$.cookie('video_player_volume_level', ui.value, { $.cookie('video_player_volume_level', ui.value, {
expires: 3650, expires: 3650,
...@@ -184,9 +188,7 @@ function () { ...@@ -184,9 +188,7 @@ function () {
}); });
this.videoVolumeControl.buttonEl.attr( this.videoVolumeControl.buttonEl.attr(
'aria-label', this.videoVolumeControl.currentVolume === 0 'aria-label', gettext(ariaLabelText)
? gettext('Volume muted')
: gettext('Volume')
); );
} }
......
...@@ -44,11 +44,13 @@ function () { ...@@ -44,11 +44,13 @@ function () {
// Functions which will be accessible via 'state' object. When called, // Functions which will be accessible via 'state' object. When called,
// these functions will get the 'state' object as a context. // these functions will get the 'state' object as a context.
function _makeFunctionsPublic(state) { function _makeFunctionsPublic(state) {
state.videoSpeedControl.changeVideoSpeed = _.bind( var methodsDict = {
changeVideoSpeed, state changeVideoSpeed: changeVideoSpeed,
); reRender: reRender,
state.videoSpeedControl.setSpeed = _.bind(setSpeed, state); setSpeed: setSpeed
state.videoSpeedControl.reRender = _.bind(reRender, state); };
state.bindTo(methodsDict, state.videoSpeedControl, state);
} }
// function _renderElements(state) // function _renderElements(state)
...@@ -229,7 +231,7 @@ function () { ...@@ -229,7 +231,7 @@ function () {
// Track the focus on intermediary speeds. // Track the focus on intermediary speeds.
speedLinks speedLinks
.filter(function (index) { .filter(function (index) {
return index === 1 || index === 2 return index === 1 || index === 2;
}) })
.on('blur', function () { .on('blur', function () {
state.previousFocus = 'otherSpeed'; state.previousFocus = 'otherSpeed';
......
...@@ -37,53 +37,40 @@ function () { ...@@ -37,53 +37,40 @@ function () {
// Functions which will be accessible via 'state' object. When called, // Functions which will be accessible via 'state' object. When called,
// these functions will get the 'state' object as a context. // these functions will get the 'state' object as a context.
function _makeFunctionsPublic(state) { function _makeFunctionsPublic(state) {
state.videoCaption.autoShowCaptions = _.bind( var methodsDict = {
autoShowCaptions, state autoHideCaptions: autoHideCaptions,
); autoShowCaptions: autoShowCaptions,
state.videoCaption.autoHideCaptions = _.bind( bindHandlers: bindHandlers,
autoHideCaptions, state bottomSpacingHeight: bottomSpacingHeight,
); calculateOffset: calculateOffset,
state.videoCaption.resize = _.bind(resize, state); captionBlur: captionBlur,
state.videoCaption.toggle = _.bind(toggle, state); captionClick: captionClick,
state.videoCaption.onMouseEnter = _.bind(onMouseEnter, state); captionFocus: captionFocus,
state.videoCaption.onMouseLeave = _.bind(onMouseLeave, state); captionHeight: captionHeight,
state.videoCaption.onMovement = _.bind(onMovement, state); captionKeyDown: captionKeyDown,
state.videoCaption.renderCaption = _.bind(renderCaption, state); captionMouseDown: captionMouseDown,
state.videoCaption.captionHeight = _.bind(captionHeight, state); captionMouseOverOut: captionMouseOverOut,
state.videoCaption.topSpacingHeight = _.bind( captionURL: captionURL,
topSpacingHeight, state fetchCaption: fetchCaption,
); hideCaptions: hideCaptions,
state.videoCaption.bottomSpacingHeight = _.bind( onMouseEnter: onMouseEnter,
bottomSpacingHeight, state onMouseLeave: onMouseLeave,
); onMovement: onMovement,
state.videoCaption.scrollCaption = _.bind(scrollCaption, state); pause: pause,
state.videoCaption.search = _.bind(search, state); play: play,
state.videoCaption.play = _.bind(play, state); renderCaption: renderCaption,
state.videoCaption.pause = _.bind(pause, state); renderElements: renderElements,
state.videoCaption.seekPlayer = _.bind(seekPlayer, state); resize: resize,
state.videoCaption.hideCaptions = _.bind(hideCaptions, state); scrollCaption: scrollCaption,
state.videoCaption.calculateOffset = _.bind( search: search,
calculateOffset, state seekPlayer: seekPlayer,
); setSubtitlesHeight: setSubtitlesHeight,
state.videoCaption.updatePlayTime = _.bind(updatePlayTime, state); toggle: toggle,
state.videoCaption.setSubtitlesHeight = _.bind( topSpacingHeight: topSpacingHeight,
setSubtitlesHeight, state updatePlayTime: updatePlayTime
); };
state.videoCaption.renderElements = _.bind(renderElements, state); state.bindTo(methodsDict, state.videoCaption, state);
state.videoCaption.bindHandlers = _.bind(bindHandlers, state);
state.videoCaption.fetchCaption = _.bind(fetchCaption, state);
state.videoCaption.captionURL = _.bind(captionURL, state);
state.videoCaption.captionMouseOverOut = _.bind(
captionMouseOverOut, state
);
state.videoCaption.captionMouseDown = _.bind(
captionMouseDown, state
);
state.videoCaption.captionClick = _.bind(captionClick, state);
state.videoCaption.captionFocus = _.bind(captionFocus, state);
state.videoCaption.captionBlur = _.bind(captionBlur, state);
state.videoCaption.captionKeyDown = _.bind(captionKeyDown, state);
} }
// *************************************************************** // ***************************************************************
......
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