rx-player
Version:
Canal+ HTML5 Video Player
848 lines (783 loc) • 31.1 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 config from "../../config";
import log from "../../log";
import type {
IAdaptation,
IManifest,
IPeriod,
IRepresentation,
ISegment,
} from "../../manifest";
import type {
ObservationPosition,
IReadOnlyPlaybackObserver,
} from "../../playback_observer";
import isNullOrUndefined from "../../utils/is_null_or_undefined";
import noop from "../../utils/noop";
import type { IRange } from "../../utils/ranges";
import { getLeftSizeOfRange } from "../../utils/ranges";
import type { IReadOnlySharedReference } from "../../utils/reference";
import SharedReference from "../../utils/reference";
import type { CancellationSignal } from "../../utils/task_canceller";
import TaskCanceller from "../../utils/task_canceller";
import type { IBufferType } from "../segment_sinks";
import BufferBasedChooser from "./buffer_based_chooser";
import GuessBasedChooser from "./guess_based_chooser";
import NetworkAnalyzer from "./network_analyzer";
import BandwidthEstimator from "./utils/bandwidth_estimator";
import filterByBitrate from "./utils/filter_by_bitrate";
import filterByResolution from "./utils/filter_by_resolution";
import type { IResolutionInfo } from "./utils/filter_by_resolution";
import LastEstimateStorage, { ABRAlgorithmType } from "./utils/last_estimate_storage";
import type {
IPendingRequestStoreBegin,
IPendingRequestStoreProgress,
} from "./utils/pending_requests_store";
import PendingRequestsStore from "./utils/pending_requests_store";
import RepresentationScoreCalculator from "./utils/representation_score_calculator";
import selectOptimalRepresentation from "./utils/select_optimal_representation";
// Create default shared references
const limitResolutionDefaultRef = new SharedReference<IResolutionInfo | undefined>(
undefined,
);
limitResolutionDefaultRef.finish();
const throttleBitrateDefaultRef = new SharedReference(Infinity);
throttleBitrateDefaultRef.finish();
/**
* Select the most adapted Representation according to the network and buffer
* metrics it receives.
*
* @param {Object} options - Initial configuration (see type definition)
* @returns {Object} - Interface allowing to select a Representation.
* @see IRepresentationEstimator
*/
export default function createAdaptiveRepresentationSelector(
options: IAdaptiveRepresentationSelectorArguments,
): IRepresentationEstimator {
/**
* Allows to estimate the current network bandwidth.
* One per active media type.
*/
const bandwidthEstimators: Partial<Record<IBufferType, BandwidthEstimator>> = {};
const { initialBitrates, throttlers, lowLatencyMode } = options;
/**
* Returns Object emitting Representation estimates as well as callbacks
* allowing to helping it produce them.
*
* @see IRepresentationEstimator
* @param {Object} context
* @param {Object} currentRepresentation
* @param {Object} representations
* @param {Object} playbackObserver
* @param {Object} stopAllEstimates
* @returns {Array.<Object>}
*/
return function getEstimates(
context: { manifest: IManifest; period: IPeriod; adaptation: IAdaptation },
currentRepresentation: IReadOnlySharedReference<IRepresentation | null>,
representations: IReadOnlySharedReference<IRepresentation[]>,
playbackObserver: IReadOnlyPlaybackObserver<IRepresentationEstimatorPlaybackObservation>,
stopAllEstimates: CancellationSignal,
): IRepresentationEstimatorResponse {
const { type } = context.adaptation;
const bandwidthEstimator = _getBandwidthEstimator(type);
const initialBitrate = initialBitrates[type] ?? 0;
const filters = {
limitResolution: throttlers.limitResolution[type] ?? limitResolutionDefaultRef,
throttleBitrate: throttlers.throttleBitrate[type] ?? throttleBitrateDefaultRef,
};
return getEstimateReference(
{
bandwidthEstimator,
context,
currentRepresentation,
filters,
initialBitrate,
playbackObserver,
representations,
lowLatencyMode,
},
stopAllEstimates,
);
};
/**
* Returns interface allowing to estimate network throughtput for a given type.
* @param {string} bufferType
* @returns {Object}
*/
function _getBandwidthEstimator(bufferType: IBufferType): BandwidthEstimator {
const originalBandwidthEstimator = bandwidthEstimators[bufferType];
if (isNullOrUndefined(originalBandwidthEstimator)) {
log.debug("ABR", "Creating new BandwidthEstimator", { bufferType });
const bandwidthEstimator = new BandwidthEstimator();
bandwidthEstimators[bufferType] = bandwidthEstimator;
return bandwidthEstimator;
}
return originalBandwidthEstimator;
}
}
/**
* Estimate regularly the current network bandwidth and the best Representation
* that can be played according to the current network and playback conditions.
*
* `getEstimateReference` only does estimations for a given type (e.g.
* "audio", "video" etc.) and Period.
*
* If estimates for multiple types and/or Periods are needed, you should
* call `getEstimateReference` as many times.
*
* This function returns a tuple:
* - the first element being the object through which estimates will be produced
* - the second element being callbacks that have to be triggered at various
* events to help it doing those estimates.
*
* @param {Object} args
* @param {Object} stopAllEstimates
* @returns {Array.<Object>}
*/
function getEstimateReference(
{
bandwidthEstimator,
context,
currentRepresentation,
filters,
initialBitrate,
lowLatencyMode,
playbackObserver,
representations: representationsRef,
}: IRepresentationEstimatorArguments,
stopAllEstimates: CancellationSignal,
): IRepresentationEstimatorResponse {
const scoreCalculator = new RepresentationScoreCalculator();
const networkAnalyzer = new NetworkAnalyzer(initialBitrate ?? 0, lowLatencyMode);
const requestsStore = new PendingRequestsStore();
/**
* Callback called each time a new segment is pushed, with the information on the
* new pushed segment.
*/
let onAddedSegment: (val: IAddedSegmentCallbackPayload) => void = noop;
const callbacks: IRepresentationEstimatorCallbacks = {
metrics: onMetric,
requestBegin: onRequestBegin,
requestProgress: onRequestProgress,
requestEnd: onRequestEnd,
addedSegment(val) {
onAddedSegment(val);
},
};
/**
* `TaskCanceller` allowing to stop producing estimate.
* This TaskCanceller is used both for restarting estimates with a new
* configuration and to cancel them altogether.
*/
let currentEstimatesCanceller = new TaskCanceller();
currentEstimatesCanceller.linkToSignal(stopAllEstimates);
// Create `SharedReference` on which estimates will be emitted.
const estimateRef = createEstimateReference(
representationsRef.getValue(),
currentEstimatesCanceller.signal,
);
representationsRef.onUpdate(restartEstimatesProductionFromCurrentConditions, {
clearSignal: stopAllEstimates,
});
return { estimates: estimateRef, callbacks };
/**
* Emit the ABR estimates on various events through the returned
* `SharedReference`.
* @param {Array.<Object>} unsortedRepresentations - All `Representation` that
* the ABR logic can choose from.
* @param {Object} innerCancellationSignal - When this `CancellationSignal`
* emits, all events registered to to emit new estimates will be unregistered.
* Note that the returned reference may still be used to produce new estimates
* in the future if you want to even after this signal emits.
* @returns {Object} - `SharedReference` through which ABR estimates will be
* produced.
*/
function createEstimateReference(
unsortedRepresentations: IRepresentation[],
innerCancellationSignal: CancellationSignal,
): SharedReference<IABREstimate> {
if (unsortedRepresentations.length <= 1) {
// There's only a single Representation. Just choose it.
return new SharedReference<IABREstimate>({
bitrate: undefined,
representation: unsortedRepresentations[0],
urgent: true,
knownStableBitrate: undefined,
});
}
/** If true, Representation estimates based on the buffer health might be used. */
let allowBufferBasedEstimates = false;
/** Ensure `Representation` objects are sorted by bitrates and only rely on this. */
const sortedRepresentations = unsortedRepresentations.sort(
(ra, rb) => ra.bitrate - rb.bitrate,
);
/**
* Module calculating the optimal Representation based on the current
* buffer's health (i.e. whether enough data is buffered, history of
* buffer size etc.).
*/
const bufferBasedChooser = new BufferBasedChooser(
sortedRepresentations.map((r) => r.bitrate),
);
/** Store the previous estimate made here. */
const prevEstimate = new LastEstimateStorage();
/**
* Module calculating the optimal Representation by "guessing it" with a
* step-by-step algorithm.
* Only used in very specific scenarios.
*/
const guessBasedChooser = new GuessBasedChooser(scoreCalculator, prevEstimate);
// get initial observation for initial estimate
let lastPlaybackObservation = playbackObserver.getReference().getValue();
/** Reference through which estimates are emitted. */
const innerEstimateRef = new SharedReference<IABREstimate>(getCurrentEstimate());
// Listen to playback observations
playbackObserver.listen(
(obs) => {
lastPlaybackObservation = obs;
updateEstimate();
},
{ includeLastObservation: false, clearSignal: innerCancellationSignal },
);
onAddedSegment = function (val: IAddedSegmentCallbackPayload) {
if (lastPlaybackObservation === null) {
return;
}
const { position, speed } = lastPlaybackObservation;
const timeRanges = val.buffered;
const bufferGap = getLeftSizeOfRange(timeRanges, position.getWanted());
const { representation } = val.content;
const currentScore = scoreCalculator.getEstimate(representation);
const currentBitrate = representation.bitrate;
const observation = { bufferGap, currentBitrate, currentScore, speed };
bufferBasedChooser.onAddedSegment(observation);
updateEstimate();
};
innerCancellationSignal.register(() => {
onAddedSegment = noop;
});
filters.throttleBitrate.onUpdate(updateEstimate, {
clearSignal: innerCancellationSignal,
});
filters.limitResolution.onUpdate(updateEstimate, {
clearSignal: innerCancellationSignal,
});
return innerEstimateRef;
function updateEstimate(): void {
innerEstimateRef.setValue(getCurrentEstimate());
}
/** Returns the actual estimate based on all methods and algorithm available. */
function getCurrentEstimate(): IABREstimate {
const { bufferGap, position, maximumPosition } = lastPlaybackObservation;
const resolutionLimit = filters.limitResolution.getValue();
const bitrateThrottle = filters.throttleBitrate.getValue();
const currentRepresentationVal = currentRepresentation.getValue();
const filteredReps = getFilteredRepresentations(
sortedRepresentations,
resolutionLimit,
bitrateThrottle,
);
const requests = requestsStore.getRequests();
const { bandwidthEstimate, bitrateChosen } = networkAnalyzer.getBandwidthEstimate(
lastPlaybackObservation,
bandwidthEstimator,
currentRepresentationVal,
requests,
prevEstimate.bandwidth,
);
const stableRepresentation = scoreCalculator.getLastStableRepresentation();
const knownStableBitrate =
stableRepresentation === null
? undefined
: stableRepresentation.bitrate /
(lastPlaybackObservation.speed > 0 ? lastPlaybackObservation.speed : 1);
const { ABR_ENTER_BUFFER_BASED_ALGO, ABR_EXIT_BUFFER_BASED_ALGO } =
config.getCurrent();
if (allowBufferBasedEstimates && bufferGap <= ABR_EXIT_BUFFER_BASED_ALGO) {
allowBufferBasedEstimates = false;
} else if (
!allowBufferBasedEstimates &&
isFinite(bufferGap) &&
bufferGap >= ABR_ENTER_BUFFER_BASED_ALGO
) {
allowBufferBasedEstimates = true;
}
/**
* Representation chosen when considering only [pessimist] bandwidth
* calculation.
* This is a safe enough choice but might be lower than what the user
* could actually profit from.
*/
const chosenRepFromBandwidth = selectOptimalRepresentation(
filteredReps,
bitrateChosen,
);
/**
* Current optimal Representation's bandwidth choosen by a buffer-based
* adaptive algorithm.
*/
const currentBufferBasedEstimate = bufferBasedChooser.getLastEstimate();
let currentBestBitrate = chosenRepFromBandwidth.bitrate;
/**
* Representation chosen when considering the current buffer size.
* If defined, takes precedence over `chosenRepFromBandwidth`.
*
* This is a very safe choice, yet it is very slow and might not be
* adapted to cases where a buffer cannot be build, such as live contents.
*
* `null` if this buffer size mode is not enabled or if we don't have a
* choice from it yet.
*/
let chosenRepFromBufferSize: IRepresentation | null = null;
if (
allowBufferBasedEstimates &&
currentBufferBasedEstimate !== undefined &&
currentBufferBasedEstimate > currentBestBitrate
) {
chosenRepFromBufferSize = selectOptimalRepresentation(
filteredReps,
currentBufferBasedEstimate,
);
currentBestBitrate = chosenRepFromBufferSize.bitrate;
}
/**
* Representation chosen by the more adventurous `GuessBasedChooser`,
* which iterates through Representations one by one until finding one
* that cannot be "maintained".
*
* If defined, takes precedence over both `chosenRepFromBandwidth` and
* `chosenRepFromBufferSize`.
*
* This is the riskiest choice (in terms of rebuffering chances) but is
* only enabled when no other solution is adapted (for now, this just
* applies for low-latency contents when playing close to the live
* edge).
*
* `null` if not enabled or if there's currently no guess.
*/
let chosenRepFromGuessMode: IRepresentation | null = null;
if (
lowLatencyMode &&
currentRepresentationVal !== null &&
context.manifest.isDynamic &&
maximumPosition - position.getWanted() < 40
) {
chosenRepFromGuessMode = guessBasedChooser.getGuess(
sortedRepresentations,
lastPlaybackObservation,
currentRepresentationVal,
currentBestBitrate,
requests,
);
}
if (
chosenRepFromGuessMode !== null &&
chosenRepFromGuessMode.bitrate > currentBestBitrate
) {
log.debug("ABR", "new guess-based estimate", {
bitrate: chosenRepFromGuessMode.bitrate,
representation: chosenRepFromGuessMode.id,
});
prevEstimate.update(
chosenRepFromGuessMode,
bandwidthEstimate,
ABRAlgorithmType.GuessBased,
);
return {
bitrate: bandwidthEstimate,
representation: chosenRepFromGuessMode,
urgent:
currentRepresentationVal === null ||
chosenRepFromGuessMode.bitrate < currentRepresentationVal.bitrate,
knownStableBitrate,
};
} else if (chosenRepFromBufferSize !== null) {
log.debug("ABR", "new buffer-based estimate", {
bitrate: chosenRepFromBufferSize.bitrate,
representation: chosenRepFromBufferSize.id,
});
prevEstimate.update(
chosenRepFromBufferSize,
bandwidthEstimate,
ABRAlgorithmType.BufferBased,
);
return {
bitrate: bandwidthEstimate,
representation: chosenRepFromBufferSize,
urgent: networkAnalyzer.isUrgent(
chosenRepFromBufferSize.bitrate,
currentRepresentationVal,
requests,
lastPlaybackObservation,
),
knownStableBitrate,
};
} else {
log.debug("ABR", "new bandwidth estimate", {
bitrate: chosenRepFromBandwidth.bitrate,
representation: chosenRepFromBandwidth.id,
});
prevEstimate.update(
chosenRepFromBandwidth,
bandwidthEstimate,
ABRAlgorithmType.BandwidthBased,
);
return {
bitrate: bandwidthEstimate,
representation: chosenRepFromBandwidth,
urgent: networkAnalyzer.isUrgent(
chosenRepFromBandwidth.bitrate,
currentRepresentationVal,
requests,
lastPlaybackObservation,
),
knownStableBitrate,
};
}
}
}
/**
* Stop previous estimate production (if one) and restart it considering new
* conditions (such as a new list of Representations).
*/
function restartEstimatesProductionFromCurrentConditions(): void {
const representations = representationsRef.getValue();
currentEstimatesCanceller.cancel();
currentEstimatesCanceller = new TaskCanceller();
currentEstimatesCanceller.linkToSignal(stopAllEstimates);
const newRef = createEstimateReference(
representations,
currentEstimatesCanceller.signal,
);
newRef.onUpdate(
function onNewEstimate(newEstimate: IABREstimate): void {
estimateRef.setValue(newEstimate);
},
{ clearSignal: currentEstimatesCanceller.signal, emitCurrentValue: true },
);
}
/**
* Callback to call when new metrics are available
* @param {Object} value
*/
function onMetric(value: IMetricsCallbackPayload): void {
const { requestDuration, segmentDuration, size, content } = value;
// calculate bandwidth
bandwidthEstimator.addSample(requestDuration, size);
if (!content.segment.isInit) {
// calculate "maintainability score"
const { segment, representation } = content;
if (segmentDuration === undefined && !segment.complete) {
// We cannot know the real duration of the segment
return;
}
const segDur = segmentDuration ?? segment.duration;
scoreCalculator.addSample(representation, requestDuration / 1000, segDur);
}
}
/** Callback called when a new request begins. */
function onRequestBegin(val: IRequestBeginCallbackPayload): void {
requestsStore.add(val);
}
/** Callback called when progress information is known on a pending request. */
function onRequestProgress(val: IRequestProgressCallbackPayload): void {
requestsStore.addProgress(val);
}
/** Callback called when a pending request ends. */
function onRequestEnd(val: IRequestEndCallbackPayload): void {
requestsStore.remove(val.id);
}
}
/**
* Filter representations given through filters options.
* @param {Array.<Representation>} representations
* @param {Object | undefined} resolutionLimit
* @param {number | undefined} bitrateThrottle
* @returns {Array.<Representation>}
*/
function getFilteredRepresentations(
representations: IRepresentation[],
resolutionLimit: IResolutionInfo | undefined,
bitrateThrottle: number | undefined,
): IRepresentation[] {
let filteredReps = representations;
if (bitrateThrottle !== undefined && bitrateThrottle < Infinity) {
filteredReps = filterByBitrate(filteredReps, bitrateThrottle);
}
if (resolutionLimit !== undefined) {
filteredReps = filterByResolution(filteredReps, resolutionLimit);
}
return filteredReps;
}
/**
* Adaptive BitRate estimate object.
*
* Helps the player to choose a Representation (i.e. a quality) by frequently
* measuring the current network and playback conditions.
*
* At regular intervals, an `IABREstimate` will be sent to that end.
*/
export interface IABREstimate {
/**
* If defined, the last calculated available bitrate for that type of
* content (e.g. "video, "audio"...).
* Note that it can be very different from the "real" user's bandwidth.
*
* If `undefined`, we do not currently know the bitrate for the current type.
*/
bitrate: undefined | number;
/**
* The Representation considered as the most adapted to the current network
* and playback conditions.
* `null` in the rare occurence where there is no `Representation` to choose
* from.
*/
representation: IRepresentation | null;
/**
* If `true`, the current `representation` suggested should be switched to as
* soon as possible. For example, you might want to interrupt all pending
* downloads for the current representation and replace it immediately by this
* one.
*
* If `false`, the suggested `representation` can be switched to less
* urgently. For example, pending segment requests for the current
* Representation can be finished before switching to that new Representation.
*/
urgent: boolean;
/**
* Last bitrate which was known to be "maintainable".
*
* The estimates provided though `IABREstimate` objects sometimes indicate
* that you should switch to a Representation with a much higher bitrate than
* what the network bandwidth can sustain.
* Such Representation are said to not be "maintainable": at a regular
* playback speed, we cannot maintain this Representation without buffering
* after some time.
*
* `knownStableBitrate` communicates the bitrate of the last Representation
* that was known to be maintaninable: it could reliably be played continually
* without interruption.
*
* This can be for example useful in some optimizations such as
* "fast-switching", where the player will load segments of higher quality to
* replace segments of low quality.
* Because in that case you're pushing segment on top of buffer you already
* have, you will most likely only want to do it when the Representation is
* known to be maintaninable.
*/
knownStableBitrate?: number | undefined;
}
/** Media properties `getEstimateReference` will need to keep track of. */
export interface IRepresentationEstimatorPlaybackObservation {
/**
* For the concerned media buffer, difference in seconds between the next
* position where no segment data is available and the current position.
*/
bufferGap: number;
/**
* Information on the current media position in seconds at the time of a
* Playback Observation.
*/
position: ObservationPosition;
/**
* Last "playback rate" set by the user. This is the ideal "playback rate" at
* which the media should play.
*/
speed: number;
/** `duration` property of the HTMLMediaElement on which the content plays. */
duration: number;
/** Theoretical maximum position on the content that can currently be played. */
maximumPosition: number;
}
/** Content of the `IABRMetricsEvent` event's payload. */
export interface IMetricsCallbackPayload {
/** Time the request took to perform the request, in milliseconds. */
requestDuration: number;
/** Amount of bytes downloaded with this request. */
size: number;
/** Duration of the loaded segment, in seconds. */
segmentDuration: number | undefined;
/** Context about the segment downloaded. */
content: {
representation: IRepresentation;
adaptation: IAdaptation;
segment: ISegment;
};
}
export type IRequestBeginCallbackPayload = IPendingRequestStoreBegin;
export type IRequestProgressCallbackPayload = IPendingRequestStoreProgress;
export interface IRequestEndCallbackPayload {
/**
* Same `id` value used to identify that request at the time the corresponding
* callback was called.
*/
id: string;
}
/** Object allowing to filter some Representations out based on different attributes. */
export interface IABRFiltersObject {
/**
* Filters out all Representations with a bitrate higher than this value.
* If all Representations have a bitrate higher than this value, still
* consider the Representation with the lowest bitrate.
*/
bitrate?: number;
/**
* Consider only Representations with a width either unknown or lower or equal
* to that value.
*
* As a special case, if no Representation has a width exactly equal to that
* value, the Representation(s) with the `width` immediately higher will also
* be considered.
*
* _This is usually used to filter out Representations for which the width
* is much higher than the maximum width of the screen. In such cases, there
* would be no difference between those higher-quality Representations._
*/
width?: number;
}
/** Callbacks returned by `getEstimateReference`. */
export interface IRepresentationEstimatorCallbacks {
/** Callback to call when a segment has been completely pushed to the buffer. */
addedSegment(val: IAddedSegmentCallbackPayload): void;
/** Callback to call when network metrics are available. */
metrics(val: IMetricsCallbackPayload): void;
/** Callback to call when an HTTP(S) request begins. */
requestBegin(val: IRequestBeginCallbackPayload): void;
/**
* Callback to call when an HTTP(S) request ends.
* Important: `requestEnd` should only be called for requests for which the
* `requestBegin` callback has been called. It should be called at most once.
*/
requestEnd(val: IRequestEndCallbackPayload): void;
/**
* Callback to call when progress information is available on a pending
* request.
* Important: `requestProgress` should only be called for requests for which
* the `requestBegin` callback has been called but the `requestEnd` method has
* not yet been called.
*/
requestProgress(val: IRequestProgressCallbackPayload): void;
}
export interface IAddedSegmentCallbackPayload {
/**
* The buffered ranges of the related media buffer after that segment has
* been pushed.
*/
buffered: IRange[];
/** The context for the segment that has been pushed. */
content: { representation: IRepresentation };
}
/** Arguments to give to `getEstimateReference`. */
export interface IRepresentationEstimatorArguments {
/** Class allowing to estimate the current network bandwidth. */
bandwidthEstimator: BandwidthEstimator;
/** Emit regular playback information. */
playbackObserver: IReadOnlyPlaybackObserver<IRepresentationEstimatorPlaybackObservation>;
/**
* The Representation currently loaded.
* `null` if no Representation is currently loaded.
*/
currentRepresentation: IReadOnlySharedReference<IRepresentation | null>;
/** Throttle Representation pool according to filters. */
filters: {
limitResolution: IReadOnlySharedReference<IResolutionInfo | undefined>;
throttleBitrate: IReadOnlySharedReference<number>;
};
/**
* The initial bitrate you want to start with.
*
* The highest-quality Representation with a bitrate lower-or-equal to that
* value will be chosen.
* If no Representation has bitrate lower or equal to that value, the
* Representation with the lowest bitrate will be chosen instead.
*/
initialBitrate?: number;
/**
* If `true` the content is a "low-latency" content.
* Such contents have specific settings.
*/
lowLatencyMode: boolean;
/** The list of Representations `getEstimateReference` can choose from. */
representations: IReadOnlySharedReference<IRepresentation[]>;
/** Context for the list of Representations to choose. */
context: {
/** In which Manifest the Representations are. */
manifest: IManifest;
/** In which Period the Representations are. */
period: IPeriod;
/** In which Adaptation the Representations are. */
adaptation: IAdaptation;
};
}
/**
* Type of the function returned by `createAdaptiveRepresentationSelector`,
* allowing to estimate the most adapted `Representation`.
*/
export type IRepresentationEstimator = (
/** Information on the content for which a Representation will be chosen */
context: { manifest: IManifest; period: IPeriod; adaptation: IAdaptation },
/** Reference emitting the Representation currently loaded. */
currentRepresentation: IReadOnlySharedReference<IRepresentation | null>,
/** Reference emitting the list of available Representations to choose from. */
representations: IReadOnlySharedReference<IRepresentation[]>,
/** Regularly emits playback conditions */
playbackObserver: IReadOnlyPlaybackObserver<IRepresentationEstimatorPlaybackObservation>,
/**
* After this `CancellationSignal` emits, resources will be disposed and
* estimates will stop to be emitted.
*/
stopAllEstimates: CancellationSignal,
) => IRepresentationEstimatorResponse;
/** Value returned by an `IRepresentationEstimator` */
export interface IRepresentationEstimatorResponse {
/**
* Regularly produces estimates of the best Representation to play (from the
* list given).
*/
estimates: IReadOnlySharedReference<IABREstimate>;
/**
* Callback which need to be called as soon as the corresponding events to
* obtain accurate Representation estimates.
*/
callbacks: IRepresentationEstimatorCallbacks;
}
/** Arguments received by `createAdaptiveRepresentationSelector`. */
export interface IAdaptiveRepresentationSelectorArguments {
/** Initial bitrate chosen, per type (minimum if not set) */
initialBitrates: Partial<Record<IBufferType, number>>;
/**
* Some settings can depend on wether you're playing a low-latency content.
* Set it to `true` if you're playing such content.
*/
lowLatencyMode: boolean;
/** Allows to filter which Representations can be choosen. */
throttlers: IRepresentationEstimatorThrottlers;
}
/**
* Throttlers are interfaces allowing to limit the pool of Representations
* to choose from.
*/
export interface IRepresentationEstimatorThrottlers {
/** Limit Representations based on maximum authorized resolution. */
limitResolution: Partial<
Record<IBufferType, IReadOnlySharedReference<IResolutionInfo>>
>;
/** Limit Representations based on maximum authorized bitrate. */
throttleBitrate: Partial<Record<IBufferType, IReadOnlySharedReference<number>>>;
}
export type { IResolutionInfo };