mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-14 15:56:38 +03:00
a10287e3f0
This change is feat! because it no longer allows modifying the `currentTime` of the mediaElement in the streaming event. With this change, only `updateStartTime` can be called to update the time, and the user should always use the `canupdatestarttime` event instead of `streaming` event when they need it. Fixes https://github.com/shaka-project/shaka-player/issues/9661
275 lines
7.4 KiB
JavaScript
275 lines
7.4 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 {function(number)} onStarted Called when the video has started.
|
|
* @param {function():number} getStartTime Called to get the time to start at.
|
|
*/
|
|
constructor(video, onSeek, onStarted, getStartTime) {
|
|
/** @private {HTMLMediaElement} */
|
|
this.video_ = video;
|
|
|
|
/** @private {function()} */
|
|
this.onSeek_ = onSeek;
|
|
|
|
/** @private {function(number)} */
|
|
this.onStarted_ = onStarted;
|
|
|
|
/** @private {?number} */
|
|
this.startTime_ = null;
|
|
|
|
/** @private {function():number} */
|
|
this.getStartTime_ = () => {
|
|
if (this.startTime_ == null) {
|
|
this.startTime_ = getStartTime();
|
|
}
|
|
return this.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.getStartTime_());
|
|
});
|
|
}
|
|
|
|
|
|
/** @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.getStartTime_();
|
|
}
|
|
|
|
|
|
/**
|
|
* 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.getStartTime_());
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* 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_();
|
|
});
|
|
|
|
this.mover_.moveTo(startTime);
|
|
}
|
|
|
|
|
|
/**
|
|
* 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_());
|
|
this.onStarted_(this.video_.currentTime);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* A class used to move the playhead away from its current time. Sometimes,
|
|
* legacy Edge ignores 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:
|
|
* Edge - 0ms - 2%
|
|
* Edge - 100ms - 40%
|
|
* Edge - 200ms - 32%
|
|
* Edge - 300ms - 24%
|
|
* Edge - 400ms - 2%
|
|
* Chrome - 0ms - 100%
|
|
*
|
|
* Unfortunately, legacy Edge is not receiving updates anymore, but it still
|
|
* must be supported as it is used for web browsers on XBox.
|
|
*
|
|
* @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.mediaElement_.currentTime === this.targetTime_) {
|
|
this.timer_.stop();
|
|
return;
|
|
}
|
|
|
|
// Sigh... Try again...
|
|
this.mediaElement_.currentTime = this.targetTime_;
|
|
this.remainingAttempts_--;
|
|
}
|
|
};
|