UNPKG

rx-player

Version:
275 lines (252 loc) 8.25 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 { IRepresentationIndex, ISegment } from "../../../manifest"; import type { ILocalIndex, ILocalIndexSegment } from "./types"; export default class LocalRepresentationIndex implements IRepresentationIndex { private _index: ILocalIndex; private _representationId: string; constructor(index: ILocalIndex, representationId: string) { this._index = index; this._representationId = representationId; } /** * @returns {Object} */ getInitSegment(): ISegment | null { return { id: `${this._representationId}_init`, isInit: true, time: 0, end: 0, duration: 0, timescale: 1, url: null, complete: true, privateInfos: { localManifestInitSegment: { load: this._index.loadInitSegment }, }, }; } /** * @param {Number} up * @param {Number} duration * @returns {Array.<Object>} */ getSegments(up: number, duration: number): ISegment[] { const startTime = up; const endTime = up + duration; const wantedSegments: ILocalIndexSegment[] = []; for (const segment of this._index.segments) { const segmentStart = segment.time; if (endTime <= segmentStart) { break; } const segmentEnd = segment.time + segment.duration; if (segmentEnd > startTime) { wantedSegments.push(segment); } } return wantedSegments.map((wantedSegment) => { return { id: `${this._representationId}_${wantedSegment.time}`, isInit: false, time: wantedSegment.time, end: wantedSegment.time + wantedSegment.duration, duration: wantedSegment.duration, timescale: 1, timestampOffset: wantedSegment.timestampOffset, url: null, complete: true, privateInfos: { localManifestSegment: { load: this._index.loadSegment, segment: wantedSegment, }, }, }; }); } /** * @returns {Number|undefined} */ getFirstAvailablePosition(): number | undefined { if (this._index.segments.length === 0) { return undefined; } const firstSegment = this._index.segments[0]; return firstSegment.time; } /** * @returns {Number|undefined} */ getLastAvailablePosition(): number | undefined { if (this._index.segments.length === 0) { return undefined; } const lastSegment = this._index.segments[this._index.segments.length - 1]; return lastSegment.time + lastSegment.duration; } /** * Returns the expected ending position of this RepresentationIndex. * `undefined` if unknown. * @returns {number|undefined} */ getEnd(): number | undefined { if (this._index.isFinished) { return this.getLastAvailablePosition(); } const { incomingRanges, segments } = this._index; if (incomingRanges === undefined || incomingRanges.length === 0) { // If incomingRanges is empty but not finished... It's ambiguous. return undefined; } const lastIncomingRange = incomingRanges[incomingRanges.length - 1]; const futureEnd = lastIncomingRange.end; if (segments.length === 0) { return futureEnd; } const lastSegment = this._index.segments[this._index.segments.length - 1]; return Math.max(lastSegment.time + lastSegment.duration, futureEnd); } /** * Returns: * - `true` if in the given time interval, at least one new segment is * expected to be available in the future. * - `false` either if all segments in that time interval are already * available for download or if none will ever be available for it. * - `undefined` when it is not possible to tell. * @param {number} start * @param {number} end * @returns {boolean|undefined} */ awaitSegmentBetween(start: number, end: number): boolean | undefined { if (this.isStillAwaitingFutureSegments()) { return false; } if (this._index.incomingRanges === undefined) { return undefined; } return this._index.incomingRanges.some( (range) => range.start < end && range.end > start, ); } /** * @returns {Boolean} */ shouldRefresh(): false { return false; } /** * @returns {Boolean} */ isSegmentStillAvailable(): true { return true; } isStillAwaitingFutureSegments(): boolean { return !this._index.isFinished; } /** * @returns {Boolean} */ canBeOutOfSyncError(): false { return false; } /** * @returns {null} */ checkDiscontinuity(): null { return null; } /** * @returns {Boolean} */ isInitialized(): true { return true; } initialize(): void { log.error("A `LocalRepresentationIndex` does not need to be initialized"); } addPredictedSegments(): void { log.warn("Cannot add predicted segments to a `LocalRepresentationIndex`"); } /** * Returns the `duration` of each segment in the context of its Manifest (i.e. * as the Manifest anounces them, actual segment duration may be different due * to approximations), in seconds. * * NOTE: we could here do a median or a mean but I chose to be lazy (and * more performant) by returning the duration of the first element instead. * As `isPrecize` is `false`, the rest of the code should be notified that * this is only an approximation. * @returns {number} */ getTargetSegmentDuration(): { duration: number; isPrecize: boolean } | undefined { if (this._index.segments.length === 0) { return undefined; } return { duration: this._index.segments[0].duration, isPrecize: false, }; } _replace(newIndex: LocalRepresentationIndex): void { this._index.segments = newIndex._index.segments; this._index.loadSegment = newIndex._index.loadSegment; this._index.loadInitSegment = newIndex._index.loadInitSegment; } _update(newIndex: LocalRepresentationIndex): void { const newSegments = newIndex._index.segments; if (newSegments.length <= 0) { return; } const insertNewIndexAtPosition = (pos: number): void => { this._index.segments.splice(pos, oldIndexLength - pos, ...newSegments); this._index.loadSegment = newIndex._index.loadSegment; this._index.loadInitSegment = newIndex._index.loadInitSegment; }; const oldIndexLength = this._index.segments.length; const newIndexStart = newSegments[0].time; for (let i = oldIndexLength - 1; i >= 0; i--) { const currSegment = this._index.segments[i]; if (currSegment.time === newIndexStart) { return insertNewIndexAtPosition(i); } else if (currSegment.time < newIndexStart) { if (currSegment.time + currSegment.duration > newIndexStart) { // the new Manifest overlaps a previous segment (weird). Remove the latter. log.warn( "Local RepresentationIndex: Manifest update removed" + " previous segments", ); return insertNewIndexAtPosition(i); } return insertNewIndexAtPosition(i + 1); } } // if we got here, it means that every segments in the previous manifest are // after the new one. This is unusual. // Either the new one has more depth or it's an older one. const oldIndexEnd = this._index.segments[oldIndexLength - 1].time + this._index.segments[oldIndexLength - 1].duration; const newIndexEnd = newSegments[newSegments.length - 1].time + newSegments[newSegments.length - 1].duration; if (oldIndexEnd >= newIndexEnd) { return; } return this._replace(newIndex); } }