# Source: streaming/models/AastLowLatencyThroughputModel.js

    /**
     * The copyright in this software is being made available under the BSD License,
     * included below. This software may be subject to other third party and contributor
     * rights, including patent rights, and no such rights are granted under this license.
     *
     * Copyright (c) 2013, Dash Industry Forum.
     * All rights reserved.
     *
     * Redistribution and use in source and binary forms, with or without modification,
     * are permitted provided that the following conditions are met:
     *  * Redistributions of source code must retain the above copyright notice, this
     *  list of conditions and the following disclaimer.
     *  * Redistributions in binary form must reproduce the above copyright notice,
     *  this list of conditions and the following disclaimer in the documentation and/or
     *  other materials provided with the distribution.
     *  * Neither the name of Dash Industry Forum nor the names of its
     *  contributors may be used to endorse or promote products derived from this software
     *  without specific prior written permission.
     *
     *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY
     *  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     *  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
     *  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
     *  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
     *  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     *  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
     *  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     *  POSSIBILITY OF SUCH DAMAGE.
     */
    import Debug from '../../core/Debug.js';
    import FactoryMaker from '../../core/FactoryMaker.js';

    function AastLowLatencyThroughputModel() {

        const LLTM_MAX_MEASUREMENTS = 10;
        // factor (<1) is used to reduce the real needed download time when at very bleeding live edge
        const LLTM_SEMI_OPTIMISTIC_ESTIMATE_FACTOR = 0.8;
        const LLTM_OPTIMISTIC_ESTIMATE_FACTOR = 0.6;

        const LLTM_SLOW_SEGMENT_DOWNLOAD_TOLERANCE = 1.05;
        const LLTM_MAX_DELAY_MS = 250;

        const context = this.context;
        let instance;
        let logger;
        let measurements = {};

        function setup() {
            logger = Debug(context).getInstance().getLogger(instance);
        }

        /**
         * Linear regression with least squares method to get a trend function for buffer lavel at chunk receive timestamps
         * @param {*} chunkMeasurements
         * @returns linear trend function
         */
        function createBufferLevelTrendFunction(chunkMeasurements) {
            const result = {};

            let sumX = 0
            let sumY = 0
            let sumXY = 0
            let sumXSq = 0
            const N = chunkMeasurements.length

            for (var i = 0; i < N; ++i) {
                sumX += chunkMeasurements[i].chunkDownloadTimeRelativeMS;
                sumY += chunkMeasurements[i].bufferLevel;
                sumXY += chunkMeasurements[i].chunkDownloadTimeRelativeMS * chunkMeasurements[i].bufferLevel;
                sumXSq += chunkMeasurements[i].chunkDownloadTimeRelativeMS * chunkMeasurements[i].chunkDownloadTimeRelativeMS
            }

            result.m = ((sumXY - sumX * sumY / N)) / (sumXSq - sumX * sumX / N)
            result.b = sumY / N - result.m * sumX / N

            return function (x) {
                return result.m * x + result.b
            }
        }

        function isBufferSafeAndStable(lastMeasurements) {
            let isBufferSafeAndStable = true;
            let lastBitrate;
            const aveBufferLevelLastSegements = lastMeasurements.reduce((prev, curr) => prev + curr.bufferLevelAtSegmentEnd, 0) / lastMeasurements.length;
            lastMeasurements.forEach(m => {
                // inner segment buffer stability
                if (Math.abs(m.bufferLevelAtSegmentEnd / m.bufferLevelAtSegmentStart) < 0.95) {
                    isBufferSafeAndStable = false;
                }

                // inter segment buffer stability
                if (m.bufferLevelAtSegmentEnd / aveBufferLevelLastSegements < 0.8) {
                    isBufferSafeAndStable = false;
                }

                // representation bitrate remained at least constant
                if (!lastBitrate) {
                    lastBitrate = m.bitrate;
                } else if (lastBitrate > m.bitrate) {
                    isBufferSafeAndStable = false;
                }
            });
            return isBufferSafeAndStable;
        }

        /**
         * Based on the MPD, timing and buffer information of the last recent segments and their chunks
         * the most stable download time (in milliseconds) is calculated.
         * @param {*} request HTTPLoader request object
         * @returns download time in milliseconds of last fetched segment
         */
        function getEstimatedDownloadDurationMS(request) {
            const lastMeasurement = measurements[request.mediaType].slice(-1).pop();
            const lastThreeMeasurements = measurements[request.mediaType].slice(-3)

            // calculate and remember the buffer level trend during the last fetched segment
            const lastChunkRelativeTimeMS = lastMeasurement.chunkMeasurements.slice(-1).pop().chunkDownloadTimeRelativeMS;
            lastMeasurement.bufferLevelAtSegmentStart = lastMeasurement.getEstimatedBufferLevel(lastChunkRelativeTimeMS / 2);
            lastMeasurement.bufferLevelAtSegmentEnd = lastMeasurement.getEstimatedBufferLevel(lastChunkRelativeTimeMS);

            const isBufferStable = isBufferSafeAndStable(lastThreeMeasurements);

            const selectedOptimisticFactor = isBufferStable ? LLTM_OPTIMISTIC_ESTIMATE_FACTOR : LLTM_SEMI_OPTIMISTIC_ESTIMATE_FACTOR;

            // fetch duration was longer than segment duration, but buffer was stable
            if (lastMeasurement.isBufferStable && lastMeasurement.segDurationMS * LLTM_SLOW_SEGMENT_DOWNLOAD_TOLERANCE < lastMeasurement.fetchDownloadDurationMS) {
                return lastMeasurement.fetchDownloadDurationMS;
            }
            // buffer is drying or fetch took too long
            if (!isBufferStable || lastMeasurement.segDurationMS < lastMeasurement.fetchDownloadDurationMS) {
                return lastMeasurement.fetchDownloadDurationMS * LLTM_SEMI_OPTIMISTIC_ESTIMATE_FACTOR;
            }

            // did we requested a fully available segment? -> most accurate throughput calculation
            // we use adjusted availability start time to decide
            // Note: this "download mode" usually happens at startup and if requests are delayed artificially
            if (lastMeasurement.adjustedAvailabilityStartTimeMS <= (lastMeasurement.requestTimeMS + lastMeasurement.throughputCapacityDelayMS) - lastMeasurement.segDurationMS) {
                return lastMeasurement.fetchDownloadDurationMS * LLTM_SEMI_OPTIMISTIC_ESTIMATE_FACTOR;
            }

            // get all chunks that have been downloaded before fetch reached bleeding live edge
            // the remaining chunks loaded at production rate we will approximated
            const chunkAvailablePeriod = (lastMeasurement.requestTimeMS + lastMeasurement.throughputCapacityDelayMS) - lastMeasurement.adjustedAvailabilityStartTimeMS;
            let chunkBytesBBLE = 0; // BBLE -> Before bleeding live edge
            let chunkDownloadtimeMSBBLE = 0;
            let chunkCount = 0;
            for (let index = 0; index < lastMeasurement.chunkMeasurements.length; index++) {
                const chunk = lastMeasurement.chunkMeasurements[index];
                if (chunkAvailablePeriod < chunkDownloadtimeMSBBLE + chunk.chunkDownloadDurationMS) {
                    break;
                }
                chunkDownloadtimeMSBBLE += chunk.chunkDownloadDurationMS;
                chunkBytesBBLE += chunk.chunkBytes;
                chunkCount++;
            }

            if (chunkAvailablePeriod < 0) {
                logger.warn('request time was before adjusted availibitly start time');
            }

            // there have to be some chunks available (20% of max count)
            // otherwise we are at bleeding live edge and the few chunks are insufficient to estimate correctly
            if (chunkBytesBBLE && chunkDownloadtimeMSBBLE && chunkCount > lastMeasurement.chunkMeasurements.length * 0.2) {
                const downloadThroughput = chunkBytesBBLE / chunkDownloadtimeMSBBLE; // bytes per millesecond
                const estimatedDownloadtimeMS = lastMeasurement.segmentBytes / downloadThroughput;
                // if real download was shorter then report this incl. semi optimistical estimate factor
                if (lastMeasurement.fetchDownloadDurationMS < estimatedDownloadtimeMS) {
                    return lastMeasurement.fetchDownloadDurationMS * selectedOptimisticFactor;
                }
                return estimatedDownloadtimeMS * selectedOptimisticFactor;
            }

            // when we are to tight at live edge and it's stable then
            // we start to optimistically estimate download time
            // in such a way that a switch to next rep will be possible
            // optimistical estimate: assume download was fast enough for next higher rendition
            let nextHigherBitrate = lastMeasurement.bitrate;
            lastMeasurement.bitrateList.some(b => {
                if (b.bandwidth > lastMeasurement.bitrate) {
                    nextHigherBitrate = b.bandwidth;
                    return true;
                }
            });
            // already highest bitrate?

            if (nextHigherBitrate === lastMeasurement.bitrate) {
                return lastMeasurement.fetchDownloadDurationMS * selectedOptimisticFactor;
            }
            return selectedOptimisticFactor * lastMeasurement.segmentBytes * 8 * 1000 / nextHigherBitrate;
        }

        /**
         * Get calculated value for a safe artificial delay of the next request to allow to accumulate some chunks.
         * This allows better line throughput measurement.
         * @param {*} request
         * @param {*} currentBufferLevel current buffer level in milliseconds
         * @returns delay in milliseconds
         */
        function getThroughputCapacityDelayMS(request, currentBufferLevelMS) {
            const lastThreeMeasurements = measurements[request.mediaType] && measurements[request.mediaType].slice(-3);

            if (!lastThreeMeasurements || lastThreeMeasurements.length < 3) {
                return 0;
            }

            // in case not stable buffer, no artificially delay for the next request
            if (!isBufferSafeAndStable(lastThreeMeasurements)) {
                return 0;
            }

            // allowed artificial delay is the min of quater of buffer level in milliseconds and LLTM_MAX_DELAY_MS
            return currentBufferLevelMS / 4 > LLTM_MAX_DELAY_MS ? LLTM_MAX_DELAY_MS : currentBufferLevelMS / 4;
        }

        /**
         * Add some measurement data for bookkeeping and being able to derive decisions on estimated throughput.
         * @param {*} request HTTPLoader object to get MPD and media info from
         * @param {*} fetchDownloadDurationMS Duration how long the fetch actually took
         * @param {*} chunkMeasurements Array containing chunk timings and buffer levels
         * @param {*} requestTimeMS Timestamp at which the fetch was initiated
         * @param {*} throughputCapacityDelayMS An artificial delay that was used for this request
         */
        function addMeasurement(request, fetchDownloadDurationMS, chunkMeasurements, requestTimeMS, throughputCapacityDelayMS) {
            if (request && request.mediaType && !measurements[request.mediaType]) {
                measurements[request.mediaType] = [];
            }
            const bitrateEntry = request.representation.mediaInfo.bitrateList.find(item => item.id === request.representation.id);
            measurements[request.mediaType].push({
                index: request.index,
                repId: request.representationId,
                mediaType: request.mediaType,
                requestTimeMS,
                adjustedAvailabilityStartTimeMS: request.availabilityStartTime.getTime(),
                segDurationMS: request.duration * 1000,
                chunksDurationMS: chunkMeasurements.reduce((prev, curr) => prev + curr.chunkDownloadDurationMS, 0),
                segmentBytes: chunkMeasurements.reduce((prev, curr) => prev + curr.chunkBytes, 0),
                bitrate: bitrateEntry && bitrateEntry.bandwidth,
                bitrateList: request.representation.mediaInfo.bitrateList,
                chunkMeasurements,
                fetchDownloadDurationMS,
                throughputCapacityDelayMS,
                getEstimatedBufferLevel: createBufferLevelTrendFunction(chunkMeasurements.slice(1)) // don't use first chunk's buffer level
            });
            // maintain only a maximum amount of most recent measurements
            if (measurements[request.mediaType].length > LLTM_MAX_MEASUREMENTS) {
                measurements[request.mediaType].shift();
            }
        }

        instance = {
            addMeasurement,
            getEstimatedDownloadDurationMS,
            getThroughputCapacityDelayMS,
            setup,
        };

        setup()

        return instance;
    }

    AastLowLatencyThroughputModel.__dashjs_factory_name = 'AastLowLatencyThroughputModel';
    export default FactoryMaker.getSingletonFactory(AastLowLatencyThroughputModel);

#### Search results