UNPKG

dashjs

Version:

A reference client implementation for the playback of MPEG DASH via Javascript and compliant browsers.

756 lines (627 loc) 40 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title>dash.js Source: streaming/net/FetchLoader.js</title> <!--[if lt IE 9]> <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <link type="text/css" rel="stylesheet" href="styles/sunlight.default.css"> <link type="text/css" rel="stylesheet" href="styles/site.spacelab.css"> </head> <body> <div class="navbar navbar-default navbar-fixed-top navbar-inverse"> <div class="container"> <div class="navbar-header"> <a class="navbar-brand" href="index.html">dash.js</a> <button class="navbar-toggle" type="button" data-toggle="collapse" data-target="#topNavigation"> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> </div> <div class="navbar-collapse collapse" id="topNavigation"> <ul class="nav navbar-nav"> <li class="dropdown"> <a href="modules.list.html" class="dropdown-toggle" data-toggle="dropdown">Modules<b class="caret"></b></a> <ul class="dropdown-menu "> <li><a href="module-DashAdapter.html">DashAdapter</a></li><li><a href="module-DashMetrics.html">DashMetrics</a></li><li><a href="module-MediaPlayer.html">MediaPlayer</a></li><li><a href="module-OfflineController.html">OfflineController</a></li><li><a href="module-ProtectionController.html">ProtectionController</a></li><li><a href="module-Settings.html">Settings</a></li> </ul> </li> <li class="dropdown"> <a href="classes.list.html" class="dropdown-toggle" data-toggle="dropdown">Classes<b class="caret"></b></a> <ul class="dropdown-menu "> <li><a href="Errors.html">Errors</a></li><li><a href="MediaPlayerEvents.html">MediaPlayerEvents</a></li><li><a href="MediaPlayerModel.html">MediaPlayerModel</a></li><li><a href="MetricsReportingEvents.html">MetricsReportingEvents</a></li><li><a href="MssErrors.html">MssErrors</a></li><li><a href="OfflineErrors.html">OfflineErrors</a></li><li><a href="OfflineEvents.html">OfflineEvents</a></li><li><a href="ProtectionErrors.html">ProtectionErrors</a></li><li><a href="ProtectionEvents.html">ProtectionEvents</a></li><li><a href="ThroughputController.html">ThroughputController</a></li><li><a href="ThroughputModel.html">ThroughputModel</a></li> </ul> </li> <li class="dropdown"> <a href="events.list.html" class="dropdown-toggle" data-toggle="dropdown">Events<b class="caret"></b></a> <ul class="dropdown-menu "> <li><a href="MediaPlayerEvents.html#event:ADAPTATION_SET_REMOVED_NO_CAPABILITIES">MediaPlayerEvents#event:ADAPTATION_SET_REMOVED_NO_CAPABILITIES</a></li><li><a href="MediaPlayerEvents.html#event:AST_IN_FUTURE">MediaPlayerEvents#event:AST_IN_FUTURE</a></li><li><a href="MediaPlayerEvents.html#event:BASE_URLS_UPDATED">MediaPlayerEvents#event:BASE_URLS_UPDATED</a></li><li><a href="MediaPlayerEvents.html#event:BUFFER_EMPTY">MediaPlayerEvents#event:BUFFER_EMPTY</a></li><li><a href="MediaPlayerEvents.html#event:BUFFER_LEVEL_STATE_CHANGED">MediaPlayerEvents#event:BUFFER_LEVEL_STATE_CHANGED</a></li><li><a href="MediaPlayerEvents.html#event:BUFFER_LEVEL_UPDATED">MediaPlayerEvents#event:BUFFER_LEVEL_UPDATED</a></li><li><a href="MediaPlayerEvents.html#event:BUFFER_LOADED">MediaPlayerEvents#event:BUFFER_LOADED</a></li><li><a href="MediaPlayerEvents.html#event:CAN_PLAY">MediaPlayerEvents#event:CAN_PLAY</a></li><li><a href="MediaPlayerEvents.html#event:CAN_PLAY_THROUGH">MediaPlayerEvents#event:CAN_PLAY_THROUGH</a></li><li><a href="MediaPlayerEvents.html#event:CAPTION_CONTAINER_RESIZE">MediaPlayerEvents#event:CAPTION_CONTAINER_RESIZE</a></li><li><a href="MediaPlayerEvents.html#event:CAPTION_RENDERED">MediaPlayerEvents#event:CAPTION_RENDERED</a></li><li><a href="MediaPlayerEvents.html#event:CONFORMANCE_VIOLATION">MediaPlayerEvents#event:CONFORMANCE_VIOLATION</a></li><li><a href="MediaPlayerEvents.html#event:CONTENT_STEERING_REQUEST_COMPLETED">MediaPlayerEvents#event:CONTENT_STEERING_REQUEST_COMPLETED</a></li><li><a href="MediaPlayerEvents.html#event:DYNAMIC_TO_STATIC">MediaPlayerEvents#event:DYNAMIC_TO_STATIC</a></li><li><a href="MediaPlayerEvents.html#event:ERROR">MediaPlayerEvents#event:ERROR</a></li><li><a href="MediaPlayerEvents.html#event:EVENT_MODE_ON_RECEIVE">MediaPlayerEvents#event:EVENT_MODE_ON_RECEIVE</a></li><li><a href="MediaPlayerEvents.html#event:EVENT_MODE_ON_START">MediaPlayerEvents#event:EVENT_MODE_ON_START</a></li><li><a href="MediaPlayerEvents.html#event:FRAGMENT_LOADING_ABANDONED">MediaPlayerEvents#event:FRAGMENT_LOADING_ABANDONED</a></li><li><a href="MediaPlayerEvents.html#event:FRAGMENT_LOADING_COMPLETED">MediaPlayerEvents#event:FRAGMENT_LOADING_COMPLETED</a></li><li><a href="MediaPlayerEvents.html#event:FRAGMENT_LOADING_PROGRESS">MediaPlayerEvents#event:FRAGMENT_LOADING_PROGRESS</a></li><li><a href="MediaPlayerEvents.html#event:FRAGMENT_LOADING_STARTED">MediaPlayerEvents#event:FRAGMENT_LOADING_STARTED</a></li><li><a href="MediaPlayerEvents.html#event:INBAND_PRFT">MediaPlayerEvents#event:INBAND_PRFT</a></li><li><a href="MediaPlayerEvents.html#event:LOG">MediaPlayerEvents#event:LOG</a></li><li><a href="MediaPlayerEvents.html#event:MANIFEST_LOADED">MediaPlayerEvents#event:MANIFEST_LOADED</a></li><li><a href="MediaPlayerEvents.html#event:MANIFEST_LOADING_FINISHED">MediaPlayerEvents#event:MANIFEST_LOADING_FINISHED</a></li><li><a href="MediaPlayerEvents.html#event:MANIFEST_LOADING_STARTED">MediaPlayerEvents#event:MANIFEST_LOADING_STARTED</a></li><li><a href="MediaPlayerEvents.html#event:MANIFEST_VALIDITY_CHANGED">MediaPlayerEvents#event:MANIFEST_VALIDITY_CHANGED</a></li><li><a href="MediaPlayerEvents.html#event:METRIC_ADDED">MediaPlayerEvents#event:METRIC_ADDED</a></li><li><a href="MediaPlayerEvents.html#event:METRIC_CHANGED">MediaPlayerEvents#event:METRIC_CHANGED</a></li><li><a href="MediaPlayerEvents.html#event:METRIC_UPDATED">MediaPlayerEvents#event:METRIC_UPDATED</a></li><li><a href="MediaPlayerEvents.html#event:METRICS_CHANGED">MediaPlayerEvents#event:METRICS_CHANGED</a></li><li><a href="MediaPlayerEvents.html#event:PERIOD_SWITCH_COMPLETED">MediaPlayerEvents#event:PERIOD_SWITCH_COMPLETED</a></li><li><a href="MediaPlayerEvents.html#event:PERIOD_SWITCH_STARTED">MediaPlayerEvents#event:PERIOD_SWITCH_STARTED</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_ENDED">MediaPlayerEvents#event:PLAYBACK_ENDED</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_ERROR">MediaPlayerEvents#event:PLAYBACK_ERROR</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_LOADED_DATA">MediaPlayerEvents#event:PLAYBACK_LOADED_DATA</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_METADATA_LOADED">MediaPlayerEvents#event:PLAYBACK_METADATA_LOADED</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_NOT_ALLOWED">MediaPlayerEvents#event:PLAYBACK_NOT_ALLOWED</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_PAUSED">MediaPlayerEvents#event:PLAYBACK_PAUSED</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_PLAYING">MediaPlayerEvents#event:PLAYBACK_PLAYING</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_PROGRESS">MediaPlayerEvents#event:PLAYBACK_PROGRESS</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_RATE_CHANGED">MediaPlayerEvents#event:PLAYBACK_RATE_CHANGED</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_SEEKED">MediaPlayerEvents#event:PLAYBACK_SEEKED</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_SEEKING">MediaPlayerEvents#event:PLAYBACK_SEEKING</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_STALLED">MediaPlayerEvents#event:PLAYBACK_STALLED</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_STARTED">MediaPlayerEvents#event:PLAYBACK_STARTED</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_TIME_UPDATED">MediaPlayerEvents#event:PLAYBACK_TIME_UPDATED</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_VOLUME_CHANGED">MediaPlayerEvents#event:PLAYBACK_VOLUME_CHANGED</a></li><li><a href="MediaPlayerEvents.html#event:PLAYBACK_WAITING">MediaPlayerEvents#event:PLAYBACK_WAITING</a></li><li><a href="MediaPlayerEvents.html#event:QUALITY_CHANGE_RENDERED">MediaPlayerEvents#event:QUALITY_CHANGE_RENDERED</a></li><li><a href="MediaPlayerEvents.html#event:QUALITY_CHANGE_REQUESTED">MediaPlayerEvents#event:QUALITY_CHANGE_REQUESTED</a></li><li><a href="MediaPlayerEvents.html#event:REPRESENTATION_SWITCH">MediaPlayerEvents#event:REPRESENTATION_SWITCH</a></li><li><a href="MediaPlayerEvents.html#event:STREAM_ACTIVATED">MediaPlayerEvents#event:STREAM_ACTIVATED</a></li><li><a href="MediaPlayerEvents.html#event:STREAM_DEACTIVATED">MediaPlayerEvents#event:STREAM_DEACTIVATED</a></li><li><a href="MediaPlayerEvents.html#event:STREAM_INITIALIZED">MediaPlayerEvents#event:STREAM_INITIALIZED</a></li><li><a href="MediaPlayerEvents.html#event:STREAM_INITIALIZING">MediaPlayerEvents#event:STREAM_INITIALIZING</a></li><li><a href="MediaPlayerEvents.html#event:STREAM_TEARDOWN_COMPLETE">MediaPlayerEvents#event:STREAM_TEARDOWN_COMPLETE</a></li><li><a href="MediaPlayerEvents.html#event:STREAM_UPDATED">MediaPlayerEvents#event:STREAM_UPDATED</a></li><li><a href="MediaPlayerEvents.html#event:TEXT_TRACK_ADDED">MediaPlayerEvents#event:TEXT_TRACK_ADDED</a></li><li><a href="MediaPlayerEvents.html#event:TEXT_TRACKS_ADDED">MediaPlayerEvents#event:TEXT_TRACKS_ADDED</a></li><li><a href="MediaPlayerEvents.html#event:THROUGHPUT_MEASUREMENT_STORED">MediaPlayerEvents#event:THROUGHPUT_MEASUREMENT_STORED</a></li><li><a href="MediaPlayerEvents.html#event:TRACK_CHANGE_RENDERED">MediaPlayerEvents#event:TRACK_CHANGE_RENDERED</a></li><li><a href="MediaPlayerEvents.html#event:TTML_PARSED">MediaPlayerEvents#event:TTML_PARSED</a></li><li><a href="MediaPlayerEvents.html#event:TTML_TO_PARSE">MediaPlayerEvents#event:TTML_TO_PARSE</a></li><li><a href="MetricsReportingEvents.html#event:CMCD_DATA_GENERATED">MetricsReportingEvents#event:CMCD_DATA_GENERATED</a></li><li><a href="OfflineEvents.html#event:OFFLINE_RECORD_FINISHED">OfflineEvents#event:OFFLINE_RECORD_FINISHED</a></li><li><a href="OfflineEvents.html#event:OFFLINE_RECORD_LOADEDMETADATA">OfflineEvents#event:OFFLINE_RECORD_LOADEDMETADATA</a></li><li><a href="OfflineEvents.html#event:OFFLINE_RECORD_STARTED">OfflineEvents#event:OFFLINE_RECORD_STARTED</a></li><li><a href="OfflineEvents.html#event:OFFLINE_RECORD_STOPPED">OfflineEvents#event:OFFLINE_RECORD_STOPPED</a></li><li><a href="ProtectionEvents.html#event:KEY_ADDED">ProtectionEvents#event:KEY_ADDED</a></li><li><a href="ProtectionEvents.html#event:KEY_ERROR">ProtectionEvents#event:KEY_ERROR</a></li><li><a href="ProtectionEvents.html#event:KEY_MESSAGE">ProtectionEvents#event:KEY_MESSAGE</a></li><li><a href="ProtectionEvents.html#event:KEY_SESSION_CLOSED">ProtectionEvents#event:KEY_SESSION_CLOSED</a></li><li><a href="ProtectionEvents.html#event:KEY_SESSION_CREATED">ProtectionEvents#event:KEY_SESSION_CREATED</a></li><li><a href="ProtectionEvents.html#event:KEY_SESSION_REMOVED">ProtectionEvents#event:KEY_SESSION_REMOVED</a></li><li><a href="ProtectionEvents.html#event:KEY_STATUSES_CHANGED">ProtectionEvents#event:KEY_STATUSES_CHANGED</a></li><li><a href="ProtectionEvents.html#event:KEY_SYSTEM_ACCESS_COMPLETE">ProtectionEvents#event:KEY_SYSTEM_ACCESS_COMPLETE</a></li><li><a href="ProtectionEvents.html#event:KEY_SYSTEM_SELECTED">ProtectionEvents#event:KEY_SYSTEM_SELECTED</a></li><li><a href="ProtectionEvents.html#event:LICENSE_REQUEST_COMPLETE">ProtectionEvents#event:LICENSE_REQUEST_COMPLETE</a></li><li><a href="ProtectionEvents.html#event:LICENSE_REQUEST_SENDING">ProtectionEvents#event:LICENSE_REQUEST_SENDING</a></li><li><a href="ProtectionEvents.html#event:PROTECTION_CREATED">ProtectionEvents#event:PROTECTION_CREATED</a></li><li><a href="ProtectionEvents.html#event:PROTECTION_DESTROYED">ProtectionEvents#event:PROTECTION_DESTROYED</a></li> </ul> </li> <li class="dropdown"> <a href="global.html" class="dropdown-toggle" data-toggle="dropdown">Global<b class="caret"></b></a> <ul class="dropdown-menu "> <li><a href="global.html#LICENSE_SERVER_MANIFEST_CONFIGURATIONS">LICENSE_SERVER_MANIFEST_CONFIGURATIONS</a></li> </ul> </li> </ul> <div class="col-sm-3 col-md-3"> <form class="navbar-form" role="search"> <div class="input-group"> <input type="text" class="form-control" placeholder="Search" name="q" id="search-input"> <div class="input-group-btn"> <button class="btn btn-default" id="search-submit"><i class="glyphicon glyphicon-search"></i></button> </div> </div> </form> </div> </div> </div> </div> <div class="container" id="toc-content"> <div class="row"> <div class="col-md-12"> <div id="main"> <h1 class="page-title">Source: streaming/net/FetchLoader.js</h1> <section> <article> <pre class="sunlight-highlight-javascript linenums">/** * 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 FactoryMaker from '../../core/FactoryMaker.js'; import Settings from '../../core/Settings.js'; import Constants from '../constants/Constants.js'; import AastLowLatencyThroughputModel from '../models/AastLowLatencyThroughputModel.js'; /** * @module FetchLoader * @ignore * @description Manages download of resources via HTTP using fetch. */ function FetchLoader() { const context = this.context; const aastLowLatencyThroughputModel = AastLowLatencyThroughputModel(context).getInstance(); const settings = Settings(context).getInstance(); let instance, dashMetrics, boxParser; function setConfig(cfg) { dashMetrics = cfg.dashMetrics; boxParser = cfg.boxParser } /** * Load request * @param {CommonMediaLibrary.request.CommonMediaRequest} httpRequest * @param {CommonMediaLibrary.request.CommonMediaResponse} httpResponse */ function load(httpRequest, httpResponse) { // Variables will be used in the callback functions const fragmentRequest = httpRequest.customData.request; const headers = new Headers(); if (httpRequest.headers) { for (let header in httpRequest.headers) { let value = httpRequest.headers[header]; if (value) { headers.append(header, value); } } } let abortController; if (typeof window.AbortController === 'function') { abortController = new AbortController(); /*jshint ignore:line*/ httpRequest.customData.abortController = abortController; abortController.signal.onabort = httpRequest.customData.onabort; } httpRequest.customData.abort = abort.bind(httpRequest); const reqOptions = { method: httpRequest.method, headers: headers, credentials: httpRequest.credentials, signal: abortController ? abortController.signal : undefined }; const calculationMode = settings.get().streaming.abr.throughput.lowLatencyDownloadTimeCalculationMode; const requestTime = performance.now(); let throughputCapacityDelayMS = 0; new Promise((resolve) => { if (calculationMode === Constants.LOW_LATENCY_DOWNLOAD_TIME_CALCULATION_MODE.AAST &amp;&amp; aastLowLatencyThroughputModel) { throughputCapacityDelayMS = aastLowLatencyThroughputModel.getThroughputCapacityDelayMS(fragmentRequest, dashMetrics.getCurrentBufferLevel(fragmentRequest.mediaType) * 1000); if (throughputCapacityDelayMS) { // safely delay the "fetch" call a bit to be able to measure the throughput capacity of the line. // this will lead to first few chunks downloaded at max network speed return setTimeout(resolve, throughputCapacityDelayMS); } } resolve(); }) .then(() => { let markBeforeFetch = performance.now(); fetch(httpRequest.url, reqOptions) .then((response) => { httpResponse.status = response.status; httpResponse.statusText = response.statusText; httpResponse.url = response.url; if (!response.ok) { httpRequest.customData.onerror(); } const responseHeaders = {}; for (const key of response.headers.keys()) { responseHeaders[key] = response.headers.get(key); } httpResponse.headers = responseHeaders; const totalBytes = parseInt(response.headers.get('Content-Length'), 10); let bytesReceived = 0; let signaledFirstByte = false; let receivedData = new Uint8Array(); let offset = 0; if (calculationMode === Constants.LOW_LATENCY_DOWNLOAD_TIME_CALCULATION_MODE.AAST &amp;&amp; aastLowLatencyThroughputModel) { _aastProcessResponse(markBeforeFetch, httpRequest, requestTime, throughputCapacityDelayMS, responseHeaders, response); } else { httpRequest.customData.reader = response.body.getReader(); } let downloadedData = []; let moofStartTimeData = []; let mdatEndTimeData = []; let lastChunkWasFinished = true; /** * Callback function for the reader. * @param value - some data. Always undefined when done is true. * @param done - true if the stream has already given you all its data. */ const _processResult = ({ value, done }) => { // Bug fix Parse whenever data is coming [value] better than 1ms looking that increase CPU if (done) { _handleRequestComplete() return; } if (value &amp;&amp; value.length > 0) { _handleDataReceived(value) } _read(httpRequest, httpResponse, _processResult); }; /** * Once a request is completed throw final progress event with the calculated bytes and download time * @private */ function _handleRequestComplete() { if (receivedData) { if (calculationMode !== Constants.LOW_LATENCY_DOWNLOAD_TIME_CALCULATION_MODE.AAST) { // If there is pending data, call progress so network metrics // are correctly generated // Same structure as https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequestEventTarget/ let calculatedThroughput = null; let calculatedTime = null; if (calculationMode === Constants.LOW_LATENCY_DOWNLOAD_TIME_CALCULATION_MODE.MOOF_PARSING) { calculatedThroughput = _calculateThroughputByChunkData(moofStartTimeData, mdatEndTimeData); if (calculatedThroughput) { calculatedTime = bytesReceived * 8 / calculatedThroughput; } } else if (calculationMode === Constants.LOW_LATENCY_DOWNLOAD_TIME_CALCULATION_MODE.DOWNLOADED_DATA) { calculatedTime = calculateDownloadedTime(downloadedData, bytesReceived); } httpRequest.customData.onprogress({ loaded: bytesReceived, total: isNaN(totalBytes) ? bytesReceived : totalBytes, lengthComputable: true, time: calculatedTime }); } httpResponse.data = receivedData.buffer; } httpRequest.customData.onload(); httpRequest.customData.onloadend(); } /** * Called every time we received data * @param value * @private */ function _handleDataReceived(value) { receivedData = _concatTypedArray(receivedData, value); bytesReceived += value.length; downloadedData.push({ ts: performance.now(), bytes: value.length }); if (calculationMode === Constants.LOW_LATENCY_DOWNLOAD_TIME_CALCULATION_MODE.MOOF_PARSING &amp;&amp; lastChunkWasFinished) { // Parse the payload and capture the 'moof' box const boxesInfo = boxParser.findLastTopIsoBoxCompleted(['moof'], receivedData, offset); if (boxesInfo.found) { // Store the beginning time of each chunk download in array StartTimeData lastChunkWasFinished = false; moofStartTimeData.push({ ts: performance.now(), bytes: value.length }); } } const boxesInfo = boxParser.findLastTopIsoBoxCompleted(['moov', 'mdat'], receivedData, offset); if (boxesInfo.found) { const endOfLastBox = boxesInfo.lastCompletedOffset + boxesInfo.size; // Store the end time of each chunk download with its size in array EndTimeData if (calculationMode === Constants.LOW_LATENCY_DOWNLOAD_TIME_CALCULATION_MODE.MOOF_PARSING &amp;&amp; !lastChunkWasFinished) { lastChunkWasFinished = true; mdatEndTimeData.push({ ts: performance.now(), bytes: receivedData.length }); } // Make the data that we received available for playback // If we are going to pass full buffer, avoid copying it and pass // complete buffer. Otherwise, clone the part of the buffer that is completed // and adjust remaining buffer. A clone is needed because ArrayBuffer of a typed-array // keeps a reference to the original data let data; if (endOfLastBox === receivedData.length) { data = receivedData; receivedData = new Uint8Array(); } else { data = new Uint8Array(receivedData.subarray(0, endOfLastBox)); receivedData = receivedData.subarray(endOfLastBox); } // Announce progress but don't track traces. Throughput measures are quite unstable // when they are based in small amount of data httpRequest.customData.onprogress({ data: data.buffer, lengthComputable: false, noTrace: true }); offset = 0; } else { offset = boxesInfo.lastCompletedOffset; // Call progress, so it generates traces that will be later used to know when the first byte // were received if (!signaledFirstByte) { httpRequest.customData.onprogress({ lengthComputable: false, noTrace: true }); signaledFirstByte = true; } } } _read(httpRequest, httpResponse, _processResult); }) .catch(function (e) { if (httpRequest.customData.onerror) { httpRequest.customData.onerror(e); } }); }); } function _aastProcessResponse(markBeforeFetch, httpRequest, requestTime, throughputCapacityDelayMS, responseHeaders, response) { let markA = markBeforeFetch; let markB = 0; function fetchMeassurement(stream) { const reader = stream.getReader(); const measurement = []; const fragmentRequest = httpRequest.customData.request; reader.read() .then(function processFetch(args) { const value = args.value; const done = args.done; markB = performance.now(); if (value &amp;&amp; value.length) { const chunkDownloadDurationMS = markB - markA; const chunkBytes = value.length; measurement.push({ chunkDownloadTimeRelativeMS: markB - markBeforeFetch, chunkDownloadDurationMS, chunkBytes, kbps: Math.round(8 * chunkBytes / (chunkDownloadDurationMS / 1000)), bufferLevel: dashMetrics.getCurrentBufferLevel(fragmentRequest.mediaType) }); } if (done) { const fetchDuration = markB - markBeforeFetch; const bytesAllChunks = measurement.reduce((prev, curr) => prev + curr.chunkBytes, 0); aastLowLatencyThroughputModel.addMeasurement(fragmentRequest, fetchDuration, measurement, requestTime, throughputCapacityDelayMS, responseHeaders); httpRequest.progress({ loaded: bytesAllChunks, total: bytesAllChunks, lengthComputable: true, time: aastLowLatencyThroughputModel.getEstimatedDownloadDurationMS(fragmentRequest) }); return; } markA = performance.now(); return reader.read().then(processFetch); }); } // tee'ing streams is supported by all current major browsers // https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/tee const [forMeasure, forConsumer] = response.body.tee(); fetchMeassurement(forMeasure); httpRequest.customData.reader = forConsumer.getReader(); } /** * Reads the response of the request. For details refer to https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader/read * @param httpRequest * @param processResult * @private */ function _read(httpRequest, httpResponse, processResult) { httpRequest.customData.reader.read() .then(processResult) .catch(function (e) { if (httpRequest.customData.onerror &amp;&amp; httpResponse.status === 200) { // Error, but response code is 200, trigger error httpRequest.customData.onerror(e); } }); } /** * Creates a new Uint8 array and adds the existing data as well as new data * @param receivedData * @param data * @returns {Uint8Array|*} * @private */ function _concatTypedArray(receivedData, data) { if (receivedData.length === 0) { return data; } const result = new Uint8Array(receivedData.length + data.length); result.set(receivedData); // set(typedarray, targetOffset) result.set(data, receivedData.length); return result; } /** * Use the AbortController to abort a request * @param request */ function abort() { // this = httpRequest (CommonMediaRequest) if (this.customData.abortController) { // For firefox and edge this.customData.abortController.abort(); } else if (this.customData.reader) { // For Chrome try { this.customData.reader.cancel(); this.onabort(); } catch (e) { // throw exceptions (TypeError) when reader was previously closed, // for example, because a network issue } } } /** * Default throughput calculation * @param downloadedData * @param bytesReceived * @returns {number|null} * @private */ function calculateDownloadedTime(downloadedData, bytesReceived) { try { downloadedData = downloadedData.filter(data => data.bytes > ((bytesReceived / 4) / downloadedData.length)); if (downloadedData.length > 1) { let time = 0; const avgTimeDistance = (downloadedData[downloadedData.length - 1].ts - downloadedData[0].ts) / downloadedData.length; downloadedData.forEach((data, index) => { // To be counted the data has to be over a threshold const next = downloadedData[index + 1]; if (next) { const distance = next.ts - data.ts; time += distance &lt; avgTimeDistance ? distance : 0; } }); return time; } return null; } catch (e) { return null; } } /** * Moof based throughput calculation * @param startTimeData * @param endTimeData * @returns {number|null} * @private */ function _calculateThroughputByChunkData(startTimeData, endTimeData) { try { let datum, datumE; // Filter the last chunks in a segment in both arrays [StartTimeData and EndTimeData] datum = startTimeData.filter((data, i) => i &lt; startTimeData.length - 1); datumE = endTimeData.filter((dataE, i) => i &lt; endTimeData.length - 1); let chunkThroughputs = []; // Compute the average throughput of the filtered chunk data if (datum.length > 1) { let shortDurationBytesReceived = 0; let shortDurationStartTime = 0; for (let i = 0; i &lt; datum.length; i++) { if (datum[i] &amp;&amp; datumE[i]) { let chunkDownloadTime = datumE[i].ts - datum[i].ts; if (chunkDownloadTime > 1) { chunkThroughputs.push((8 * datumE[i].bytes) / chunkDownloadTime); shortDurationStartTime = 0; } else { if (shortDurationStartTime === 0) { shortDurationStartTime = datum[i].ts; shortDurationBytesReceived = 0; } let cumulatedChunkDownloadTime = datumE[i].ts - shortDurationStartTime; if (cumulatedChunkDownloadTime > 1) { shortDurationBytesReceived += datumE[i].bytes; chunkThroughputs.push((8 * shortDurationBytesReceived) / cumulatedChunkDownloadTime); shortDurationStartTime = 0; } else { // continue cumulating short duration data shortDurationBytesReceived += datumE[i].bytes; } } } } if (chunkThroughputs.length > 0) { const sumOfChunkThroughputs = chunkThroughputs.reduce((a, b) => a + b, 0); return sumOfChunkThroughputs / chunkThroughputs.length; } } return null; } catch (e) { return null; } } instance = { load, abort, setConfig, calculateDownloadedTime }; return instance; } FetchLoader.__dashjs_factory_name = 'FetchLoader'; const factory = FactoryMaker.getClassFactory(FetchLoader); export default factory; </pre> </article> </section> </div> </div> <div class="clearfix"></div> </div> </div> <div class="modal fade" id="searchResults"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button> <h4 class="modal-title">Search results</h4> </div> <div class="modal-body"></div> <div class="modal-footer"> <button type="button" class="btn btn-default" data-dismiss="modal">Close</button> </div> </div><!-- /.modal-content --> </div><!-- /.modal-dialog --> </div> <footer> <span class="copyright"> <h3>DASH Industry Forum</h3> </span> <span class="jsdoc-message"> Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.6.11</a> on Mon Jan 29th 2024 using the <a href="https://github.com/docstrap/docstrap">DocStrap template</a>. </span> </footer> <script src="scripts/docstrap.lib.js"></script> <script src="scripts/toc.js"></script> <script type="text/javascript" src="scripts/fulltext-search-ui.js"></script> <script> $( function () { $( "[id*='$']" ).each( function () { var $this = $( this ); $this.attr( "id", $this.attr( "id" ).replace( "$", "__" ) ); } ); $( ".tutorial-section pre, .readme-section pre, pre.prettyprint.source" ).each( function () { var $this = $( this ); var example = $this.find( "code" ); exampleText = example.html(); var lang = /{@lang (.*?)}/.exec( exampleText ); if ( lang && lang[1] ) { exampleText = exampleText.replace( lang[0], "" ); example.html( exampleText ); lang = lang[1]; } else { var langClassMatch = example.parent()[0].className.match(/lang\-(\S+)/); lang = langClassMatch ? langClassMatch[1] : "javascript"; } if ( lang ) { $this .addClass( "sunlight-highlight-" + lang ) .addClass( "linenums" ) .html( example.html() ); } } ); Sunlight.highlightAll( { lineNumbers : true, showMenu : true, enableDoclinks : true } ); $.catchAnchorLinks( { navbarOffset: 10 } ); $( "#toc" ).toc( { anchorName : function ( i, heading, prefix ) { return $( heading ).attr( "id" ) || ( prefix + i ); }, selectors : "#toc-content h1,#toc-content h2,#toc-content h3,#toc-content h4", showAndHide : false, smoothScrolling: true } ); $( "#main span[id^='toc']" ).addClass( "toc-shim" ); $( '.dropdown-toggle' ).dropdown(); $( "table" ).each( function () { var $this = $( this ); $this.addClass('table'); } ); } ); </script> <!--Navigation and Symbol Display--> <script> $( function () { $( '#main' ).localScroll( { offset : { top : 60 } //offset by the height of your header (give or take a few px, see what works for you) } ); $( "dt.name" ).each( function () { var $this = $( this ).find("h4"); var icon = $( "<i/>" ).addClass( "icon-plus-sign" ).addClass( "pull-right" ).addClass( "icon-white" ); var dt = $(this); var children = dt.next( "dd" ); dt.prepend( icon ).css( {cursor : "pointer"} ); dt.addClass( "member-collapsed" ).addClass( "member" ); children.hide(); dt.children().on( "click", function () { children = dt.next( "dd" ); children.slideToggle( "fast", function () { if ( children.is( ":visible" ) ) { icon.addClass( "icon-minus-sign" ).removeClass( "icon-plus-sign" ).removeClass( "icon-white" ); dt.addClass( "member-open" ).animate( "member-collapsed" ); } else { icon.addClass( "icon-plus-sign" ).removeClass( "icon-minus-sign" ).addClass( "icon-white" ); dt.addClass( "member-collapsed" ).removeClass( "member-open" ); } } ); } ); } ); } ); </script> <!--Google Analytics--> <script type="text/javascript"> $(document).ready(function() { SearcherDisplay.init(); }); </script> </body> </html>