/** * 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 { areSameContent } from "../../../manifest"; import getMonotonicTimeStamp from "../../../utils/monotonic_timestamp"; import type { IChunkContext } from "./types"; /** * Entry of a `BufferedHistory`, added when the initial buffered range of a * **full segment** (and not just of a sub-chunk of a segment like in some * low-latency streaming usages) in the buffer becomes known. */ export interface IBufferedHistoryEntry { /** The RxPlayer's monotonically-raising timestamp when the event happened. */ date: number; /** * Timestamps of what has been buffered with that segment. * `null` if it has been immediately garbage collected. */ buffered: null | { /** Start time at which we observed that segment/chunk starts at, in seconds. */ start: number; /** *End time at which we observed that segment/chunk ends at, in seconds. */ end: number; }; /** Content metadata linked to the segment, allowing to recognize it. */ context: IChunkContext; } /** * Register a short-lived history of buffer information. * * This class can be useful to develop heuristics based on short-term buffer * history, such as knowing the real start and end of a buffered segment once * it has been pushed in a buffer. * * By storing in a history important recent actions and events, the * `BufferedHistory` can help other RxPlayer modules detect and work-around * unusual behavior. * * @class BufferedHistory */ export default class BufferedHistory { /** Complete recent history in chronological order (from oldest to newest) */ private _history: IBufferedHistoryEntry[]; /** Maximum time a history entry should be retained. */ private _lifetime: number; /** Maximum number of entries the `BufferedHistory`'s history should have. */ private _maxHistoryLength: number; /** * @param {number} lifetime - Maximum time a history entry should be retained. * @param {number} maxHistoryLength - Maximum number of entries the history * should have. */ constructor(lifetime: number, maxHistoryLength: number) { this._history = []; this._lifetime = lifetime; this._maxHistoryLength = maxHistoryLength; } /** * Add an entry to the `BufferedHistory`'s history indicating the buffered * range of a pushed segment. * * To call when the full range of a given segment becomes known. * * @param {Object} context * @param {Array.|null} buffered */ public addBufferedSegment( context: IChunkContext, buffered: { start: number; end: number } | null, ): void { const now = getMonotonicTimeStamp(); this._history.push({ date: now, buffered, context }); this._cleanHistory(now); } /** * Returns all entries linked to the given segment. * @param {Object} context * @returns {Array.} */ public getHistoryFor(context: IChunkContext): IBufferedHistoryEntry[] { return this._history.filter((el) => areSameContent(el.context, context)); } /** * If the current history does not satisfy `_lifetime` or `_maxHistoryLength`, * clear older entries until it does. * @param {number} now - Current monotonically-raising timestamp. */ private _cleanHistory(now: number) { const historyEarliestLimit = now - this._lifetime; let firstKeptIndex = 0; for (const event of this._history) { if (event.date < historyEarliestLimit) { firstKeptIndex++; } else { break; } } if (firstKeptIndex > 0) { this._history = this._history.splice(firstKeptIndex); } if (this._history.length > this._maxHistoryLength) { const toRemove = this._history.length - this._maxHistoryLength; this._history = this._history.splice(toRemove); } } }