UNPKG

rx-player

Version:
204 lines (184 loc) 5.73 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 isNullOrUndefined from "../../../utils/is_null_or_undefined"; /** Generic way to represent segments in an index of segments. */ export interface IIndexSegment { /** The segment starting presentation time. */ start: number; /** Difference between the last and first presentation time. */ duration: number; /** * Repeat counter. * 1 === Repeat 1 time == 2 consecutive segments of this same duration. */ repeatCount: number; /** Optional byte-range the segment is available at when requested. */ range?: [number, number] | undefined; } /** * Calculate the number of times a timeline element repeats based on the next * element. * @param {Object} element * @param {Object|null|undefined} nextElement * @param {number|undefined} maxPosition * @returns {Number} */ export function calculateRepeat( element: IIndexSegment, nextElement?: IIndexSegment | null | undefined, maxPosition?: number | undefined, ): number { const { repeatCount } = element; if (repeatCount >= 0) { return repeatCount; } // A negative value of the @r attribute of the S element indicates // that the duration indicated in @d attribute repeats until the // start of the next S element, the end of the Period or until the // next MPD update. let segmentEnd: number; if (!isNullOrUndefined(nextElement)) { segmentEnd = nextElement.start; } else if (maxPosition !== undefined) { segmentEnd = maxPosition; } else { segmentEnd = Number.MAX_VALUE; } return Math.ceil((segmentEnd - element.start) / element.duration) - 1; } /** * Returns end of the segment given, in index time. * @param {Object} segment * @param {Object|null} [nextSegment] * @param {number} maxPosition * @returns {Number} */ export function getIndexSegmentEnd( segment: IIndexSegment, nextSegment: IIndexSegment | null, maxPosition?: number, ): number { const { start, duration } = segment; if (duration <= 0) { return start; } const repeat = calculateRepeat(segment, nextSegment, maxPosition); return start + (repeat + 1) * duration; } /** * Convert from `presentationTime`, the time of the segment at the moment it * is decoded to `mediaTime`, the original time the segments point at. * @param {number} time * @param {Object} indexOptions * @returns {number} */ export function toIndexTime( time: number, indexOptions: { timescale: number; indexTimeOffset?: number }, ): number { return time * indexOptions.timescale + (indexOptions.indexTimeOffset ?? 0); } /** * Convert from `mediaTime`, the original time the segments point at to * `presentationTime`, the time of the segment at the moment it is decoded. * @param {number} time * @param {Object} indexOptions * @returns {number} */ export function fromIndexTime( time: number, indexOptions: { timescale: number; indexTimeOffset?: number }, ): number { return (time - (indexOptions.indexTimeOffset ?? 0)) / indexOptions.timescale; } /** * @param {Number} start * @param {Number} duration * @param {Number} timescale * @returns {Object} - Object with two properties: * - up {Number}: timescaled timestamp of the beginning time * - to {Number}: timescaled timestamp of the end time (start time + duration) */ export function getTimescaledRange( start: number, duration: number, timescale: number, ): [number, number] { return [start * timescale, (start + duration) * timescale]; } /** * Get index of the last segment in the timeline starting before/at the given * timescaled time. * Returns -1 if the given time is lower than the start of the first available * segment. * @param {Object} timeline * @param {Number} timeTScaled * @returns {Number} */ function getIndexOfLastObjectBefore( timeline: IIndexSegment[], timeTScaled: number, ): number { let low = 0; let high = timeline.length; while (low < high) { const mid = (low + high) >>> 1; // Divide by two + floor if (timeline[mid].start <= timeTScaled) { low = mid + 1; } else { high = mid; } } return low - 1; } /** * @param {Object} index * @param {number} timeSec * @param {number} [maxPosition] * @returns {number|null} */ export function checkDiscontinuity( index: { timeline: IIndexSegment[]; timescale: number; indexTimeOffset?: number; }, timeSec: number, maxPosition?: number, ): number | null { const { timeline } = index; const scaledTime = toIndexTime(timeSec, index); if (scaledTime < 0) { return null; } const segmentIndex = getIndexOfLastObjectBefore(timeline, scaledTime); if (segmentIndex < 0 || segmentIndex >= timeline.length - 1) { return null; } const timelineItem = timeline[segmentIndex]; if (timelineItem.duration <= 0) { return null; } const nextTimelineItem = timeline[segmentIndex + 1]; if (nextTimelineItem === undefined) { return null; } const nextStart = nextTimelineItem.start; const segmentEnd = getIndexSegmentEnd(timelineItem, nextTimelineItem, maxPosition); return scaledTime >= segmentEnd && scaledTime < nextStart ? fromIndexTime(nextStart, index) : null; }