UNPKG

rx-player

Version:
179 lines (169 loc) 6.29 kB
/** * 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; } }