UNPKG

rx-player

Version:
215 lines (208 loc) 7.59 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 config from "../../../config"; import log from "../../../log"; import type { IManifestMetadata } from "../../../manifest"; import { getLivePosition, getMaximumSafePosition, getMinimumSafePosition, } from "../../../manifest"; import isNullOrUndefined from "../../../utils/is_null_or_undefined"; import getMonotonicTimeStamp from "../../../utils/monotonic_timestamp"; /** * All possible initial time options that can be set. * * Written this way (with many type possibilities) on purpose to avoid issues * if the application using the RxPlayer gives undocumented values such as * `null`. * * TODO we shouldn't be that robust, at least not outside the API code, because * if we begin to be that liberal here, where should we end? * We may instead either want to progressively remove tolerance on the wrong * type being given or be better to enforce type-compliancy in the API code. */ export interface IInitialTimeOptions { /** If set, we should begin at this position, in seconds. */ position?: number | null | undefined; /** If set, we should begin at this unix timestamp, in seconds. */ wallClockTime?: number | null | undefined; /** * If set, we should begin at this position relative to the content's start, * in seconds. */ fromFirstPosition?: number | null | undefined; /** * If set, we should begin at this position relative to the content's maximum * seekable position, in seconds. * * It should consequently in most cases be a negative value. */ fromLastPosition?: number | null | undefined; /** * If set, we should begin at this position relative to the content's live * edge if it makes sense, in seconds. * * It should consequently in most cases be a negative value. * * If the live edge is unknown or if it does not make sense for the current * content, that position is relative to the content's maximum position * instead. */ fromLivePosition?: number | null | undefined; /** * If set, we should begin at this position relative to the whole duration of * the content, in percentage. */ percentage?: number | null | undefined; } /** * Returns the calculated initial time for the content described by the given * Manifest: * 1. if a start time is defined by user, calculate starting time from the * manifest information * 2. else if the media is live, use the live edge and suggested delays from * it * 3. else returns the minimum time announced in the manifest * @param {Manifest} manifest * @param {boolean} lowLatencyMode * @param {Object} startAt * @returns {Number} */ export default function getInitialTime( manifest: IManifestMetadata, lowLatencyMode: boolean, startAt?: IInitialTimeOptions, ): number { if (!isNullOrUndefined(startAt)) { const min = getMinimumSafePosition(manifest); const max = getMaximumSafePosition(manifest); if (!isNullOrUndefined(startAt.position)) { log.debug("Init", "Initial Position: using startAt.position", { position: startAt.position, min, max, isDynamic: manifest.isDynamic, }); if (manifest.isDynamic) { return startAt.position; } return Math.max(Math.min(startAt.position, max), min); } else if (!isNullOrUndefined(startAt.wallClockTime)) { const ast = manifest.availabilityStartTime === undefined ? 0 : manifest.availabilityStartTime; const position = startAt.wallClockTime - ast; log.debug("Init", "Initial Position: using startAt.wallClockTime", { wallClockTime: startAt.wallClockTime, wallClockOffset: ast, deOffseted: position, min, max, isDynamic: manifest.isDynamic, }); if (manifest.isDynamic) { return position; } return Math.max(Math.min(position, max), min); } else if (!isNullOrUndefined(startAt.fromFirstPosition)) { const { fromFirstPosition } = startAt; log.debug("Init", "Initial Position: using startAt.fromFirstPosition", { fromFirstPosition, min, max, }); return fromFirstPosition <= 0 ? min : Math.min(max, min + fromFirstPosition); } else if (!isNullOrUndefined(startAt.fromLastPosition)) { const { fromLastPosition } = startAt; log.debug("Init", "Initial Position: using startAt.fromLastPosition", { fromLastPosition, min, max, }); return fromLastPosition >= 0 ? max : Math.max(min, max + fromLastPosition); } else if (!isNullOrUndefined(startAt.fromLivePosition)) { const livePosition = getLivePosition(manifest) ?? max; const { fromLivePosition } = startAt; log.debug("Init", "Initial Position: using startAt.fromLivePosition", { fromLivePosition, livePosition, min, }); return fromLivePosition >= 0 ? livePosition : Math.max(min, livePosition + fromLivePosition); } else if (!isNullOrUndefined(startAt.percentage)) { const { percentage } = startAt; log.debug("Init", "Initial Position: using startAt.percentage", { percentage, min, max, }); if (percentage > 100) { return max; } else if (percentage < 0) { return min; } const ratio = +percentage / 100; const extent = max - min; return min + extent * ratio; } } const minimumPosition = getMinimumSafePosition(manifest); if (manifest.isLive) { const { suggestedPresentationDelay, clockOffset } = manifest; const maximumPosition = getMaximumSafePosition(manifest); let liveTime: number; const { DEFAULT_LIVE_GAP } = config.getCurrent(); if (clockOffset === undefined) { log.info( "Init", "no clock offset found for a live content, " + "starting close to maximum available position", { maximumPosition }, ); liveTime = maximumPosition; } else { const ast = manifest.availabilityStartTime === undefined ? 0 : manifest.availabilityStartTime; const clockRelativeLiveTime = (getMonotonicTimeStamp() + clockOffset) / 1000 - ast; liveTime = Math.min(maximumPosition, clockRelativeLiveTime); log.info( "Init", "clock offset found for a live content, " + "checking if we can start close to it", { wallClockOffset: ast, clockRelativeLiveTime, liveTime, }, ); } const diffFromLiveTime = suggestedPresentationDelay ?? (lowLatencyMode ? DEFAULT_LIVE_GAP.LOW_LATENCY : DEFAULT_LIVE_GAP.DEFAULT); log.debug("Init", "Initial Position: Applying gap from live time", { liveTime, diffFromLiveTime, minimumPosition, }); return Math.max(liveTime - diffFromLiveTime, minimumPosition); } log.info("Init", "Initial Position: starting at the minimum available position", { minimumPosition, }); return minimumPosition; }