UNPKG

rx-player

Version:
247 lines (224 loc) 9.12 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 log from "../../../log"; import type { ISegment } from "../../../manifest"; import getMonotonicTimeStamp from "../../../utils/monotonic_timestamp"; import clearTimelineFromPosition from "../utils/clear_timeline_from_position"; import { getIndexSegmentEnd } from "../utils/index_helpers"; import updateSegmentTimeline from "../utils/update_segment_timeline"; import addSegmentInfos from "./utils/add_segment_infos"; /** * Smooth contents provide the index of segments under a "StreamIndex", the * smooth equivalent of an AdaptationSet. * * This means that multiple "QualityLevel" (smooth's Representation) are going * to rely on the exact same list of segments. This also means that all * mutations on that timeline (whether it is to evict old segments or to add * new ones) should presumably happen for all of them at the same time. * * The `SharedSmoothSegmentTimeline` is an abstraction over that index of * segments whose goal is to explicitely provide a data structure that can be * shared to every `RepresentationIndex` linked to Representations being part * of the same smooth Adaptation, thus allowing to mutualize any side-effect * done to it automatically. * * @class SharedSmoothSegmentTimeline */ export default class SharedSmoothSegmentTimeline { /** * Array describing the list of segments available. * Note that this same list might be shared by multiple `RepresentationIndex` * (as they link to the same timeline in the Manifest). */ public timeline: IIndexSegment[]; /** Timescale allowing to convert time values in `timeline` into seconds. */ public timescale: number; /** * Defines the earliest time - expressed in the RxPlayer's * monotonically-raising timestamp unit - when the timeline was known to be * valid (that is, when all segments declared in it are available). * * This is either: * - the Manifest downloading time, if known * - else, the time of creation of this SharedSmoothSegmentTimeline, as a * guess */ public validityTime: number; private _timeShiftBufferDepth: number | undefined; private _initialScaledLastPosition: number | undefined; constructor(args: ISharedSmoothSegmentTimelineArguments) { const { timeline, timescale, timeShiftBufferDepth, manifestReceivedTime } = args; this.timeline = timeline; this.timescale = timescale; const estimatedReceivedTime = manifestReceivedTime ?? getMonotonicTimeStamp(); this.validityTime = estimatedReceivedTime; this._timeShiftBufferDepth = timeShiftBufferDepth; if (timeline.length !== 0) { const lastItem = timeline[timeline.length - 1]; const scaledEnd = getIndexSegmentEnd(lastItem, null); this._initialScaledLastPosition = scaledEnd; } } /** * Clean-up timeline to remove segment information which should not be * available due to the timeshift window */ public refresh(): void { // clean segments before time shift buffer depth if (this._initialScaledLastPosition === undefined) { return; } const timeShiftBufferDepth = this._timeShiftBufferDepth; const timeSinceLastRealUpdate = (getMonotonicTimeStamp() - this.validityTime) / 1000; const lastPositionEstimate = timeSinceLastRealUpdate + this._initialScaledLastPosition / this.timescale; if (timeShiftBufferDepth !== undefined) { const minimumPosition = (lastPositionEstimate - timeShiftBufferDepth) * this.timescale; clearTimelineFromPosition(this.timeline, minimumPosition); } } /** * Replace this SharedSmoothSegmentTimeline by a newly downloaded one. * Check if the old timeline had more information about new segments and re-add * them if that's the case. * @param {Object} newSmoothTimeline */ public replace(newSmoothTimeline: SharedSmoothSegmentTimeline): void { const oldTimeline = this.timeline; const newTimeline = newSmoothTimeline.timeline; const oldTimescale = this.timescale; const newTimescale = newSmoothTimeline.timescale; this._initialScaledLastPosition = newSmoothTimeline._initialScaledLastPosition; this.validityTime = newSmoothTimeline.validityTime; if ( oldTimeline.length === 0 || newTimeline.length === 0 || oldTimescale !== newTimescale ) { return; // don't take risk, if something is off, take the new one } const lastOldTimelineElement = oldTimeline[oldTimeline.length - 1]; const lastNewTimelineElement = newTimeline[newTimeline.length - 1]; const newEnd = getIndexSegmentEnd(lastNewTimelineElement, null); if (getIndexSegmentEnd(lastOldTimelineElement, null) <= newEnd) { return; } for (let i = 0; i < oldTimeline.length; i++) { const oldTimelineRange = oldTimeline[i]; const oldEnd = getIndexSegmentEnd(oldTimelineRange, null); if (oldEnd === newEnd) { // just add the supplementary segments this.timeline = this.timeline.concat(oldTimeline.slice(i + 1)); return; } if (oldEnd > newEnd) { // adjust repeatCount + add supplementary segments if (oldTimelineRange.duration !== lastNewTimelineElement.duration) { return; } const rangeDuration = newEnd - oldTimelineRange.start; if (rangeDuration === 0) { log.warn( "smooth", "a discontinuity detected in the previous manifest" + " has been resolved.", ); this.timeline = this.timeline.concat(oldTimeline.slice(i)); return; } if (rangeDuration < 0 || rangeDuration % oldTimelineRange.duration !== 0) { return; } const repeatWithOld = rangeDuration / oldTimelineRange.duration - 1; const relativeRepeat = oldTimelineRange.repeatCount - repeatWithOld; if (relativeRepeat < 0) { return; } lastNewTimelineElement.repeatCount += relativeRepeat; const supplementarySegments = oldTimeline.slice(i + 1); this.timeline = this.timeline.concat(supplementarySegments); return; } } } /** * Update the current SharedSmoothSegmentTimeline with a new, partial, version. * This method might be use to only add information about new segments. * @param {Object} newSmoothTimeline */ public update(newSmoothTimeline: SharedSmoothSegmentTimeline): void { updateSegmentTimeline(this.timeline, newSmoothTimeline.timeline); this._initialScaledLastPosition = newSmoothTimeline._initialScaledLastPosition; this.validityTime = newSmoothTimeline.validityTime; } /** * Add segments to a `SharedSmoothSegmentTimeline` that were predicted to come * after `currentSegment`. * @param {Array.<Object>} nextSegments - The segment information parsed. * @param {Object} currentSegment - Information on the segment which contained * that new segment information. */ public addPredictedSegments( nextSegments: Array<{ duration: number; time: number; timescale: number }>, currentSegment: ISegment, ): void { if (currentSegment.privateInfos?.smoothMediaSegment === undefined) { log.warn("smooth", "should only encounter SmoothRepresentationIndex"); return; } this.refresh(); for (const nextSeg of nextSegments) { addSegmentInfos( this.timeline, this.timescale, nextSeg, currentSegment.privateInfos.smoothMediaSegment, ); } } } export interface ISharedSmoothSegmentTimelineArguments { timeline: IIndexSegment[]; timescale: number; timeShiftBufferDepth: number | undefined; manifestReceivedTime: number | undefined; } /** * Object describing information about one segment or several consecutive * segments. */ export interface IIndexSegment { /** Time (timescaled) at which the segment starts. */ start: number; /** Duration (timescaled) of the segment. */ duration: number; /** * Amount of consecutive segments with that duration. * * For example let's consider the following IIndexSegment: * ``` * { start: 10, duration: 2, repeatCount: 2 } * ``` * Here, because `repeatCount` is set to `2`, this object actually defines 3 * segments: * 1. one starting at `10` and ending at `12` (10 + 2) * 2. another one starting at `12` (the previous one's end) and ending at * `14` (12 + 2) * 3. another one starting at `14` (the previous one's end) and ending at * `16` (14 +2) */ repeatCount: number; }