rx-player
Version:
Canal+ HTML5 Video Player
179 lines (169 loc) • 6.29 kB
text/typescript
/**
* Copyright 2015 CANAL+ Group
*
* 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.
*/
import getMonotonicTimeStamp from "../../../../utils/monotonic_timestamp";
/**
* This class allows to easily calculate the first and last available positions
* in a content at any time.
*
* By centralizing the manifest bounds calculation in this class and by giving
* an instance of it to each parsed elements which might depend on it, we
* ensure that we can provide it once it is known to every one of those
* elements without needing to parse a second time the MPD.
* @class ManifestBoundsCalculator
*/
export default class ManifestBoundsCalculator {
/**
* Value of MPD@timeShiftBufferDepth.
* `null` if not defined.
*/
private _timeShiftBufferDepth: number | null;
/**
* Value of MPD@availabilityStartTime as an unix timestamp in seconds.
* `0` if it wasn't defined.
*/
private _availabilityStartTime: number;
/** `true` if MPD@type is equal to "dynamic". */
private _isDynamic: boolean;
/**
* Monotonically-raising timestamp (the one used by the RxPlayer) when
* `lastPosition` was calculated.
*/
private _positionTime: number | undefined;
/** Last position calculated at a given moment (itself indicated by `_positionTime`. */
private _lastPosition: number | undefined;
/**
* Offset to add to `performance.now` to obtain a good estimation of the
* server-side unix timestamp.
*
* `undefined` if unknown.
*/
private _serverTimestampOffset: number | undefined;
/**
* @param {Object} args
*/
constructor(args: {
availabilityStartTime: number;
timeShiftBufferDepth: number | undefined;
isDynamic: boolean;
serverTimestampOffset: number | undefined;
}) {
this._isDynamic = args.isDynamic;
this._timeShiftBufferDepth =
!args.isDynamic || args.timeShiftBufferDepth === undefined
? null
: args.timeShiftBufferDepth;
this._serverTimestampOffset = args.serverTimestampOffset;
this._availabilityStartTime = args.availabilityStartTime;
}
/**
* Set the last position and the position time (the value of the RxPlayer's
* monotonically-raising timestamp at the time that position was true
* converted into seconds).
*
* @example
* Example if you trust `Date.now()` to give you a reliable offset:
* ```js
* const lastPosition = Date.now();
* const positionTime = getMonotonicTimeStamp() / 1000;
* manifestBoundsCalculator.setLastPosition(lastPosition, positionTime);
* ```
*
* @param {number} lastPosition
* @param {number|undefined} positionTime
*/
setLastPosition(lastPosition: number, positionTime?: number): void {
this._lastPosition = lastPosition;
this._positionTime = positionTime;
}
/**
* Returns `true` if the last position and the position time
* (for dynamic content only) have been comunicated.
* `false` otherwise.
* @returns {boolean}
*/
lastPositionIsKnown(): boolean {
if (this._isDynamic) {
return this._positionTime !== undefined && this._lastPosition !== undefined;
}
return this._lastPosition !== undefined;
}
/**
* Estimate a minimum bound for the content from the last set segment time
* and buffer depth.
* Consider that it is only an estimate, not the real value.
* @param {number} segmentDuration - In DASH, the buffer depth actually also
* depend on a corresponding's segment duration (e.g. a segment become
* unavailable once the `timeShiftBufferDepth` + its duration has elapsed).
* This argument can thus be set the approximate duration of a segment.
* @return {number|undefined}
*/
getEstimatedMinimumSegmentTime(segmentDuration: number): number | undefined {
if (!this._isDynamic || this._timeShiftBufferDepth === null) {
return 0;
}
const maximumBound =
this.getEstimatedLiveEdge() ?? this.getEstimatedMaximumPosition(0);
if (maximumBound === undefined) {
return undefined;
}
const minimumBound = maximumBound - (this._timeShiftBufferDepth + segmentDuration);
return minimumBound;
}
/**
* Estimate the segment time in seconds that corresponds to what could be
* considered the live edge (or `undefined` for non-live contents).
*
* Note that for some contents which just anounce segments in advance, this
* value might be very different than the maximum position that is
* requestable.
* @return {number|undefined}
*/
getEstimatedLiveEdge(): number | undefined {
if (!this._isDynamic || this._serverTimestampOffset === undefined) {
return undefined;
}
return (
(getMonotonicTimeStamp() + this._serverTimestampOffset) / 1000 -
this._availabilityStartTime
);
}
/**
* Produce a rough estimate of the ending time of the last requestable segment
* in that content.
*
* This value is only an estimate and may be far from reality.
*
* The `availabilityTimeOffset` in argument is the corresponding
* `availabilityTimeOffset` that applies to the current wanted segment, or `0`
* if none exist. It will be applied on live content to deduce the maximum
* segment time available.
*/
getEstimatedMaximumPosition(availabilityTimeOffset: number): number | undefined {
if (!this._isDynamic) {
return this._lastPosition;
}
const liveEdge = this.getEstimatedLiveEdge();
if (liveEdge !== undefined && availabilityTimeOffset !== Infinity) {
return liveEdge + availabilityTimeOffset;
} else if (this._positionTime !== undefined && this._lastPosition !== undefined) {
return Math.max(
this._lastPosition - this._positionTime + getMonotonicTimeStamp() / 1000,
0,
);
}
return this._lastPosition;
}
}