diff --git a/lib/media/playhead.js b/lib/media/playhead.js index b5177d5a77..55803bfbb6 100644 --- a/lib/media/playhead.js +++ b/lib/media/playhead.js @@ -80,7 +80,10 @@ shaka.media.Playhead = function( this.buffering_ = false; /** @private {number} */ - this.lastPlaybackRate_ = 0; + this.playbackRate_ = 1; + + /** @private {?number} */ + this.trickPlayIntervalId_ = null; // Check if the video has already loaded some metadata. if (video.readyState > 0) { @@ -89,6 +92,8 @@ shaka.media.Playhead = function( this.eventManager_.listen( video, 'loadedmetadata', this.onLoadedMetadata_.bind(this)); } + + this.eventManager_.listen(video, 'ratechange', this.onRateChange_.bind(this)); }; @@ -97,6 +102,11 @@ shaka.media.Playhead.prototype.destroy = function() { var p = this.eventManager_.destroy(); this.eventManager_ = null; + if (this.trickPlayIntervalId_ != null) { + window.clearInterval(this.trickPlayIntervalId_); + this.trickPlayIntervalId_ = null; + } + this.video_ = null; this.timeline_ = null; this.onBuffering_ = null; @@ -164,21 +174,62 @@ shaka.media.Playhead.prototype.getStartTime_ = function() { * continue. */ shaka.media.Playhead.prototype.setBuffering = function(buffering) { - if (buffering && !this.buffering_) { - this.lastPlaybackRate_ = this.video_.playbackRate; - this.video_.playbackRate = 0; - this.buffering_ = true; - this.onBuffering_(true); - } else if (!buffering && this.buffering_) { - if (this.video_.playbackRate == 0) { - // The app hasn't set a new playback rate, so restore the old one. - this.video_.playbackRate = this.lastPlaybackRate_; - } else { - // There's nothing we could have done to stop the app from setting a new - // rate, so we don't need to do anything here. - } - this.buffering_ = false; - this.onBuffering_(false); + if (buffering != this.buffering_) { + this.buffering_ = buffering; + this.setPlaybackRate(this.playbackRate_); + this.onBuffering_(buffering); + } +}; + + +/** + * Gets the current effective playback rate. This may be negative even if the + * browser does not directly support rewinding. + * @return {number} + */ +shaka.media.Playhead.prototype.getPlaybackRate = function() { + return this.playbackRate_; +}; + + +/** + * Sets the playback rate. + * @param {number} rate + */ +shaka.media.Playhead.prototype.setPlaybackRate = function(rate) { + if (this.trickPlayIntervalId_ != null) { + window.clearInterval(this.trickPlayIntervalId_); + this.trickPlayIntervalId_ = null; + } + + this.playbackRate_ = rate; + // All major browsers support playback rates above zero. Only need fake + // trick play for negative rates. + this.video_.playbackRate = (this.buffering_ || rate < 0) ? 0 : rate; + + if (!this.buffering_ && rate < 0) { + // Defer creating the timer until we stop buffering. This function will be + // called again from setBuffering(). + this.trickPlayIntervalId_ = window.setInterval(function() { + this.video_.currentTime += rate / 4; + }.bind(this), 250); + } +}; + + +/** + * Handles a 'ratechange' event. + * + * @private + */ +shaka.media.Playhead.prototype.onRateChange_ = function() { + // NOTE: This will not allow explicitly setting the playback rate to 0 while + // the playback rate is negative. Pause will still work. + var expectedRate = + this.buffering_ || this.playbackRate_ < 0 ? 0 : this.playbackRate_; + if (this.video_.playbackRate != expectedRate) { + shaka.log.debug('Video playback rate changed to', this.video_.playbackRate); + this.setPlaybackRate(this.video_.playbackRate); } }; diff --git a/lib/player.js b/lib/player.js index 4815ee96ec..b298afff62 100644 --- a/lib/player.js +++ b/lib/player.js @@ -117,9 +117,6 @@ shaka.Player = function(video) { /** @private {?shakaExtern.PlayerConfiguration} */ this.config_ = this.defaultConfig_(); - /** @private {?number} */ - this.trickPlayIntervalId_ = null; - /** @private {!Array.} */ this.switchHistory_ = []; @@ -682,6 +679,18 @@ shaka.Player.prototype.unload = function() { }; +/** + * Gets the current effective playback rate. If using trick play, it will + * return the current trick play rate; otherwise, it will return the video + * playback rate. + * @return {number} + * @export + */ +shaka.Player.prototype.getPlaybackRate = function() { + return this.playhead_ ? this.playhead_.getPlaybackRate() : 0; +}; + + /** * Skip through the content without playing. Simulated using repeated seeks. * @@ -694,15 +703,8 @@ shaka.Player.prototype.unload = function() { * @export */ shaka.Player.prototype.trickPlay = function(rate) { - this.cancelTrickPlay(); - if (rate == 1) { - return; - } - - this.video_.playbackRate = 0; - this.trickPlayIntervalId_ = window.setInterval(function() { - this.video_.currentTime += rate / 4; - }.bind(this), 250); + if (this.playhead_) + this.playhead_.setPlaybackRate(rate); }; @@ -711,11 +713,8 @@ shaka.Player.prototype.trickPlay = function(rate) { * @export */ shaka.Player.prototype.cancelTrickPlay = function() { - this.video_.playbackRate = 1; - if (this.trickPlayIntervalId_ != null) { - window.clearInterval(this.trickPlayIntervalId_); - } - this.trickPlayIntervalId_ = null; + if (this.playhead_) + this.playhead_.setPlaybackRate(1); }; diff --git a/test/playhead_unit.js b/test/playhead_unit.js index 64d6d6a8fe..231528e4d8 100644 --- a/test/playhead_unit.js +++ b/test/playhead_unit.js @@ -26,6 +26,9 @@ describe('Playhead', function() { // Callback to Playhead to simulate 'seeking' event from |video|. var videoOnSeeking; + // Callback to Playhead to simulate 'ratechange' event from |video|. + var videoOnRateChange; + // Callback to us from Playhead when the buffering state changes. var onBuffering; @@ -47,6 +50,8 @@ describe('Playhead', function() { videoOnLoadedMetadata = f; } else if (eventName == 'seeking') { videoOnSeeking = f; + } else if (eventName == 'ratechange') { + videoOnRateChange = f; } else { throw new Error('Unexpected event:' + eventName); } @@ -77,7 +82,7 @@ describe('Playhead', function() { expect(video.addEventListener).toHaveBeenCalledWith( 'loadedmetadata', videoOnLoadedMetadata, false); - expect(video.addEventListener.calls.count()).toBe(1); + expect(video.addEventListener.calls.count()).toBe(2); expect(playhead.getTime()).toBe(5); expect(video.currentTime).toBe(0); @@ -87,7 +92,7 @@ describe('Playhead', function() { expect(video.addEventListener).toHaveBeenCalledWith( 'seeking', videoOnSeeking, false); - expect(video.addEventListener.calls.count()).toBe(2); + expect(video.addEventListener.calls.count()).toBe(3); expect(playhead.getTime()).toBe(5); expect(video.currentTime).toBe(5); @@ -132,6 +137,7 @@ describe('Playhead', function() { // Set to 2 to ensure Playhead restores the correct rate. video.playbackRate = 2; + videoOnRateChange(); playhead.setBuffering(false); expect(onBuffering).not.toHaveBeenCalled();