Skip to content

Commit

Permalink
Move Trick Play Out of Video Wrapper
Browse files Browse the repository at this point in the history
This change moves the trick play logic out of the video wrapper so that
it will be more available to the src= code. By doing this, I hope that
we can make it clearer how we are working with the playback rate and
ensure a tighter integration with it.

Issue #816
Issue #997

Change-Id: Id462cda2c5eb82c3713237341424b91891bd38ea
  • Loading branch information
vaage committed Apr 11, 2019
1 parent d8abdb0 commit 40212ec
Show file tree
Hide file tree
Showing 7 changed files with 445 additions and 252 deletions.
1 change: 1 addition & 0 deletions build/types/core
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
+../../lib/media/media_source_engine.js
+../../lib/media/mp4_segment_index_parser.js
+../../lib/media/period_observer.js
+../../lib/media/play_rate_controller.js
+../../lib/media/playhead.js
+../../lib/media/playhead_observer.js
+../../lib/media/presentation_timeline.js
Expand Down
208 changes: 208 additions & 0 deletions lib/media/play_rate_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
/**
* @license
* Copyright 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

goog.provide('shaka.media.PlayRateController');

goog.require('shaka.util.IReleasable');
goog.require('shaka.util.Timer');

/**
* The play rate controller controls the playback rate on the media element.
* This provides some missing functionality (e.g. negative playback rate). If
* the playback rate on the media element can change outside of the controller,
* the playback controller will need to be updated to stay in-sync.
*
* @implements {shaka.util.IReleasable}
* @final
*/
shaka.media.PlayRateController = class {
/**
* @param {shaka.media.PlayRateController.Harness} harness
*/
constructor(harness) {
/** @private {?shaka.media.PlayRateController.Harness} */
this.harness_ = harness;

/** @private {!Set.<shaka.media.PlayRateController.Modifier>} */
this.modifiers_ = new Set();

/** @private {number} */
this.rate_ = this.harness_.getRate();

/** @private {number} */
this.pollRate_ = 0.25;

/** @private {shaka.util.Timer} */
this.timer_ = new shaka.util.Timer(() => {
this.harness_.movePlayhead(this.rate_ * this.pollRate_);
});
}

/** @override */
release() {
if (this.timer_) {
this.timer_.stop();
this.timer_ = null;
}

this.harness_ = null;
}

/**
* Add a modifier to the controller. If the modifier has already been applied,
* this will be a no-op. Calling |removeModifier| will remove all instances of
* this modifier from the controller.
*
* @param {shaka.media.PlayRateController.Modifier} modifier
*/
addModifier(modifier) {
this.modifiers_.add(modifier);
this.apply_();
}

/**
* Remove |modifier| from the controller. If the modifier was never applied,
* this will be a no-op. If the modifier was applied multiple times, this will
* remove all instances of it.
*
* @param {shaka.media.PlayRateController.Modifier} modifier
*/
removeModifier(modifier) {
this.modifiers_.delete(modifier);
this.apply_();
}

/**
* Set the playback rate. This rate will only be used as provided when there
* are not modifiers. If the rate needs to be set to zero, it is suggested
* that you use the ZERO_RATE modifier instead.
*
* @param {number} rate
*/
set(rate) {
this.rate_ = rate;
this.apply_();
}

/**
* Get the rate that the user will experience. This means that if we are using
* trick play, this will report the trick play rate. If we are buffering, this
* will report zero. If playback is occurring as normal, this will report 1.
*
* @return {number}
*/
getActiveRate() {
return this.calculateCurrentRate_();
}

/**
* Reapply the effects of |this.rate_| and |this.active_| to the media
* element. This will only update the rate via the harness if the desired rate
* has changed.
*
* @private
*/
apply_() {
// Always stop the timer. We may not start it again.
this.timer_.stop();

/** @type {number} */
const rate = this.calculateCurrentRate_();

if (rate >= 0) {
this.applyRate_(rate);
return;
}

// When moving backwards, set the playback rate to 0 so that we can manually
// seek backwards with out fighting the playhead.
this.timer_.tickEvery(this.pollRate_);
this.applyRate_(0);
}

/**
* Calculate the rate that the controller wants the media element to have
* based on the current state of the controller.
*
* @return {number}
* @private
*/
calculateCurrentRate_() {
// We assume that all modifiers affect the play rate in the same way,
// setting it to zero. So if there are any modifiers, we should use a rate
// equal to zero.
return this.modifiers_.size ? 0 : this.rate_;
}

/**
* If the new rate is different than the media element's playback rate, this
* will change the playback rate. If the rate does not need to change, it will
* not be set. This will avoid unnecessary ratechange events.
*
* @param {number} newRate
* @return {boolean}
* @private
*/
applyRate_(newRate) {
const oldRate = this.harness_.getRate();

if (oldRate != newRate) {
this.harness_.setRate(newRate);
}

return oldRate != newRate;
}
};


/**
* Modifiers are differ types of modifications that can be applied to the play
* rate controller than will affect what playback rate is used. Right now all
* modifiers will override the playback rate to 0.
*
* @enum {number}
*/
shaka.media.PlayRateController.Modifier = {
ZERO_RATE: 0,
BUFFERING: 1,
};


/**
* @typedef {{
* getRate: function():number,
* setRate: function(number),
* movePlayhead: function(number)
* }}
*
* @description
* A layer of abstraction between the controller and what it is controlling.
* In tests this will be implemented with spies. In production this will be
* implemented using a media element.
*
* @property {function():number} getRate
* Get the current playback rate being seen by the user.
*
* @property {function(number)} setRate
* Set the playback rate that the user should see.
*
* @property {function(number)} movePlayhead
* Move the playhead N seconds. If N is positive, the playhead will move
* forward abs(N) seconds. If N is negative, the playhead will move backwards
* abs(N) seconds.
*/
shaka.media.PlayRateController.Harness;
85 changes: 0 additions & 85 deletions lib/media/playhead.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,30 +55,6 @@ shaka.media.Playhead = class {
*/
getTime() {}

/**
* Mark the playhead as buffering or not buffering.
*
* @param {boolean} isBuffering
*/
setBuffering(isBuffering) {}

/**
* Get the playhead's playback rate.
*
* @return {number}
*/
getPlaybackRate() {}

/**
* Set the playhead's playback rate. Setting to values greater than 1 will
* result in faster than normal playback. Setting to values less than 1 will
* return in slower than normal playback. Setting to 0 will stop playback.
* Setting to valids less than 0 is not allowed.
*
* @param {number} rate
*/
setPlaybackRate(rate) {}

/**
* Notify the playhead that the buffered ranges have changed.
*/
Expand All @@ -103,10 +79,6 @@ shaka.media.SrcEqualsPlayhead = class {
this.started_ = false;
/** @private {?number} */
this.startTime_ = null;
/** @private {number} */
this.playbackRate_ = 1;
/** @private {boolean} */
this.isBuffering_ = false;

/** @private {shaka.util.EventManager} */
this.eventManager_ = new shaka.util.EventManager();
Expand Down Expand Up @@ -157,50 +129,8 @@ shaka.media.SrcEqualsPlayhead = class {
return time || 0;
}

/** @override */
setBuffering(isBuffering) {
this.updateBufferingAndPlaybackRate_(isBuffering, null);
}

/** @override */
getPlaybackRate() {
// Always report our cached playback rate since it is what we should be
// playing at. If we are buffering, the media element's playback rate will
// be 0, but we are still planning to play at our cached playback rate.
return this.playbackRate_;
}

/** @override */
setPlaybackRate(rate) {
this.updateBufferingAndPlaybackRate_(null, rate);
}

/** @override */
notifyOfBufferingChange() {}

/**
* Update the internal state by setting the buffering and playback rate. When
* a value is set as |null|, the old value will be used. This will update the
* playback rate on the media element.
*
* @param {?boolean} buffering
* @param {?number} rate
*/
updateBufferingAndPlaybackRate_(buffering, rate) {
if (buffering != null) {
this.isBuffering_ = buffering;
}

if (rate != null) {
this.playbackRate_ = rate;
}

// If we are buffering, we want playback rate to be 0, but once we stop
// buffering we want to reset the playback rate to what it was previously.
this.mediaElement_.playbackRate = this.isBuffering_ ?
0 :
this.playbackRate_;
}
};


Expand Down Expand Up @@ -358,21 +288,6 @@ shaka.media.MediaSourcePlayhead = class {
return this.clampSeekToDuration_(this.clampTime_(startTime));
}

/** @override */
setBuffering(buffering) {
this.videoWrapper_.setBuffering(buffering);
}

/** @override */
getPlaybackRate() {
return this.videoWrapper_.getPlaybackRate();
}

/** @override */
setPlaybackRate(rate) {
this.videoWrapper_.setPlaybackRate(rate);
}

/** @override */
notifyOfBufferingChange() {
this.gapController_.onSegmentAppended();
Expand Down
Loading

0 comments on commit 40212ec

Please sign in to comment.