Files
shaka-player/lib/media/video_wrapper.js
T
Joey Parrish f539147d48 fix: Correct license headers in compiled output
This fixes all the license headers in the main library, which corrects
the appearance of the main license in the compiled output.

It seems that the `!` in the header forces the compiler to keep it in
the output.  I believe older compiler releases did this purely based
on `@license`.

Issue #2638

Change-Id: I7f0e918caad10c9af689c9d07672b7fe9be7b2f3
2020-06-09 16:05:09 -07:00

276 lines
7.5 KiB
JavaScript

/*! @license
* Shaka Player
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
goog.provide('shaka.media.VideoWrapper');
goog.provide('shaka.media.VideoWrapper.PlayheadMover');
goog.require('goog.asserts');
goog.require('shaka.log');
goog.require('shaka.util.EventManager');
goog.require('shaka.util.IReleasable');
goog.require('shaka.util.MediaReadyState');
goog.require('shaka.util.Timer');
/**
* Creates a new VideoWrapper that manages setting current time and playback
* rate. This handles seeks before content is loaded and ensuring the video
* time is set properly. This doesn't handle repositioning within the
* presentation window.
*
* @implements {shaka.util.IReleasable}
*/
shaka.media.VideoWrapper = class {
/**
* @param {!HTMLMediaElement} video
* @param {function()} onSeek Called when the video seeks.
* @param {number} startTime The time to start at.
*/
constructor(video, onSeek, startTime) {
/** @private {HTMLMediaElement} */
this.video_ = video;
/** @private {function()} */
this.onSeek_ = onSeek;
/** @private {number} */
this.startTime_ = startTime;
/** @private {boolean} */
this.started_ = false;
/** @private {shaka.util.EventManager} */
this.eventManager_ = new shaka.util.EventManager();
/** @private {shaka.media.VideoWrapper.PlayheadMover} */
this.mover_ = new shaka.media.VideoWrapper.PlayheadMover(
/* mediaElement= */ video,
/* maxAttempts= */ 10);
// Before we can set the start time, we must check if the video element is
// ready. If the video element is not ready, we cannot set the time. To work
// around this, we will wait for the "loadedmetadata" event which tells us
// that the media element is now ready.
shaka.util.MediaReadyState.waitForReadyState(this.video_,
HTMLMediaElement.HAVE_METADATA,
this.eventManager_,
() => {
this.setStartTime_(this.startTime_);
});
}
/** @override */
release() {
if (this.eventManager_) {
this.eventManager_.release();
this.eventManager_ = null;
}
if (this.mover_ != null) {
this.mover_.release();
this.mover_ = null;
}
this.onSeek_ = () => {};
this.video_ = null;
}
/**
* Gets the video's current (logical) position.
*
* @return {number}
*/
getTime() {
return this.started_ ? this.video_.currentTime : this.startTime_;
}
/**
* Sets the current time of the video.
*
* @param {number} time
*/
setTime(time) {
if (this.video_.readyState > 0) {
this.mover_.moveTo(time);
} else {
shaka.util.MediaReadyState.waitForReadyState(this.video_,
HTMLMediaElement.HAVE_METADATA,
this.eventManager_,
() => {
this.setStartTime_(this.startTime_);
});
}
}
/**
* Set the start time for the content. The given start time will be ignored if
* the content does not start at 0.
*
* @param {number} startTime
* @private
*/
setStartTime_(startTime) {
// If we start close enough to our intended start time, then we won't do
// anything special.
if (Math.abs(this.video_.currentTime - startTime) < 0.001) {
this.startListeningToSeeks_();
return;
}
// We will need to delay adding our normal seeking listener until we have
// seen the first seek event. We will force the first seek event later in
// this method.
this.eventManager_.listenOnce(this.video_, 'seeking', () => {
this.startListeningToSeeks_();
});
// If the currentTime != 0, it indicates that the user has seeked after
// calling |Player.load|, meaning that |currentTime| is more meaningful than
// |startTime|.
//
// Seeking to the current time is a work around for Issue 1298. If we don't
// do this, the video may get stuck and not play.
//
// TODO: Need further investigation why it happens. Before and after
// setting the current time, video.readyState is 1, video.paused is true,
// and video.buffered's TimeRanges length is 0.
// See: https://github.com/google/shaka-player/issues/1298
this.mover_.moveTo(
this.video_.currentTime == 0 ?
startTime :
this.video_.currentTime);
}
/**
* Add the listener for seek-events. This will call the externally-provided
* |onSeek| callback whenever the media element seeks.
*
* @private
*/
startListeningToSeeks_() {
goog.asserts.assert(
this.video_.readyState > 0,
'The media element should be ready before we listen for seeking.');
// Now that any startup seeking is complete, we can trust the video element
// for currentTime.
this.started_ = true;
this.eventManager_.listen(this.video_, 'seeking', () => this.onSeek_());
}
};
/**
* A class used to move the playhead away from its current time. Sometimes, IE
* and Edge ignore re-seeks. After changing the current time, check every 100ms,
* retrying if the change was not accepted.
*
* Delay stats over 100 runs of a re-seeking integration test:
* IE - 0ms - 47%
* IE - 100ms - 63%
* Edge - 0ms - 2%
* Edge - 100ms - 40%
* Edge - 200ms - 32%
* Edge - 300ms - 24%
* Edge - 400ms - 2%
* Chrome - 0ms - 100%
*
* TODO: File a bug on IE/Edge about this.
*
* @implements {shaka.util.IReleasable}
* @final
*/
shaka.media.VideoWrapper.PlayheadMover = class {
/**
* @param {!HTMLMediaElement} mediaElement
* The media element that the mover can manipulate.
*
* @param {number} maxAttempts
* To prevent us from infinitely trying to change the current time, the
* mover accepts a max attempts value. At most, the mover will check if the
* video moved |maxAttempts| times. If this is zero of negative, no
* attempts will be made.
*/
constructor(mediaElement, maxAttempts) {
/** @private {HTMLMediaElement} */
this.mediaElement_ = mediaElement;
/** @private {number} */
this.maxAttempts_ = maxAttempts;
/** @private {number} */
this.remainingAttempts_ = 0;
/** @private {number} */
this.originTime_ = 0;
/** @private {number} */
this.targetTime_ = 0;
/** @private {shaka.util.Timer} */
this.timer_ = new shaka.util.Timer(() => this.onTick_());
}
/** @override */
release() {
if (this.timer_) {
this.timer_.stop();
this.timer_ = null;
}
this.mediaElement_ = null;
}
/**
* Try forcing the media element to move to |timeInSeconds|. If a previous
* call to |moveTo| is still in progress, this will override it.
*
* @param {number} timeInSeconds
*/
moveTo(timeInSeconds) {
this.originTime_ = this.mediaElement_.currentTime;
this.targetTime_ = timeInSeconds;
this.remainingAttempts_ = this.maxAttempts_;
// Set the time and then start the timer. The timer will check if the set
// was successful, and retry if not.
this.mediaElement_.currentTime = timeInSeconds;
this.timer_.tickEvery(/* seconds= */ 0.1);
}
/**
* @private
*/
onTick_() {
// Sigh... We ran out of retries...
if (this.remainingAttempts_ <= 0) {
shaka.log.warning([
'Failed to move playhead from', this.originTime_,
'to', this.targetTime_,
].join(' '));
this.timer_.stop();
return;
}
// Yay! We were successful.
if (this.mediaElement_.currentTime != this.originTime_) {
this.timer_.stop();
return;
}
// Sigh... Try again...
this.mediaElement_.currentTime = this.targetTime_;
this.remainingAttempts_--;
}
};