-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Timothy Drews
committed
Jan 15, 2016
1 parent
736fe9e
commit a4ff271
Showing
10 changed files
with
2,955 additions
and
85 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,226 @@ | ||
/** | ||
* @license | ||
* Copyright 2015 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.Playhead'); | ||
|
||
goog.require('shaka.asserts'); | ||
goog.require('shaka.media.PresentationTimeline'); | ||
goog.require('shaka.media.TimeRangesUtils'); | ||
goog.require('shaka.util.EventManager'); | ||
goog.require('shaka.util.IDestroyable'); | ||
|
||
|
||
|
||
/** | ||
* Creates a Playhead, which manages the video's current time. | ||
* | ||
* The Playhead provides mechanisms for setting the presentation's start time, | ||
* restricting seeking to valid time ranges, and stopping playback for startup | ||
* and re- buffering. | ||
* | ||
* @param {!HTMLVideoElement} video | ||
* @param {!shaka.media.PresentationTimeline} timeline | ||
* @param {number} minBufferTime | ||
* @param {number} startTime The time, in seconds, to start the presentation. | ||
* This time should be within the presentation timeline. | ||
* @param {function(boolean)} onBuffering Called and passed true when stopped | ||
* for buffering; called and passed false when proceeding after buffering. | ||
* @param {function()} onSeek Called when the user agent seeks to a time within | ||
* the presentation timeline. | ||
* | ||
* @constructor | ||
* @struct | ||
* @implements {shaka.util.IDestroyable} | ||
*/ | ||
shaka.media.Playhead = function( | ||
video, timeline, minBufferTime, startTime, onBuffering, onSeek) { | ||
/** @private {HTMLVideoElement} */ | ||
this.video_ = video; | ||
|
||
/** @private {shaka.media.PresentationTimeline} */ | ||
this.timeline_ = timeline; | ||
|
||
/** @private {number} */ | ||
this.minBufferTime_ = minBufferTime; | ||
|
||
/** | ||
* The presentation time, in seconds, at which to begin playback. | ||
* @private {number} | ||
*/ | ||
this.startTime_ = startTime; | ||
|
||
/** @private {?function(boolean)} */ | ||
this.onBuffering_ = onBuffering; | ||
|
||
/** @private {?function()} */ | ||
this.onSeek_ = onSeek; | ||
|
||
/** @private {shaka.util.EventManager} */ | ||
this.eventManager_ = new shaka.util.EventManager(); | ||
|
||
/** @private {boolean} */ | ||
this.buffering_ = false; | ||
|
||
/** @private {number} */ | ||
this.lastPlaybackRate_ = 0; | ||
|
||
// Check if the video has already loaded some metadata. | ||
if (video.readyState > 0) { | ||
this.onLoadedMetadata_(); | ||
} else { | ||
this.eventManager_.listen( | ||
video, 'loadedmetadata', this.onLoadedMetadata_.bind(this)); | ||
} | ||
}; | ||
|
||
|
||
/** @override */ | ||
shaka.media.Playhead.prototype.destroy = function() { | ||
var p = this.eventManager_.destroy(); | ||
this.eventManager_ = null; | ||
|
||
this.video_ = null; | ||
this.timeline_ = null; | ||
this.onBuffering_ = null; | ||
this.onSeek_ = null; | ||
|
||
return p; | ||
}; | ||
|
||
|
||
/** | ||
* Gets the playhead's current (logical) position. | ||
* | ||
* @return {number} | ||
*/ | ||
shaka.media.Playhead.prototype.getTime = function() { | ||
var time = this.video_.readyState > 0 ? | ||
this.video_.currentTime : | ||
this.startTime_; | ||
// Although we restrict the video's currentTime elsewhere, clamp it here to | ||
// ensure any timing issues (e.g., the user agent seeks and calls this | ||
// function before we receive the 'seeking' event) don't cause us to return a | ||
// time outside the segment availability window. | ||
return this.clampTime_(time); | ||
}; | ||
|
||
|
||
/** | ||
* Stops the playhead for buffering, or resumes the playhead after buffering. | ||
* | ||
* @param {boolean} buffering True to stop the playhead; false to allow it to | ||
* 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); | ||
} | ||
}; | ||
|
||
|
||
/** | ||
* Handles a 'loadedmetadata' event. | ||
* | ||
* @private | ||
*/ | ||
shaka.media.Playhead.prototype.onLoadedMetadata_ = function() { | ||
var video = /** @type {!HTMLVideoElement} */(this.video_); | ||
this.eventManager_.unlisten(video, 'loadedmetadata'); | ||
this.eventManager_.listen(video, 'seeking', this.onSeeking_.bind(this)); | ||
|
||
// Trigger call to onSeeking_(). | ||
this.video_.currentTime = this.clampTime_(this.startTime_); | ||
}; | ||
|
||
|
||
/** | ||
* Handles a 'seeking' event. | ||
* | ||
* @private | ||
*/ | ||
shaka.media.Playhead.prototype.onSeeking_ = function() { | ||
shaka.asserts.assert(this.video_.readyState > 0, | ||
'readyState should be greater than 0'); | ||
|
||
var currentTime = this.video_.currentTime; | ||
var targetTime = currentTime; | ||
|
||
var d = this.timeline_.getSegmentAvailabilityDuration(); | ||
var live = (d != null) && (d < Number.POSITIVE_INFINITY); | ||
|
||
var start = this.timeline_.getSegmentAvailabilityStart(); | ||
var end = this.timeline_.getSegmentAvailabilityEnd(); | ||
|
||
if (!live && (currentTime < start)) { | ||
targetTime = start; | ||
} else if (live && (currentTime < start + this.minBufferTime_)) { | ||
targetTime = Math.max(currentTime, start); | ||
var bufferedAhead = shaka.media.TimeRangesUtils.bufferedAheadOf( | ||
this.video_.buffered, targetTime); | ||
if (bufferedAhead == 0) { | ||
// The playhead is in an unbuffered region, so buffering will be | ||
// required at the seek target; since the segment availability window | ||
// is moving, we cannot seek to the seek target exactly; otherwise, we | ||
// would fall outside the segment availability window again... so seek | ||
// a bit ahead of the seek target. | ||
targetTime = this.clampTime_(start + this.minBufferTime_); | ||
} | ||
} else if (currentTime > end) { | ||
targetTime = end; | ||
} | ||
|
||
if (targetTime != currentTime) { | ||
shaka.log.debug('Cannot seek outside segment availability window.'); | ||
// Triggers another call to onSeeking_(). | ||
this.video_.currentTime = targetTime; | ||
return; | ||
} | ||
|
||
this.onSeek_(); | ||
}; | ||
|
||
|
||
/** | ||
* Clamps the given time to the segment availability window. | ||
* | ||
* @param {number} time The time in seconds. | ||
* @return {number} The clamped time in seconds. | ||
* @private | ||
*/ | ||
shaka.media.Playhead.prototype.clampTime_ = function(time) { | ||
var start = this.timeline_.getSegmentAvailabilityStart(); | ||
if (time < start) return start; | ||
|
||
var end = this.timeline_.getSegmentAvailabilityEnd(); | ||
if (time > end) return end; | ||
|
||
return time; | ||
}; | ||
|
Oops, something went wrong.