/** * 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 log from "../../../log"; import type { IRepresentation } from "../../../manifest"; import EWMA from "./ewma"; /** * Object representing a maintainability score as calculated by the * `RepresentationScoreCalculator`. */ export interface IRepresentationMaintainabilityScore { /** * Weighted mean of dividing the loaded segment's duration by the time to make * their request. */ score: number; /** * The confidence we have on the calculated `score` in reflecting a useful * maintainability hint for the concerned Representation. * * Basically, the more segments have been loaded, the higher the confidence. */ confidenceLevel: ScoreConfidenceLevel; } /** * Calculate the "maintainability score" of a given Representation: * - A score higher than 1 means that the Representation can theorically * be downloaded faster than the duration of the media it represents. * (e.g. a segment representing 4 seconds can be downloaded in less than 4 * seconds). * - A score lower or equal to 1 means that the Representation cannot be * downloaded * * The score follows a simple linear relation to both variables it is based * on: * - if n seconds of content can be downloaded in 2*n seconds, the score will * be `0.5`. * - if n seconds of content can be downloaded in n seconds, the score will be * `1`. * - if n seconds of content can be downloaded in n/2 seconds, the score will * be `2`. * - ... * * The score is mainly here to tell you when your buffer-based guesses are * actually higher than the quality you should normally reach. * * /!\ Please bear in mind that we don't consider the playback rate in those * operations. * Still, integrating the playback rate a posteriori should not be difficult * (e.g. you can just divide the score by that rate). * * @class RepresentationScoreCalculator */ export default class RepresentationScoreCalculator { private _currentRepresentationData: { representation: IRepresentation; ewma: EWMA; loadedSegments: number; loadedDuration: number; } | null; private _lastRepresentationWithGoodScore: IRepresentation | null; constructor() { this._currentRepresentationData = null; this._lastRepresentationWithGoodScore = null; } /** * Add new sample data. * @param {Object} representation * @param {number} requestDuration - duration taken for doing the request for * the whole segment. * @param {number} segmentDuration - media duration of the whole segment, in * seconds. */ public addSample( representation: IRepresentation, requestDuration: number, segmentDuration: number, ): void { const ratio = segmentDuration / requestDuration; const currentRep = this._currentRepresentationData; let currentEWMA: EWMA; if (currentRep !== null && currentRep.representation.id === representation.id) { currentEWMA = currentRep.ewma; currentRep.ewma.addSample(requestDuration, ratio); currentRep.loadedDuration += segmentDuration; currentRep.loadedSegments++; } else { currentEWMA = new EWMA(5); currentEWMA.addSample(requestDuration, ratio); this._currentRepresentationData = { representation, ewma: currentEWMA, loadedDuration: segmentDuration, loadedSegments: 0, }; } if ( currentEWMA.getEstimate() > 1 && this._lastRepresentationWithGoodScore !== representation ) { log.debug("ABR", "New last stable representation", { bitrate: representation.bitrate, }); this._lastRepresentationWithGoodScore = representation; } } /** * Get score estimate for the given Representation. * undefined if no estimate is available. * @param {Object} representation * @returns {number|undefined} */ public getEstimate( representation: IRepresentation, ): IRepresentationMaintainabilityScore | undefined { if ( this._currentRepresentationData === null || this._currentRepresentationData.representation.id !== representation.id ) { return undefined; } const { ewma, loadedSegments, loadedDuration } = this._currentRepresentationData; const estimate = ewma.getEstimate(); const confidenceLevel = loadedSegments >= 5 && loadedDuration >= 10 ? ScoreConfidenceLevel.HIGH : ScoreConfidenceLevel.LOW; return { score: estimate, confidenceLevel }; } /** * Returns last Representation which had reached a score superior to 1. * This Representation is the last known one which could be maintained. * Useful to know if a current guess is higher than what you should * normally be able to play. * `null` if no Representation ever reach that score. * @returns {Object|null} */ public getLastStableRepresentation(): IRepresentation | null { return this._lastRepresentationWithGoodScore; } } /** * Value representing the level of confidence to have on a given score. * A low confidence level is most likely linked to too few samples to be relied * on without adapted considerations. */ export const enum ScoreConfidenceLevel { /** High confidence level: the score can be relied on. */ HIGH = 1, /** Low confidence level: the score may still incorrectly reflect the truth. */ LOW = 0, }