rx-player
Version:
Canal+ HTML5 Video Player
204 lines (184 loc) • 5.73 kB
text/typescript
/**
* 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;
}