diff --git a/index.d.ts b/index.d.ts
index 52b7d2f9b3..1196e2b6c4 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -1024,6 +1024,14 @@ declare namespace dashjs {
value: string;
}
+ export interface ThroughputDictValue {
+ downloadTimeInMs: number,
+ downloadedBytes: number,
+ latencyInMs: number
+ serviceLocation: string,
+ value: number,
+ }
+
/**
* Dash
**/
@@ -1630,7 +1638,6 @@ declare namespace dashjs {
applyServiceDescription?: boolean,
applyProducerReferenceTime?: boolean,
applyContentSteering?: boolean,
- eventControllerRefreshDelay?: number,
enableManifestDurationMismatchFix?: boolean,
parseInbandPrft?: boolean,
enableManifestTimescaleMismatchFix?: boolean,
@@ -1643,6 +1650,10 @@ declare namespace dashjs {
filterHDRMetadataFormatEssentialProperties?: boolean,
filterVideoColometryEssentialProperties?: boolean
},
+ events?: {
+ eventControllerRefreshDelay?: number,
+ deleteEventMessageDataAfterEventStarted?: boolean
+ }
timeShiftBuffer?: {
calcFromSegmentTimeline?: boolean
fallbackToSegmentTimeline?: boolean
@@ -2049,6 +2060,8 @@ declare namespace dashjs {
getAvailableLocations(): MpdLocation[];
+ getAverageLatency(type: MediaType, calculationMode?: string | null, sampleSize?: number): number;
+
getAverageThroughput(type: MediaType, calculationMode?: string | null, sampleSize?: number): number;
getBufferLength(type: MediaType): number;
@@ -2085,8 +2098,12 @@ declare namespace dashjs {
getProtectionController(): ProtectionController;
+ getRawThroughputData(type: MediaType): ThroughputDictValue[];
+
getRepresentationsByType(type: MediaType, streamId?: string | null): Representation[];
+ getSafeAverageThroughput(type: MediaType, calculationMode?: string | null, sampleSize?: number): number;
+
getSettings(): MediaPlayerSettingClass;
getSource(): string | object;
@@ -5499,11 +5516,21 @@ declare namespace dashjs {
}
export class IsoBoxSearchInfo {
- constructor(lastCompletedOffset: number, found: boolean, size: number);
+ constructor(found: boolean,
+ sizeOfLastCompletedBox: number,
+ sizeOfLastFoundTargetBox: number,
+ startOffsetOfLastCompletedBox: number,
+ startOffsetOfLastFoundTargetBox: number,
+ typeOfLastCompletedBox: string,
+ typeOfLastTargetBox: string);
found: boolean;
- lastCompletedOffset: number;
- size: number;
+ sizeOfLastCompletedBox: number;
+ sizeOfLastFoundTargetBox: number;
+ startOffsetOfLastCompletedBox: number;
+ startOffsetOfLastFoundTargetBox: number;
+ typeOfLastCompletedBox: string | null;
+ typeOfLastTargetBox: string | null;
}
export class MetricsList {
diff --git a/samples/advanced/ext-url-query-info.html b/samples/advanced/ext-url-query-info.html
index 3397656506..128d76f71c 100644
--- a/samples/advanced/ext-url-query-info.html
+++ b/samples/advanced/ext-url-query-info.html
@@ -153,7 +153,7 @@
-
+
diff --git a/samples/dash-if-reference-player/app/sources.json b/samples/dash-if-reference-player/app/sources.json
index 72ba3638fa..b7729ac63d 100644
--- a/samples/dash-if-reference-player/app/sources.json
+++ b/samples/dash-if-reference-player/app/sources.json
@@ -280,6 +280,11 @@
"name": "SCTE35 events",
"provider": "dashif"
},
+ {
+ "url": "https://livesim.dashif.org/livesim2/annexI_dashjs=rocks/testpic_6s/Manifest.mpd?dashjs=rocks",
+ "name": "Annex I query parameters in the Video AdaptationSet",
+ "provider": "dashif"
+ },
{
"url": "https://demo.unified-streaming.com/k8s/live/scte35.isml/.mpd",
"name": "Unified Streaming reference stream with scte35 markers",
diff --git a/src/core/Settings.js b/src/core/Settings.js
index 1c75759be2..d1976785ec 100644
--- a/src/core/Settings.js
+++ b/src/core/Settings.js
@@ -67,7 +67,6 @@ import Events from './events/Events.js';
* applyServiceDescription: true,
* applyProducerReferenceTime: true,
* applyContentSteering: true,
- * eventControllerRefreshDelay: 100,
* enableManifestDurationMismatchFix: true,
* parseInbandPrft: false,
* enableManifestTimescaleMismatchFix: false,
@@ -86,6 +85,10 @@ import Events from './events/Events.js';
* filterVideoColorimetryEssentialProperties: false,
* filterHDRMetadataFormatEssentialProperties: false
* },
+ * events: {
+ * eventControllerRefreshDelay: 100,
+ * deleteEventMessageDataAfterEventStarted: true
+ * }
* timeShiftBuffer: {
* calcFromSegmentTimeline: false,
* fallbackToSegmentTimeline: true
@@ -264,7 +267,7 @@ import Events from './events/Events.js';
* useResourceTimingApi: true,
* useNetworkInformationApi: {
* xhr: false,
- * fetch: true
+ * fetch: false
* },
* useDeadTimeLatency: true,
* bandwidthSafetyFactor: 0.9,
@@ -281,7 +284,8 @@ import Events from './events/Events.js';
* throughputSlowHalfLifeSeconds: 8,
* throughputFastHalfLifeSeconds: 3,
* latencySlowHalfLifeCount: 2,
- * latencyFastHalfLifeCount: 1
+ * latencyFastHalfLifeCount: 1,
+ * weightDownloadTimeMultiplicationFactor: 0.0015
* }
* },
* maxBitrate: {
@@ -342,6 +346,16 @@ import Events from './events/Events.js';
* In case the MPD uses \.
*/
+/**
+ * @typedef {Object} EventSettings
+ * @property {number} [eventControllerRefreshDelay=100]
+ * Interval timer used by the EventController to check if events need to be triggered or removed.
+ * @property {boolean} [deleteEventMessageDataAfterEventStarted=true]
+ * If this flag is enabled the EventController will delete the message data of events after they have been started. This is to save memory in case events have a long duration and need to be persisted in the EventController.
+ * Note: Applications will receive a copy of the original event data when they subscribe to an event. This copy contains the original message data and is not affected by this setting.
+ * Only if an event is dispatched for the second time (e.g. when the user seeks back) the message data will not be included in the dispatched event.
+ */
+
/**
* @typedef {Object} LiveDelay
* @property {number} [liveDelayFragmentCount=NaN]
@@ -827,7 +841,7 @@ import Events from './events/Events.js';
* @property {boolean} [useResourceTimingApi=true]
* If set to true the ResourceTimingApi is used to derive the download time and the number of downloaded bytes.
* This option has no effect for low latency streaming as the download time equals the segment duration in most of the cases and therefor does not provide reliable values
- * @property {object} [useNetworkInformationApi = { xhr=false, fetch=true}]
+ * @property {object} [useNetworkInformationApi = { xhr=false, fetch=false}]
* If set to true the NetworkInformationApi is used to derive the current throughput. Browser support is limited, only available in Chrome and Edge.
* Applies to standard (XHR requests) and/or low latency streaming (Fetch API requests).
* @property {boolean} [useDeadTimeLatency=true]
@@ -847,12 +861,13 @@ import Events from './events/Events.js';
* - `increaseScale`: Increase sample size by one if the ratio of current and previous sample is higher or equal this value
* - `maxMeasurementsToKeep`: Number of samples to keep before sliding samples out of the window
* - `averageLatencySampleAmount`: Number of latency samples to use (sample size)
- * @property {object} [ewma={throughputSlowHalfLifeSeconds=8,throughputFastHalfLifeSeconds=3,latencySlowHalfLifeCount=2,latencyFastHalfLifeCount=1}]
+ * @property {object} [ewma={throughputSlowHalfLifeSeconds=8,throughputFastHalfLifeSeconds=3,latencySlowHalfLifeCount=2,latencyFastHalfLifeCount=1, weightDownloadTimeMultiplicationFactor=0.0015}]
* When deriving the throughput based on the exponential weighted moving average these settings define:
* - `throughputSlowHalfLifeSeconds`: Number by which the weight of the current throughput measurement is divided, see ThroughputModel._updateEwmaValues
* - `throughputFastHalfLifeSeconds`: Number by which the weight of the current throughput measurement is divided, see ThroughputModel._updateEwmaValues
* - `latencySlowHalfLifeCount`: Number by which the weight of the current latency is divided, see ThroughputModel._updateEwmaValues
* - `latencyFastHalfLifeCount`: Number by which the weight of the current latency is divided, see ThroughputModel._updateEwmaValues
+ * - `weightDownloadTimeMultiplicationFactor`: This value is multiplied with the download time in milliseconds to derive the weight for the EWMA calculation.
*/
/**
@@ -937,8 +952,6 @@ import Events from './events/Events.js';
* Set to true if dash.js should use the parameters defined in ProducerReferenceTime elements in combination with ServiceDescription elements.
* @property {boolean} [applyContentSteering=true]
* Set to true if dash.js should apply content steering during playback.
- * @property {number} [eventControllerRefreshDelay=100]
- * For multi-period streams, overwrite the manifest mediaPresentationDuration attribute with the sum of period durations if the manifest mediaPresentationDuration is greater than the sum of period durations
* @property {boolean} [enableManifestDurationMismatchFix=true]
* Overwrite the manifest segments base information timescale attributes with the timescale set in initialization segments
* @property {boolean} [enableManifestTimescaleMismatchFix=false]
@@ -947,6 +960,7 @@ import Events from './events/Events.js';
* Set to true if dash.js should parse inband prft boxes (ProducerReferenceTime) and trigger events.
* @property {module:Settings~Metrics} metrics Metric settings
* @property {module:Settings~LiveDelay} delay Live Delay settings
+ * @property {module:Settings~EventSettings} events Event settings
* @property {module:Settings~TimeShiftBuffer} timeShiftBuffer TimeShiftBuffer settings
* @property {module:Settings~Protection} protection DRM related settings
* @property {module:Settings~Capabilities} capabilities Capability related settings
@@ -1078,7 +1092,6 @@ function Settings() {
applyServiceDescription: true,
applyProducerReferenceTime: true,
applyContentSteering: true,
- eventControllerRefreshDelay: 100,
enableManifestDurationMismatchFix: true,
parseInbandPrft: false,
enableManifestTimescaleMismatchFix: false,
@@ -1099,6 +1112,10 @@ function Settings() {
filterVideoColorimetryEssentialProperties: false,
filterHDRMetadataFormatEssentialProperties: false
},
+ events: {
+ eventControllerRefreshDelay: 100,
+ deleteEventMessageDataAfterEventStarted: true
+ },
timeShiftBuffer: {
calcFromSegmentTimeline: false,
fallbackToSegmentTimeline: true
@@ -1290,7 +1307,7 @@ function Settings() {
useResourceTimingApi: true,
useNetworkInformationApi: {
xhr: false,
- fetch: true
+ fetch: false
},
useDeadTimeLatency: true,
bandwidthSafetyFactor: 0.9,
diff --git a/src/streaming/MediaPlayer.js b/src/streaming/MediaPlayer.js
index 98a474ab74..587463bc9c 100644
--- a/src/streaming/MediaPlayer.js
+++ b/src/streaming/MediaPlayer.js
@@ -1232,6 +1232,20 @@ function MediaPlayer() {
customParametersModel.restoreDefaultUTCTimingSources();
}
+ /**
+ * Returns the average latency computed in the ThroughputController in milliseconds
+ *
+ * @param {MediaType} type
+ * @param {string} calculationMode
+ * @param {number} sampleSize
+ * @return {number} value
+ * @memberof module:MediaPlayer
+ * @instance
+ */
+ function getAverageLatency(type, calculationMode = null, sampleSize = NaN) {
+ return throughputController ? throughputController.getAverageLatency(type, calculationMode, sampleSize) : 0;
+ }
+
/**
* Returns the average throughput computed in the ThroughputController in kbit/s
*
@@ -1246,6 +1260,32 @@ function MediaPlayer() {
return throughputController ? throughputController.getAverageThroughput(type, calculationMode, sampleSize) : 0;
}
+ /**
+ * Returns the safe average throughput computed in the ThroughputController in kbit/s. The safe average throughput is the average throughput multiplied by bandwidthSafetyFactor
+ *
+ * @param {MediaType} type
+ * @param {string} calculationMode
+ * @param {number} sampleSize
+ * @return {number} value
+ * @memberof module:MediaPlayer
+ * @instance
+ */
+ function getSafeAverageThroughput(type, calculationMode = null, sampleSize = NaN) {
+ return throughputController ? throughputController.getSafeAverageThroughput(type, calculationMode, sampleSize) : 0;
+ }
+
+ /**
+ * Returns the raw throughput data without calculating the average. This can be used to calculate the current throughput yourself.
+ *
+ * @param {MediaType} type
+ * @return {Array} value
+ * @memberof module:MediaPlayer
+ * @instance
+ */
+ function getRawThroughputData(type) {
+ return throughputController ? throughputController.getRawThroughputData(type) : [];
+ }
+
/**
* Sets whether withCredentials on XHR requests for a particular request
* type is true or false
@@ -2759,6 +2799,7 @@ function MediaPlayer() {
getAutoPlay,
getAvailableBaseUrls,
getAvailableLocations,
+ getAverageLatency,
getAverageThroughput,
getBufferLength,
getCurrentLiveLatency,
@@ -2777,7 +2818,9 @@ function MediaPlayer() {
getOfflineController,
getPlaybackRate,
getProtectionController,
+ getRawThroughputData,
getRepresentationsByType,
+ getSafeAverageThroughput,
getSettings,
getSource,
getStreamsFromManifest,
diff --git a/src/streaming/Stream.js b/src/streaming/Stream.js
index dad44879c5..3724dbcade 100644
--- a/src/streaming/Stream.js
+++ b/src/streaming/Stream.js
@@ -552,6 +552,10 @@ function Stream(config) {
if (textController) {
textController.deactivateStream(streamInfo);
}
+ if (thumbnailController) {
+ thumbnailController.reset();
+ thumbnailController = null;
+ }
streamProcessors = [];
isActive = false;
hasFinishedBuffering = false;
@@ -624,6 +628,10 @@ function Stream(config) {
segmentBlacklistController = null;
}
+ if (textController && streamInfo) {
+ textController.clearDataForStream(streamInfo.id);
+ }
+
resetInitialSettings(keepBuffers);
streamInfo = null;
diff --git a/src/streaming/controllers/BlacklistController.js b/src/streaming/controllers/BlacklistController.js
index a5aa6f0683..e4f7df2d4e 100644
--- a/src/streaming/controllers/BlacklistController.js
+++ b/src/streaming/controllers/BlacklistController.js
@@ -78,6 +78,9 @@ function BlackListController(config) {
}
function reset() {
+ if (addBlacklistEventName) {
+ eventBus.off(addBlacklistEventName, onAddBlackList, instance);
+ }
blacklist = [];
}
diff --git a/src/streaming/controllers/EventController.js b/src/streaming/controllers/EventController.js
index 837bcabf2e..01864a9121 100644
--- a/src/streaming/controllers/EventController.js
+++ b/src/streaming/controllers/EventController.js
@@ -118,7 +118,7 @@ function EventController() {
try {
checkConfig();
logger.debug('Start Event Controller');
- const refreshDelay = settings.get().streaming.eventControllerRefreshDelay;
+ const refreshDelay = settings.get().streaming.events.eventControllerRefreshDelay;
if (!isStarted && !isNaN(refreshDelay)) {
isStarted = true;
eventInterval = setInterval(_onEventTimer, refreshDelay);
@@ -474,7 +474,7 @@ function EventController() {
if (mode === MediaPlayerEvents.EVENT_MODE_ON_RECEIVE && !event.triggeredReceivedEvent) {
logger.debug(`Received event ${eventId}`);
event.triggeredReceivedEvent = true;
- eventBus.trigger(event.eventStream.schemeIdUri, { event: event }, { mode });
+ eventBus.trigger(event.eventStream.schemeIdUri, { event: JSON.parse(JSON.stringify(event)) }, { mode });
return;
}
@@ -490,7 +490,11 @@ function EventController() {
_sendCallbackRequest(event.messageData);
} else {
logger.debug(`Starting event ${eventId} from period ${event.eventStream.period.id} at ${currentVideoTime}`);
- eventBus.trigger(event.eventStream.schemeIdUri, { event: event }, { mode });
+ eventBus.trigger(event.eventStream.schemeIdUri, { event: JSON.parse(JSON.stringify(event)) }, { mode });
+ if (settings.get().streaming.events.deleteEventMessageDataAfterEventStarted) {
+ delete event.messageData;
+ delete event.parsedMessageData;
+ }
}
event.triggeredStartEvent = true;
}
diff --git a/src/streaming/net/FetchLoader.js b/src/streaming/net/FetchLoader.js
index 5190ec30b4..9f68414036 100644
--- a/src/streaming/net/FetchLoader.js
+++ b/src/streaming/net/FetchLoader.js
@@ -32,7 +32,7 @@
import FactoryMaker from '../../core/FactoryMaker.js';
import Settings from '../../core/Settings.js';
import Constants from '../constants/Constants.js';
-import AastLowLatencyThroughputModel from '../models/AastLowLatencyThroughputModel.js';
+import Debug from '../../core/Debug.js';
/**
* @module FetchLoader
@@ -42,312 +42,308 @@ import AastLowLatencyThroughputModel from '../models/AastLowLatencyThroughputMod
function FetchLoader() {
const context = this.context;
- const aastLowLatencyThroughputModel = AastLowLatencyThroughputModel(context).getInstance();
const settings = Settings(context).getInstance();
- let instance, dashMetrics, boxParser;
+ let instance, boxParser, logger;
function setConfig(cfg) {
- dashMetrics = cfg.dashMetrics;
boxParser = cfg.boxParser
}
+ function setup() {
+ logger = Debug(context).getInstance().getLogger(instance);
+ }
+
/**
* Load request
- * @param {CommonMediaRequest} httpRequest
- * @param {CommonMediaResponse} httpResponse
+ * With HTTP responses that use chunked transfer encoding, the promise returned by fetch will resolve as soon as the response's headers are received.
+ * @param {CommonMediaRequest} commonMediaRequest
+ * @param {CommonMediaResponse} commonMediaResponse
*/
- function load(httpRequest, httpResponse) {
- // Variables will be used in the callback functions
- const fragmentRequest = httpRequest.customData.request;
+ function load(commonMediaRequest, commonMediaResponse) {
+ const headers = _getHeaders(commonMediaRequest);
+ const abortController = _setupAbortMechanism(commonMediaRequest);
+ const fetchResourceRequestObject = _getFetchResourceRequestObject(commonMediaRequest, headers, abortController);
+
+ fetch(fetchResourceRequestObject)
+ .then((fetchResponse) => {
+ _handleFetchResponse(fetchResponse, commonMediaRequest, commonMediaResponse);
+ })
+ .catch(() => {
+ _handleFetchError(commonMediaRequest);
+ })
+ }
- const headers = new Headers();
- if (httpRequest.headers) {
- for (let header in httpRequest.headers) {
- let value = httpRequest.headers[header];
- if (value) {
- headers.append(header, value);
- }
- }
- }
+ function _handleFetchResponse(fetchResponse, commonMediaRequest, commonMediaResponse) {
+ _updateCommonMediaResponseInstance(commonMediaResponse, fetchResponse);
- let abortController;
- if (typeof window.AbortController === 'function') {
- abortController = new AbortController(); /*jshint ignore:line*/
- httpRequest.customData.abortController = abortController;
- abortController.signal.onabort = httpRequest.customData.onabort;
+ if (!fetchResponse.ok) {
+ commonMediaRequest.customData.onloadend();
}
- httpRequest.customData.abort = abort.bind(httpRequest);
+ let totalBytesReceived = 0;
+ let signaledFirstByte = false;
+ let receivedData = new Uint8Array();
+ let endPositionOfLastProcessedBoxInReceivedData = 0;
- const reqOptions = {
- method: httpRequest.method,
- headers: headers,
- credentials: httpRequest.credentials,
- signal: abortController ? abortController.signal : undefined
- };
+ commonMediaRequest.customData.reader = fetchResponse.body.getReader();
+ let downloadedData = [];
+ let moofStartTimeData = [];
+ let mdatEndTimeData = [];
+ let lastChunkWasFinished = true;
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 && 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);
- }
+
+ /**
+ * Callback function for ReadableStreamDefaultReader
+ * @param value - chunk data. Always undefined when done is true.
+ * @param done - true if the stream has already given you all its data.
+ */
+ const _processResult = ({ value, done }) => {
+
+ if (done) {
+ _handleRequestComplete()
+ return;
}
- 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.onloadend();
- }
- const responseHeaders = {};
- for (const key of response.headers.keys()) {
- responseHeaders[key] = response.headers.get(key);
- }
- httpResponse.headers = responseHeaders;
+ if (value && value.length > 0) {
+ _handleReceivedChunkData(value)
+ }
- const totalBytes = parseInt(response.headers.get('Content-Length'), 10);
- let bytesReceived = 0;
- let signaledFirstByte = false;
- let receivedData = new Uint8Array();
- let offset = 0;
+ _readResponseBody(commonMediaRequest, commonMediaResponse, _processResult);
+ };
- if (calculationMode === Constants.LOW_LATENCY_DOWNLOAD_TIME_CALCULATION_MODE.AAST && aastLowLatencyThroughputModel) {
- _aastProcessResponse(markBeforeFetch, httpRequest, requestTime, throughputCapacityDelayMS, responseHeaders, response);
- } else {
- httpRequest.customData.reader = response.body.getReader();
- }
+ /**
+ * Once a request is completed throw final progress event with the calculated bytes and download time
+ * @private
+ */
+ function _handleRequestComplete() {
+ if (receivedData) {
+ const calculatedDownloadTime = _calculateDownloadTime();
+
+ // In this case we push an entry to the traces.
+ // This is the only entry we push as the other calls to onprogress use noTrace = true
+ commonMediaRequest.customData.onprogress({
+ loaded: totalBytesReceived,
+ total: totalBytesReceived,
+ lengthComputable: true,
+ time: calculatedDownloadTime
+ });
- 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 && 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.onloadend();
- }
+ commonMediaResponse.data = receivedData.buffer;
+ }
+ commonMediaRequest.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 && 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 && !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;
- }
- }
- }
+ function _calculateDownloadTime() {
+ // 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 downloadTime = null;
+ if (calculationMode === Constants.LOW_LATENCY_DOWNLOAD_TIME_CALCULATION_MODE.MOOF_PARSING) {
+ downloadTime = _getDownloadTimeForMoofParsing();
+ } else if (calculationMode === Constants.LOW_LATENCY_DOWNLOAD_TIME_CALCULATION_MODE.DOWNLOADED_DATA) {
+ downloadTime = _getDownloadTimeForDownloadedData()
+ }
- _read(httpRequest, httpResponse, _processResult);
- })
- .catch(function () {
- if (httpRequest.customData.onloadend) {
- httpRequest.customData.onloadend();
- }
- });
+ return downloadTime;
+ }
+
+ function _getDownloadTimeForMoofParsing() {
+ const calculatedThroughput = _calculateThroughputByMoofMdatTimes(moofStartTimeData, mdatEndTimeData);
+
+ if (calculatedThroughput) {
+ return totalBytesReceived * 8 / calculatedThroughput;
+ }
+
+ return null;
+ }
+
+ function _getDownloadTimeForDownloadedData() {
+ return calculateDownloadedTime(downloadedData, totalBytesReceived);
+ }
+
+ /**
+ * Called every time we received data if the request is not completed
+ * @param value
+ * @private
+ */
+ function _handleReceivedChunkData(value) {
+ receivedData = _concatTypedArray(receivedData, value);
+ totalBytesReceived += value.length;
+
+ downloadedData.push({
+ timestamp: _getCurrentTimestamp(),
+ bytes: value.length
});
- }
+ if (calculationMode === Constants.LOW_LATENCY_DOWNLOAD_TIME_CALCULATION_MODE.MOOF_PARSING && lastChunkWasFinished) {
+ _findMoofBoxInChunkData();
+ }
- 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 && 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)
- });
- }
+ const boxesInfo = boxParser.findLastTopIsoBoxCompleted(['moov', 'mdat'], receivedData, endPositionOfLastProcessedBoxInReceivedData);
+ if (boxesInfo.found) {
+ _handleTopIsoBoxCompleted(boxesInfo);
+ } else {
+ _handleNoCompletedTopIsoBox(boxesInfo);
+ }
+ }
- if (done) {
+ function _findMoofBoxInChunkData() {
+ const boxesInfo = boxParser.findLastTopIsoBoxCompleted(['moof'], receivedData, endPositionOfLastProcessedBoxInReceivedData);
- const fetchDuration = markB - markBeforeFetch;
- const bytesAllChunks = measurement.reduce((prev, curr) => prev + curr.chunkBytes, 0);
+ if (boxesInfo.found) {
+ lastChunkWasFinished = false;
+ moofStartTimeData.push({
+ timestamp: _getCurrentTimestamp()
+ });
+ }
+ }
- aastLowLatencyThroughputModel.addMeasurement(fragmentRequest, fetchDuration, measurement, requestTime, throughputCapacityDelayMS, responseHeaders);
+ function _handleTopIsoBoxCompleted(boxesInfo) {
+ const endPositionOfLastTargetBox = boxesInfo.startOffsetOfLastFoundTargetBox + boxesInfo.sizeOfLastFoundTargetBox;
+ const data = _getDataForMediaSourceBufferAndAdjustReceivedData(endPositionOfLastTargetBox);
- httpRequest.progress({
- loaded: bytesAllChunks,
- total: bytesAllChunks,
- lengthComputable: true,
- time: aastLowLatencyThroughputModel.getEstimatedDownloadDurationMS(fragmentRequest)
- });
- return;
- }
- markA = performance.now();
- return reader.read().then(processFetch);
+ // 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 && !lastChunkWasFinished) {
+ lastChunkWasFinished = true;
+ mdatEndTimeData.push({
+ timestamp: _getCurrentTimestamp(),
+ bytes: data.length
});
+ }
+
+ // Announce progress but don't track traces. Throughput measures are quite unstable
+ // when they are based in small amount of data
+ commonMediaRequest.customData.onprogress({
+ data: data.buffer,
+ lengthComputable: false,
+ noTrace: true
+ });
+
+ endPositionOfLastProcessedBoxInReceivedData = 0;
}
- // 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();
+ /**
+ * 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
+ * @param endPositionOfLastTargetBox
+ * @returns {Uint8Array}
+ * @private
+ */
+ function _getDataForMediaSourceBufferAndAdjustReceivedData(endPositionOfLastTargetBox) {
+ let data;
+
+ if (endPositionOfLastTargetBox === receivedData.length) {
+ data = receivedData;
+ receivedData = new Uint8Array();
+ } else {
+ data = new Uint8Array(receivedData.subarray(0, endPositionOfLastTargetBox));
+ receivedData = receivedData.subarray(endPositionOfLastTargetBox);
+ }
+
+ return data
+ }
+
+ function _handleNoCompletedTopIsoBox(boxesInfo) {
+ endPositionOfLastProcessedBoxInReceivedData = boxesInfo.startOffsetOfLastCompletedBox + boxesInfo.sizeOfLastCompletedBox;
+ // Call progress, so it generates traces that will be later used to know when the first byte
+ // were received
+ if (!signaledFirstByte) {
+ commonMediaRequest.customData.onprogress({
+ lengthComputable: false,
+ noTrace: true
+ });
+ signaledFirstByte = true;
+ }
+ }
+
+ _readResponseBody(commonMediaRequest, commonMediaResponse, _processResult);
}
/**
* Reads the response of the request. For details refer to https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader/read
- * @param httpRequest
+ * @param {CommonMediaRequest} commonMediaRequest
+ * @param {CommonMediaResponse} commonMediaResponse
* @param processResult
* @private
*/
- function _read(httpRequest, httpResponse, processResult) {
- httpRequest.customData.reader.read()
+ function _readResponseBody(commonMediaRequest, commonMediaResponse, processResult) {
+ commonMediaRequest.customData.reader.read()
.then(processResult)
.catch(function () {
- if (httpRequest.customData.onloadend) {
- httpRequest.customData.onloadend();
- }
+ _handleFetchError(commonMediaRequest);
});
}
+ function _handleFetchError(commonMediaRequest) {
+ if (commonMediaRequest.customData.onloadend) {
+ commonMediaRequest.customData.onloadend();
+ }
+ }
+
+ function _updateCommonMediaResponseInstance(commonMediaResponse, fetchResponse) {
+ commonMediaResponse.status = fetchResponse.status;
+ commonMediaResponse.statusText = fetchResponse.statusText;
+ commonMediaResponse.url = fetchResponse.url;
+
+ const responseHeaders = {};
+ for (const key of fetchResponse.headers.keys()) {
+ responseHeaders[key] = fetchResponse.headers.get(key);
+ }
+ commonMediaResponse.headers = responseHeaders;
+ }
+
+ function _getHeaders(commonMediaRequest) {
+ const headers = new Headers();
+
+ if (commonMediaRequest.headers) {
+ for (let header in commonMediaRequest.headers) {
+ let value = commonMediaRequest.headers[header];
+ if (value) {
+ headers.append(header, value);
+ }
+ }
+ }
+
+ return headers
+ }
+
+ function _setupAbortMechanism(commonMediaRequest) {
+ let abortController;
+
+ if (typeof window.AbortController === 'function') {
+ abortController = new AbortController();
+ commonMediaRequest.customData.abortController = abortController;
+ abortController.signal.onabort = commonMediaRequest.customData.onabort;
+ }
+
+ commonMediaRequest.customData.abort = abort.bind(commonMediaRequest);
+
+ return abortController
+ }
+
+ function _getFetchResourceRequestObject(commonMediaRequest, headers, abortController) {
+ const fetchResourceRequestObject = new Request(commonMediaRequest.url, {
+ method: commonMediaRequest.method,
+ headers: headers,
+ credentials: commonMediaRequest.credentials,
+ signal: abortController ? abortController.signal : undefined
+ });
+
+ return fetchResourceRequestObject
+ }
+
+ function _getCurrentTimestamp() {
+ if (typeof performance !== 'undefined' && typeof performance.now === 'function') {
+ return performance.now();
+ } else {
+ return Date.now();
+ }
+ }
+
/**
* Creates a new Uint8 array and adds the existing data as well as new data
* @param receivedData
@@ -405,12 +401,12 @@ function FetchLoader() {
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;
+ const avgTimeDistance = (downloadedData[downloadedData.length - 1].timestamp - downloadedData[0].timestamp) / 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;
+ const distance = next.timestamp - data.timestamp;
time += distance < avgTimeDistance ? distance : 0;
}
});
@@ -422,52 +418,56 @@ function FetchLoader() {
}
}
- /**
- * Moof based throughput calculation
- * @param startTimeData
- * @param endTimeData
- * @returns {number|null}
- * @private
- */
- function _calculateThroughputByChunkData(startTimeData, endTimeData) {
+ function _calculateThroughputByMoofMdatTimes(moofStartTimeData, mdatEndTimeData) {
try {
- let datum, datumE;
+ let filteredMoofStartTimeData,
+ filteredMdatEndTimeData;
+
// Filter the last chunks in a segment in both arrays [StartTimeData and EndTimeData]
- datum = startTimeData.filter((data, i) => i < startTimeData.length - 1);
- datumE = endTimeData.filter((dataE, i) => i < 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 < datum.length; i++) {
- if (datum[i] && datumE[i]) {
- let chunkDownloadTime = datumE[i].ts - datum[i].ts;
- if (chunkDownloadTime > 1) {
- chunkThroughputs.push((8 * datumE[i].bytes) / chunkDownloadTime);
+ filteredMoofStartTimeData = moofStartTimeData.slice(0, -1);
+ filteredMdatEndTimeData = mdatEndTimeData.slice(0, -1);
+
+ if (filteredMoofStartTimeData.length !== filteredMdatEndTimeData.length) {
+ logger.warn(`[FetchLoader] Moof and Mdat data arrays have different lengths. Moof: ${filteredMoofStartTimeData.length}, Mdat: ${filteredMdatEndTimeData.length}`);
+ }
+
+ if (filteredMoofStartTimeData.length <= 1) {
+ return null;
+ }
+
+ let chunkThroughputValues = [];
+ let shortDurationBytesReceived = 0;
+ let shortDurationStartTime = 0;
+
+ for (let i = 0; i < filteredMoofStartTimeData.length; i++) {
+ if (filteredMoofStartTimeData[i] && filteredMdatEndTimeData[i]) {
+ let chunkDownloadTime = filteredMdatEndTimeData[i].timestamp - filteredMoofStartTimeData[i].timestamp;
+ if (chunkDownloadTime > 1) {
+ const throughput = _getThroughputInBitPerMs(filteredMdatEndTimeData[i].bytes, chunkDownloadTime);
+ chunkThroughputValues.push(throughput);
+ shortDurationStartTime = 0;
+ } else {
+ if (shortDurationStartTime === 0) {
+ shortDurationStartTime = filteredMoofStartTimeData[i].timestamp;
+ shortDurationBytesReceived = 0;
+ }
+ let cumulatedChunkDownloadTime = filteredMdatEndTimeData[i].timestamp - shortDurationStartTime;
+ if (cumulatedChunkDownloadTime > 1) {
+ shortDurationBytesReceived += filteredMdatEndTimeData[i].bytes;
+ const throughput = _getThroughputInBitPerMs(shortDurationBytesReceived, cumulatedChunkDownloadTime);
+ chunkThroughputValues.push(throughput);
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;
- }
+ // continue cumulating short duration data
+ shortDurationBytesReceived += filteredMdatEndTimeData[i].bytes;
}
}
}
+ }
- if (chunkThroughputs.length > 0) {
- const sumOfChunkThroughputs = chunkThroughputs.reduce((a, b) => a + b, 0);
- return sumOfChunkThroughputs / chunkThroughputs.length;
- }
+ if (chunkThroughputValues.length > 0) {
+ const sumOfChunkThroughputValues = chunkThroughputValues.reduce((a, b) => a + b, 0);
+ return sumOfChunkThroughputValues / chunkThroughputValues.length;
}
return null;
@@ -476,6 +476,12 @@ function FetchLoader() {
}
}
+ function _getThroughputInBitPerMs(bytes, timeInMs) {
+ return (8 * bytes) / timeInMs
+ }
+
+ setup();
+
instance = {
abort,
calculateDownloadedTime,
diff --git a/src/streaming/net/HTTPLoader.js b/src/streaming/net/HTTPLoader.js
index e69a0823d8..2df9e9833f 100644
--- a/src/streaming/net/HTTPLoader.js
+++ b/src/streaming/net/HTTPLoader.js
@@ -45,6 +45,8 @@ import CustomParametersModel from '../models/CustomParametersModel.js';
import CommonAccessTokenController from '../controllers/CommonAccessTokenController.js';
import ClientDataReportingController from '../controllers/ClientDataReportingController.js';
import ExtUrlQueryInfoController from '../controllers/ExtUrlQueryInfoController.js';
+import CommonMediaRequest from '../vo/CommonMediaRequest.js';
+import CommonMediaResponse from '../vo/CommonMediaResponse.js';
/**
* @module HTTPLoader
@@ -167,16 +169,16 @@ function HTTPLoader(cfg) {
if (!event.lengthComputable ||
(event.lengthComputable && event.total !== event.loaded)) {
requestObject.firstByteDate = currentTime;
- httpResponse.resourceTiming.responseStart = currentTime.getTime();
+ commonMediaResponse.resourceTiming.responseStart = currentTime.getTime();
}
}
// lengthComputable indicating if the resource concerned by the ProgressEvent has a length that can be calculated. If not, the ProgressEvent.total property has no significant value.
if (event.lengthComputable) {
- requestObject.bytesLoaded = httpResponse.length = event.loaded;
- requestObject.bytesTotal = httpResponse.resourceTiming.encodedBodySize = event.total;
- httpResponse.length = event.total;
- httpResponse.resourceTiming.encodedBodySize = event.loaded;
+ requestObject.bytesLoaded = commonMediaResponse.length = event.loaded;
+ requestObject.bytesTotal = commonMediaResponse.resourceTiming.encodedBodySize = event.total;
+ commonMediaResponse.length = event.total;
+ commonMediaResponse.resourceTiming.encodedBodySize = event.loaded;
}
if (!event.noTrace) {
@@ -200,8 +202,8 @@ function HTTPLoader(cfg) {
if (settings.get().streaming.fragmentRequestProgressTimeout > 0) {
progressTimeout = setTimeout(function () {
// No more progress => abort request and treat as an error
- logger.warn('Abort request ' + httpRequest.url + ' due to progress timeout');
- loader.abort(httpRequest);
+ logger.warn('Abort request ' + commonMediaRequest.url + ' due to progress timeout');
+ loader.abort(commonMediaRequest);
_onloadend();
}, settings.get().streaming.fragmentRequestProgressTimeout);
}
@@ -236,8 +238,8 @@ function HTTPLoader(cfg) {
const _onRequestEnd = function (aborted = false) {
// Remove the request from our list of requests
- if (httpRequests.indexOf(httpRequest) !== -1) {
- httpRequests.splice(httpRequests.indexOf(httpRequest), 1);
+ if (httpRequests.indexOf(commonMediaRequest) !== -1) {
+ httpRequests.splice(httpRequests.indexOf(commonMediaRequest), 1);
}
if (progressTimeout) {
@@ -245,15 +247,15 @@ function HTTPLoader(cfg) {
progressTimeout = null;
}
- commonAccessTokenController.processResponseHeaders(httpResponse);
+ commonAccessTokenController.processResponseHeaders(commonMediaResponse);
_updateRequestTimingInfo();
_updateResourceTimingInfo();
- _applyResponseInterceptors(httpResponse).then((_httpResponse) => {
- httpResponse = _httpResponse;
+ _applyResponseInterceptors(commonMediaResponse).then((_httpResponse) => {
+ commonMediaResponse = _httpResponse;
- _addHttpRequestMetric(httpRequest, httpResponse, traces);
+ _addHttpRequestMetric(commonMediaRequest, commonMediaResponse, traces);
// Ignore aborted requests
if (aborted) {
@@ -268,18 +270,18 @@ function HTTPLoader(cfg) {
eventBus.trigger(Events.MANIFEST_LOADING_FINISHED, { requestObject });
}
- if (httpResponse.status >= 200 && httpResponse.status <= 299 && httpResponse.data) {
+ if (commonMediaResponse.status >= 200 && commonMediaResponse.status <= 299 && commonMediaResponse.data) {
if (config.success) {
- config.success(httpResponse.data, httpResponse.statusText, httpResponse.url);
+ config.success(commonMediaResponse.data, commonMediaResponse.statusText, commonMediaResponse.url);
}
if (config.complete) {
- config.complete(requestObject, httpResponse.statusText);
+ config.complete(requestObject, commonMediaResponse.statusText);
}
} else {
// If we get a 404 to a media segment we should check the client clock again and perform a UTC sync in the background.
try {
- if (httpResponse.status === 404 && settings.get().streaming.utcSynchronization.enableBackgroundSyncAfterSegmentDownloadError && requestObject.type === HTTPRequest.MEDIA_SEGMENT_TYPE) {
+ if (commonMediaResponse.status === 404 && settings.get().streaming.utcSynchronization.enableBackgroundSyncAfterSegmentDownloadError && requestObject.type === HTTPRequest.MEDIA_SEGMENT_TYPE) {
// Only trigger a sync if the loading failed for the first time
const initialNumberOfAttempts = mediaPlayerModel.getRetryAttemptsForType(HTTPRequest.MEDIA_SEGMENT_TYPE);
if (initialNumberOfAttempts === remainingAttempts) {
@@ -292,7 +294,6 @@ function HTTPLoader(cfg) {
_retriggerRequest();
}
});
-
};
const _updateRequestTimingInfo = function () {
@@ -302,11 +303,11 @@ function HTTPLoader(cfg) {
}
const _updateResourceTimingInfo = function () {
- httpResponse.resourceTiming.responseEnd = Date.now();
+ commonMediaResponse.resourceTiming.responseEnd = Date.now();
// If enabled the ResourceTimingApi we add the corresponding information to the request object.
// These values are more accurate and can be used by the ThroughputController later
- _addResourceTimingValues(httpRequest, httpResponse);
+ _addResourceTimingValues(commonMediaRequest, commonMediaResponse);
}
const _loadRequest = function (loader, httpRequest, httpResponse) {
@@ -350,15 +351,15 @@ function HTTPLoader(cfg) {
errHandler.error(new DashJSError(downloadErrorToRequestTypeMap[requestObject.type], requestObject.url + ' is not available', {
request: requestObject,
- response: httpResponse
+ response: commonMediaResponse
}));
if (config.error) {
- config.error(requestObject, 'error', httpResponse.statusText, httpResponse);
+ config.error(requestObject, 'error', commonMediaResponse.statusText, commonMediaResponse);
}
if (config.complete) {
- config.complete(requestObject, httpResponse.statusText);
+ config.complete(requestObject, commonMediaResponse.statusText);
}
}
}
@@ -368,8 +369,8 @@ function HTTPLoader(cfg) {
const traces = [];
let firstProgress, requestStartTime, lastTraceTime, lastTraceReceivedCount, progressTimeout;
- let httpRequest; // CommonMediaRequest
- let httpResponse; // CommonMediaResponse
+ let commonMediaRequest;
+ let commonMediaResponse;
requestObject.bytesLoaded = NaN;
requestObject.bytesTotal = NaN;
@@ -396,7 +397,8 @@ function HTTPLoader(cfg) {
}
const withCredentials = customParametersModel.getXHRWithCredentialsForType(requestObject.type);
- httpRequest = /* CommonMediaRequest */{
+
+ commonMediaRequest = new CommonMediaRequest({
url: requestObject.url,
method: HTTPRequest.GET,
responseType: requestObject.responseType,
@@ -405,29 +407,28 @@ function HTTPLoader(cfg) {
timeout: requestTimeout,
cmcd: cmcdModel.getCmcdData(requestObject),
customData: { request: requestObject }
- };
+ });
- // Init response (CommoneMediaResponse)
- httpResponse = {
- request: httpRequest,
+ commonMediaResponse = new CommonMediaResponse({
+ request: commonMediaRequest,
resourceTiming: {
startTime: Date.now(),
encodedBodySize: 0
},
status: 0
- };
+ });
// Adds the ability to delay single fragment loading time to control buffer.
let now = new Date().getTime();
if (isNaN(requestObject.delayLoadingTime) || now >= requestObject.delayLoadingTime) {
// no delay - just send
- httpRequests.push(httpRequest);
- return _loadRequest(loader, httpRequest, httpResponse);
+ httpRequests.push(commonMediaRequest);
+ return _loadRequest(loader, commonMediaRequest, commonMediaResponse);
} else {
// delay
let delayedRequest = {
- httpRequest,
- httpResponse
+ httpRequest: commonMediaRequest,
+ httpResponse: commonMediaResponse
};
delayedRequests.push(delayedRequest);
delayedRequest.delayTimeout = setTimeout(function () {
diff --git a/src/streaming/net/XHRLoader.js b/src/streaming/net/XHRLoader.js
index fea478cec1..f724750242 100644
--- a/src/streaming/net/XHRLoader.js
+++ b/src/streaming/net/XHRLoader.js
@@ -43,45 +43,45 @@ function XHRLoader() {
/**
* Load request
- * @param {CommonMediaRequest} httpRequest
- * @param {CommonMediaResponse} httpResponse
+ * @param {CommonMediaRequest} commonMediaRequest
+ * @param {CommonMediaResponse} commonMediaResponse
*/
- function load(httpRequest, httpResponse) {
+ function load(commonMediaRequest, commonMediaResponse) {
xhr = null;
xhr = new XMLHttpRequest();
- xhr.open(httpRequest.method, httpRequest.url, true);
+ xhr.open(commonMediaRequest.method, commonMediaRequest.url, true);
- if (httpRequest.responseType) {
- xhr.responseType = httpRequest.responseType;
+ if (commonMediaRequest.responseType) {
+ xhr.responseType = commonMediaRequest.responseType;
}
- if (httpRequest.headers) {
- for (let header in httpRequest.headers) {
- let value = httpRequest.headers[header];
+ if (commonMediaRequest.headers) {
+ for (let header in commonMediaRequest.headers) {
+ let value = commonMediaRequest.headers[header];
if (value) {
xhr.setRequestHeader(header, value);
}
}
}
- xhr.withCredentials = httpRequest.credentials === 'include';
- xhr.timeout = httpRequest.timeout;
+ xhr.withCredentials = commonMediaRequest.credentials === 'include';
+ xhr.timeout = commonMediaRequest.timeout;
xhr.onload = function () {
- httpResponse.url = this.responseURL;
- httpResponse.status = this.status;
- httpResponse.statusText = this.statusText;
- httpResponse.headers = Utils.parseHttpHeaders(this.getAllResponseHeaders());
- httpResponse.data = this.response;
+ commonMediaResponse.url = this.responseURL;
+ commonMediaResponse.status = this.status;
+ commonMediaResponse.statusText = this.statusText;
+ commonMediaResponse.headers = Utils.parseHttpHeaders(this.getAllResponseHeaders());
+ commonMediaResponse.data = this.response;
}
- xhr.onloadend = httpRequest.customData.onloadend;
- xhr.onprogress = httpRequest.customData.onprogress;
- xhr.onabort = httpRequest.customData.onabort;
- xhr.ontimeout = httpRequest.customData.ontimeout;
+ xhr.onloadend = commonMediaRequest.customData.onloadend;
+ xhr.onprogress = commonMediaRequest.customData.onprogress;
+ xhr.onabort = commonMediaRequest.customData.onabort;
+ xhr.ontimeout = commonMediaRequest.customData.ontimeout;
xhr.send();
- httpRequest.customData.abort = abort.bind(this);
+ commonMediaRequest.customData.abort = abort.bind(this);
return true;
}
diff --git a/src/streaming/text/TextController.js b/src/streaming/text/TextController.js
index ab479f3d38..6fe1882bc7 100644
--- a/src/streaming/text/TextController.js
+++ b/src/streaming/text/TextController.js
@@ -508,6 +508,23 @@ function TextController(config) {
}
}
+ function clearDataForStream(streamId) {
+ if (textSourceBuffers[streamId]) {
+ textSourceBuffers[streamId].resetEmbedded();
+ textSourceBuffers[streamId].reset();
+ delete textSourceBuffers[streamId];
+ }
+
+ if (textTracks[streamId]) {
+ textTracks[streamId].deleteAllTextTracks();
+ delete textTracks[streamId];
+ }
+
+ if (streamData[streamId]) {
+ delete streamData[streamId];
+ }
+ }
+
function resetInitialSettings() {
textSourceBuffers = {};
textTracks = {};
@@ -518,6 +535,11 @@ function TextController(config) {
}
function reset() {
+ Object.keys(textSourceBuffers).forEach((key) => {
+ textSourceBuffers[key].resetEmbedded();
+ textSourceBuffers[key].reset();
+ });
+
dvbFonts.reset();
resetInitialSettings();
eventBus.off(Events.TEXT_TRACKS_QUEUE_INITIALIZED, _onTextTracksAdded, instance);
@@ -528,11 +550,6 @@ function TextController(config) {
eventBus.off(Events.PLAYBACK_TIME_UPDATED, _onPlaybackTimeUpdated, instance);
eventBus.off(Events.PLAYBACK_SEEKING, _onPlaybackSeeking, instance)
}
-
- Object.keys(textSourceBuffers).forEach((key) => {
- textSourceBuffers[key].resetEmbedded();
- textSourceBuffers[key].reset();
- });
}
instance = {
@@ -550,6 +567,7 @@ function TextController(config) {
isTextEnabled,
reset,
setTextTrack,
+ clearDataForStream,
};
setup();
return instance;
diff --git a/src/streaming/thumbnail/ThumbnailTracks.js b/src/streaming/thumbnail/ThumbnailTracks.js
index ca41d51fef..daa26cc886 100644
--- a/src/streaming/thumbnail/ThumbnailTracks.js
+++ b/src/streaming/thumbnail/ThumbnailTracks.js
@@ -325,6 +325,10 @@ function ThumbnailTracks(config) {
representations = [];
currentTrackIndex = -1;
mediaInfo = null;
+ if (dashHandler) {
+ dashHandler.reset();
+ dashHandler = null;
+ }
}
instance = {
diff --git a/src/streaming/utils/BoxParser.js b/src/streaming/utils/BoxParser.js
index 73138aa091..e856dfce99 100644
--- a/src/streaming/utils/BoxParser.js
+++ b/src/streaming/utils/BoxParser.js
@@ -71,25 +71,27 @@ function BoxParser(/*config*/) {
/**
* From the list of type boxes to look for, returns the latest one that is fully completed (header + payload). This
* method only looks into the list of top boxes and doesn't analyze nested boxes.
- * @param {string[]} types
+ * @param {string[]} boxTypesToSearchFor
* @param {ArrayBuffer|uint8Array} buffer
* @param {number} offset
* @returns {IsoBoxSearchInfo}
* @memberof BoxParser#
*/
- function findLastTopIsoBoxCompleted(types, buffer, offset) {
+ function findLastTopIsoBoxCompleted(boxTypesToSearchFor, buffer, offset) {
if (offset === undefined) {
offset = 0;
}
// 8 = size (uint32) + type (4 characters)
if (!buffer || offset + 8 >= buffer.byteLength) {
- return new IsoBoxSearchInfo(0, false);
+ return new IsoBoxSearchInfo({ found: false });
}
const data = (buffer instanceof ArrayBuffer) ? new Uint8Array(buffer) : buffer;
let boxInfo;
- let lastCompletedOffset = 0;
+ let startOffsetOfLastCompletedBox = 0;
+ let sizeOfLastCompletedBox = 0;
+ let typeOfLastCompletedBox = null;
while (offset < data.byteLength) {
const boxSize = parseUint32(data, offset);
const boxType = parseIsoBoxType(data, offset + 4);
@@ -99,10 +101,17 @@ function BoxParser(/*config*/) {
}
if (offset + boxSize <= data.byteLength) {
- if (types.indexOf(boxType) >= 0) {
- boxInfo = new IsoBoxSearchInfo(offset, true, boxSize);
+ if (boxTypesToSearchFor.indexOf(boxType) >= 0) {
+ boxInfo = new IsoBoxSearchInfo({
+ found: true,
+ startOffsetOfLastFoundTargetBox: offset,
+ sizeOfLastFoundTargetBox: boxSize,
+ typeOfLastTargetBox: boxType
+ });
} else {
- lastCompletedOffset = offset + boxSize;
+ startOffsetOfLastCompletedBox = offset;
+ sizeOfLastCompletedBox = boxSize;
+ typeOfLastCompletedBox = boxType;
}
}
@@ -110,7 +119,12 @@ function BoxParser(/*config*/) {
}
if (!boxInfo) {
- return new IsoBoxSearchInfo(lastCompletedOffset, false);
+ return new IsoBoxSearchInfo({
+ found: false,
+ startOffsetOfLastCompletedBox,
+ sizeOfLastCompletedBox,
+ typeOfLastCompletedBox
+ });
}
return boxInfo;
@@ -259,58 +273,12 @@ function BoxParser(/*config*/) {
return initRange;
}
- /**
- * Real-time parsing (whenever data is loaded in the buffer payload) of the payload to capture the moof of a chunk
- * @param {array} types
- * @param {ArrayBuffer} buffer
- * @param {number} offset
- * @return {IsoBoxSearchInfo}
- */
- function parsePayload(types, buffer, offset) {
- if (offset === undefined) {
- offset = 0;
- }
-
- if (!buffer || offset + 8 >= buffer.byteLength) {
- return new IsoBoxSearchInfo(0, false);
- }
-
- const data = (buffer instanceof ArrayBuffer) ? new Uint8Array(buffer) : buffer;
- let boxInfo;
- let lastCompletedOffset = 0;
- while (offset < data.byteLength) {
- const boxSize = parseUint32(data, offset);
- const boxType = parseIsoBoxType(data, offset + 4);
-
- if (boxSize === 0) {
- break;
- }
-
- if (offset + boxSize <= data.byteLength) {
- if (types.indexOf(boxType) >= 0) {
- boxInfo = new IsoBoxSearchInfo(offset, true, boxSize, boxType);
- } else {
- lastCompletedOffset = offset + boxSize;
- }
- }
-
- offset += boxSize;
- }
-
- if (!boxInfo) {
- return new IsoBoxSearchInfo(lastCompletedOffset, false);
- }
-
- return boxInfo;
- }
-
instance = {
- parse,
+ findInitRange,
findLastTopIsoBoxCompleted,
getMediaTimescaleFromMoov,
getSamplesInfo,
- findInitRange,
- parsePayload
+ parse,
};
setup();
diff --git a/src/streaming/vo/CommonMediaRequest.js b/src/streaming/vo/CommonMediaRequest.js
new file mode 100644
index 0000000000..819d0e8884
--- /dev/null
+++ b/src/streaming/vo/CommonMediaRequest.js
@@ -0,0 +1,27 @@
+class CommonMediaRequest {
+ /**
+ * @param {Object} params
+ * @param {string} params.url
+ * @param {string} params.method
+ * @param {string} [params.responseType]
+ * @param {Object} [params.headers]
+ * @param {RequestCredentials} [params.credentials]
+ * @param {RequestMode} [params.mode]
+ * @param {number} [params.timeout]
+ * @param {Cmcd} [params.cmcd]
+ * @param {any} [params.customData]
+ */
+ constructor(params) {
+ this.url = params.url;
+ this.method = params.method;
+ this.responseType = params.responseType !== undefined ? params.responseType : null;
+ this.headers = params.headers !== undefined ? params.headers : {};
+ this.credentials = params.credentials !== undefined ? params.credentials : null;
+ this.mode = params.mode !== undefined ? params.mode : null;
+ this.timeout = params.timeout !== undefined ? params.timeout : 0;
+ this.cmcd = params.cmcd !== undefined ? params.cmcd : null;
+ this.customData = params.customData !== undefined ? params.customData : null;
+ }
+}
+
+export default CommonMediaRequest;
diff --git a/src/streaming/vo/CommonMediaResponse.js b/src/streaming/vo/CommonMediaResponse.js
new file mode 100644
index 0000000000..e4d7f2c654
--- /dev/null
+++ b/src/streaming/vo/CommonMediaResponse.js
@@ -0,0 +1,27 @@
+class CommonMediaResponse {
+ /**
+ * @param {Object} params
+ * @param {CommonMediaRequest} params.request
+ * @param {string} [params.url]
+ * @param {boolean} [params.redirected]
+ * @param {number} [params.status]
+ * @param {string} [params.statusText]
+ * @param {string} [params.type]
+ * @param {Object} [params.headers]
+ * @param {any} [params.data]
+ * @param {ResourceTiming} [params.resourceTiming]
+ */
+ constructor(params) {
+ this.request = params.request;
+ this.url = params.url !== undefined ? params.url : null;
+ this.redirected = params.redirected !== undefined ? params.redirected : false;
+ this.status = params.status !== undefined ? params.status : null;
+ this.statusText = params.statusText !== undefined ? params.statusText : '';
+ this.type = params.type !== undefined ? params.type : '';
+ this.headers = params.headers !== undefined ? params.headers : {};
+ this.data = params.data !== undefined ? params.data : null;
+ this.resourceTiming = params.resourceTiming !== undefined ? params.resourceTiming : null;
+ }
+}
+
+export default CommonMediaResponse;
diff --git a/src/streaming/vo/IsoBoxSearchInfo.js b/src/streaming/vo/IsoBoxSearchInfo.js
index 39bd03d2eb..b60b2ccf21 100644
--- a/src/streaming/vo/IsoBoxSearchInfo.js
+++ b/src/streaming/vo/IsoBoxSearchInfo.js
@@ -33,10 +33,22 @@
* @ignore
*/
class IsoBoxSearchInfo {
- constructor(lastCompletedOffset, found, size) {
- this.lastCompletedOffset = lastCompletedOffset;
- this.found = found;
- this.size = size;
+ constructor({
+ found,
+ sizeOfLastCompletedBox,
+ sizeOfLastFoundTargetBox,
+ startOffsetOfLastCompletedBox,
+ startOffsetOfLastFoundTargetBox,
+ typeOfLastCompletedBox,
+ typeOfLastTargetBox,
+ }) {
+ this.found = found !== undefined ? found : false;
+ this.sizeOfLastCompletedBox = sizeOfLastCompletedBox !== undefined ? sizeOfLastCompletedBox : 0;
+ this.sizeOfLastFoundTargetBox = sizeOfLastFoundTargetBox !== undefined ? sizeOfLastFoundTargetBox : 0;
+ this.startOffsetOfLastCompletedBox = startOffsetOfLastCompletedBox !== undefined ? startOffsetOfLastCompletedBox : 0;
+ this.startOffsetOfLastFoundTargetBox = startOffsetOfLastFoundTargetBox !== undefined ? startOffsetOfLastFoundTargetBox : 0;
+ this.typeOfLastCompletedBox = typeOfLastCompletedBox !== undefined ? typeOfLastCompletedBox : null;
+ this.typeOfLastTargetBox = typeOfLastTargetBox !== undefined ? typeOfLastTargetBox : null;
}
}
diff --git a/test/unit/mocks/TextControllerMock.js b/test/unit/mocks/TextControllerMock.js
index d70c287f8a..7c450bab70 100644
--- a/test/unit/mocks/TextControllerMock.js
+++ b/test/unit/mocks/TextControllerMock.js
@@ -32,6 +32,9 @@ class TextControllerMock {
initializeForStream() {
}
+
+ clearDataForStream() {
+ }
}
export default TextControllerMock;
diff --git a/test/unit/test/streaming/streaming.net.FetchLoader.js b/test/unit/test/streaming/streaming.net.FetchLoader.js
index f52322ddc1..7f9f6e15cc 100644
--- a/test/unit/test/streaming/streaming.net.FetchLoader.js
+++ b/test/unit/test/streaming/streaming.net.FetchLoader.js
@@ -11,10 +11,10 @@ describe('FetchLoader', function () {
fetchLoader = FetchLoader(context).create({});
// 1MB test, 2.4MB received
- const Data1MB = '[{"ts":1529393808348,"bytes":16384},{"ts":1529393808617,"bytes":32768},{"ts":1529393808864,"bytes":32768},{"ts":1529393809121,"bytes":32768},{"ts":1529393809378,"bytes":32768},{"ts":1529393809637,"bytes":32768},{"ts":1529393809942,"bytes":32768},{"ts":1529393810258,"bytes":32768},{"ts":1529393810516,"bytes":32768},{"ts":1529393810762,"bytes":32759},{"ts":1529393811020,"bytes":32768},{"ts":1529393811326,"bytes":32768},{"ts":1529393811582,"bytes":32768},{"ts":1529393811840,"bytes":32768},{"ts":1529393812099,"bytes":32768},{"ts":1529393812380,"bytes":32768},{"ts":1529393812508,"bytes":16393},{"ts":1529393812766,"bytes":32768},{"ts":1529393813023,"bytes":32768},{"ts":1529393813327,"bytes":32759},{"ts":1529393813585,"bytes":32768},{"ts":1529393813843,"bytes":32768},{"ts":1529393814090,"bytes":32768},{"ts":1529393814384,"bytes":32768},{"ts":1529393814641,"bytes":32768},{"ts":1529393814899,"bytes":32768},{"ts":1529393815191,"bytes":32768},{"ts":1529393815449,"bytes":32768},{"ts":1529393815708,"bytes":32759},{"ts":1529393815954,"bytes":32768},{"ts":1529393816233,"bytes":32768},{"ts":1529393816492,"bytes":32768},{"ts":1529393816750,"bytes":32768},{"ts":1529393816751,"bytes":18},{"ts":1529393817007,"bytes":32768},{"ts":1529393817264,"bytes":32768},{"ts":1529393817510,"bytes":32768},{"ts":1529393817792,"bytes":32768},{"ts":1529393818075,"bytes":32759},{"ts":1529393818368,"bytes":32768},{"ts":1529393818626,"bytes":32768},{"ts":1529393818883,"bytes":32768},{"ts":1529393819174,"bytes":32768},{"ts":1529393819422,"bytes":32768},{"ts":1529393819679,"bytes":32768},{"ts":1529393819937,"bytes":32768},{"ts":1529393820241,"bytes":32768},{"ts":1529393820499,"bytes":32759},{"ts":1529393820758,"bytes":32768},{"ts":1529393821004,"bytes":32768},{"ts":1529393821006,"bytes":18},{"ts":1529393821332,"bytes":32768},{"ts":1529393821589,"bytes":32768},{"ts":1529393821848,"bytes":32768},{"ts":1529393822104,"bytes":32768},{"ts":1529393822398,"bytes":32768},{"ts":1529393822656,"bytes":32768},{"ts":1529393822903,"bytes":32768},{"ts":1529393823193,"bytes":32759},{"ts":1529393823453,"bytes":32768},{"ts":1529393823710,"bytes":32768},{"ts":1529393823968,"bytes":32768},{"ts":1529393824261,"bytes":32768},{"ts":1529393824506,"bytes":32768},{"ts":1529393824766,"bytes":32768},{"ts":1529393825022,"bytes":32768},{"ts":1529393825280,"bytes":32768},{"ts":1529393825282,"bytes":9},{"ts":1529393825538,"bytes":32759},{"ts":1529393825797,"bytes":32768},{"ts":1529393826090,"bytes":32768},{"ts":1529393826382,"bytes":32768},{"ts":1529393826639,"bytes":32768},{"ts":1529393826897,"bytes":32768},{"ts":1529393827191,"bytes":32768},{"ts":1529393827448,"bytes":32768},{"ts":1529393827706,"bytes":32768},{"ts":1529393827800,"bytes":13409}]';
+ const Data1MB = '[{"timestamp":1529393808348,"bytes":16384},{"timestamp":1529393808617,"bytes":32768},{"timestamp":1529393808864,"bytes":32768},{"timestamp":1529393809121,"bytes":32768},{"timestamp":1529393809378,"bytes":32768},{"timestamp":1529393809637,"bytes":32768},{"timestamp":1529393809942,"bytes":32768},{"timestamp":1529393810258,"bytes":32768},{"timestamp":1529393810516,"bytes":32768},{"timestamp":1529393810762,"bytes":32759},{"timestamp":1529393811020,"bytes":32768},{"timestamp":1529393811326,"bytes":32768},{"timestamp":1529393811582,"bytes":32768},{"timestamp":1529393811840,"bytes":32768},{"timestamp":1529393812099,"bytes":32768},{"timestamp":1529393812380,"bytes":32768},{"timestamp":1529393812508,"bytes":16393},{"timestamp":1529393812766,"bytes":32768},{"timestamp":1529393813023,"bytes":32768},{"timestamp":1529393813327,"bytes":32759},{"timestamp":1529393813585,"bytes":32768},{"timestamp":1529393813843,"bytes":32768},{"timestamp":1529393814090,"bytes":32768},{"timestamp":1529393814384,"bytes":32768},{"timestamp":1529393814641,"bytes":32768},{"timestamp":1529393814899,"bytes":32768},{"timestamp":1529393815191,"bytes":32768},{"timestamp":1529393815449,"bytes":32768},{"timestamp":1529393815708,"bytes":32759},{"timestamp":1529393815954,"bytes":32768},{"timestamp":1529393816233,"bytes":32768},{"timestamp":1529393816492,"bytes":32768},{"timestamp":1529393816750,"bytes":32768},{"timestamp":1529393816751,"bytes":18},{"timestamp":1529393817007,"bytes":32768},{"timestamp":1529393817264,"bytes":32768},{"timestamp":1529393817510,"bytes":32768},{"timestamp":1529393817792,"bytes":32768},{"timestamp":1529393818075,"bytes":32759},{"timestamp":1529393818368,"bytes":32768},{"timestamp":1529393818626,"bytes":32768},{"timestamp":1529393818883,"bytes":32768},{"timestamp":1529393819174,"bytes":32768},{"timestamp":1529393819422,"bytes":32768},{"timestamp":1529393819679,"bytes":32768},{"timestamp":1529393819937,"bytes":32768},{"timestamp":1529393820241,"bytes":32768},{"timestamp":1529393820499,"bytes":32759},{"timestamp":1529393820758,"bytes":32768},{"timestamp":1529393821004,"bytes":32768},{"timestamp":1529393821006,"bytes":18},{"timestamp":1529393821332,"bytes":32768},{"timestamp":1529393821589,"bytes":32768},{"timestamp":1529393821848,"bytes":32768},{"timestamp":1529393822104,"bytes":32768},{"timestamp":1529393822398,"bytes":32768},{"timestamp":1529393822656,"bytes":32768},{"timestamp":1529393822903,"bytes":32768},{"timestamp":1529393823193,"bytes":32759},{"timestamp":1529393823453,"bytes":32768},{"timestamp":1529393823710,"bytes":32768},{"timestamp":1529393823968,"bytes":32768},{"timestamp":1529393824261,"bytes":32768},{"timestamp":1529393824506,"bytes":32768},{"timestamp":1529393824766,"bytes":32768},{"timestamp":1529393825022,"bytes":32768},{"timestamp":1529393825280,"bytes":32768},{"timestamp":1529393825282,"bytes":9},{"timestamp":1529393825538,"bytes":32759},{"timestamp":1529393825797,"bytes":32768},{"timestamp":1529393826090,"bytes":32768},{"timestamp":1529393826382,"bytes":32768},{"timestamp":1529393826639,"bytes":32768},{"timestamp":1529393826897,"bytes":32768},{"timestamp":1529393827191,"bytes":32768},{"timestamp":1529393827448,"bytes":32768},{"timestamp":1529393827706,"bytes":32768},{"timestamp":1529393827800,"bytes":13409}]';
let result1MB = fetchLoader.calculateDownloadedTime(JSON.parse(Data1MB), 2405464);
- const Data300MB = '[{"ts":1529394745999,"bytes":32768},{"ts":1529394746180,"bytes":32768},{"ts":1529394746182,"bytes":32768},{"ts":1529394746359,"bytes":32768},{"ts":1529394746361,"bytes":65536},{"ts":1529394746363,"bytes":16384},{"ts":1529394746537,"bytes":16384},{"ts":1529394746538,"bytes":65536},{"ts":1529394746540,"bytes":38539},{"ts":1529394746718,"bytes":32768},{"ts":1529394746720,"bytes":65536},{"ts":1529394746720,"bytes":32768},{"ts":1529394746897,"bytes":32768},{"ts":1529394746899,"bytes":26997},{"ts":1529394746900,"bytes":5771},{"ts":1529394746903,"bytes":65536},{"ts":1529394747072,"bytes":5771},{"ts":1529394747098,"bytes":49152},{"ts":1529394747099,"bytes":16384},{"ts":1529394747101,"bytes":65536},{"ts":1529394747274,"bytes":32768},{"ts":1529394747275,"bytes":65536},{"ts":1529394747277,"bytes":49152},{"ts":1529394747453,"bytes":22155},{"ts":1529394748096,"bytes":32761},{"ts":1529394748098,"bytes":16391},{"ts":1529394748099,"bytes":16384},{"ts":1529394748273,"bytes":32768},{"ts":1529394748275,"bytes":16384},{"ts":1529394748277,"bytes":31839},{"ts":1529394748279,"bytes":33697},{"ts":1529394748452,"bytes":32768},{"ts":1529394748454,"bytes":16384},{"ts":1529394748456,"bytes":65536},{"ts":1529394748458,"bytes":5771},{"ts":1529394749095,"bytes":16384},{"ts":1529394749097,"bytes":32768},{"ts":1529394749279,"bytes":16384},{"ts":1529394749279,"bytes":32768},{"ts":1529394749281,"bytes":65536},{"ts":1529394749283,"bytes":16384},{"ts":1529394749458,"bytes":32768},{"ts":1529394749460,"bytes":65536},{"ts":1529394749461,"bytes":22155},{"ts":1529394750095,"bytes":32761},{"ts":1529394750096,"bytes":7},{"ts":1529394750097,"bytes":16384},{"ts":1529394750273,"bytes":20297},{"ts":1529394750275,"bytes":28855},{"ts":1529394750276,"bytes":65536},{"ts":1529394750452,"bytes":32768},{"ts":1529394750454,"bytes":65536},{"ts":1529394750455,"bytes":16384},{"ts":1529394750551,"bytes":16384},{"ts":1529394750628,"bytes":5771},{"ts":1529394751095,"bytes":32761},{"ts":1529394751097,"bytes":7},{"ts":1529394751099,"bytes":16384},{"ts":1529394751275,"bytes":65536},{"ts":1529394751277,"bytes":65536},{"ts":1529394751452,"bytes":32768},{"ts":1529394751454,"bytes":65536},{"ts":1529394751455,"bytes":14526},{"ts":1529394751456,"bytes":7629},{"ts":1529394752095,"bytes":16384},{"ts":1529394752098,"bytes":32768},{"ts":1529394752273,"bytes":32768},{"ts":1529394752276,"bytes":65536},{"ts":1529394752277,"bytes":32768},{"ts":1529394752451,"bytes":32768},{"ts":1529394752454,"bytes":65536},{"ts":1529394752466,"bytes":22155}]';
+ const Data300MB = '[{"timestamp":1529394745999,"bytes":32768},{"timestamp":1529394746180,"bytes":32768},{"timestamp":1529394746182,"bytes":32768},{"timestamp":1529394746359,"bytes":32768},{"timestamp":1529394746361,"bytes":65536},{"timestamp":1529394746363,"bytes":16384},{"timestamp":1529394746537,"bytes":16384},{"timestamp":1529394746538,"bytes":65536},{"timestamp":1529394746540,"bytes":38539},{"timestamp":1529394746718,"bytes":32768},{"timestamp":1529394746720,"bytes":65536},{"timestamp":1529394746720,"bytes":32768},{"timestamp":1529394746897,"bytes":32768},{"timestamp":1529394746899,"bytes":26997},{"timestamp":1529394746900,"bytes":5771},{"timestamp":1529394746903,"bytes":65536},{"timestamp":1529394747072,"bytes":5771},{"timestamp":1529394747098,"bytes":49152},{"timestamp":1529394747099,"bytes":16384},{"timestamp":1529394747101,"bytes":65536},{"timestamp":1529394747274,"bytes":32768},{"timestamp":1529394747275,"bytes":65536},{"timestamp":1529394747277,"bytes":49152},{"timestamp":1529394747453,"bytes":22155},{"timestamp":1529394748096,"bytes":32761},{"timestamp":1529394748098,"bytes":16391},{"timestamp":1529394748099,"bytes":16384},{"timestamp":1529394748273,"bytes":32768},{"timestamp":1529394748275,"bytes":16384},{"timestamp":1529394748277,"bytes":31839},{"timestamp":1529394748279,"bytes":33697},{"timestamp":1529394748452,"bytes":32768},{"timestamp":1529394748454,"bytes":16384},{"timestamp":1529394748456,"bytes":65536},{"timestamp":1529394748458,"bytes":5771},{"timestamp":1529394749095,"bytes":16384},{"timestamp":1529394749097,"bytes":32768},{"timestamp":1529394749279,"bytes":16384},{"timestamp":1529394749279,"bytes":32768},{"timestamp":1529394749281,"bytes":65536},{"timestamp":1529394749283,"bytes":16384},{"timestamp":1529394749458,"bytes":32768},{"timestamp":1529394749460,"bytes":65536},{"timestamp":1529394749461,"bytes":22155},{"timestamp":1529394750095,"bytes":32761},{"timestamp":1529394750096,"bytes":7},{"timestamp":1529394750097,"bytes":16384},{"timestamp":1529394750273,"bytes":20297},{"timestamp":1529394750275,"bytes":28855},{"timestamp":1529394750276,"bytes":65536},{"timestamp":1529394750452,"bytes":32768},{"timestamp":1529394750454,"bytes":65536},{"timestamp":1529394750455,"bytes":16384},{"timestamp":1529394750551,"bytes":16384},{"timestamp":1529394750628,"bytes":5771},{"timestamp":1529394751095,"bytes":32761},{"timestamp":1529394751097,"bytes":7},{"timestamp":1529394751099,"bytes":16384},{"timestamp":1529394751275,"bytes":65536},{"timestamp":1529394751277,"bytes":65536},{"timestamp":1529394751452,"bytes":32768},{"timestamp":1529394751454,"bytes":65536},{"timestamp":1529394751455,"bytes":14526},{"timestamp":1529394751456,"bytes":7629},{"timestamp":1529394752095,"bytes":16384},{"timestamp":1529394752098,"bytes":32768},{"timestamp":1529394752273,"bytes":32768},{"timestamp":1529394752276,"bytes":65536},{"timestamp":1529394752277,"bytes":32768},{"timestamp":1529394752451,"bytes":32768},{"timestamp":1529394752454,"bytes":65536},{"timestamp":1529394752466,"bytes":22155}]';
let result300MB = fetchLoader.calculateDownloadedTime(JSON.parse(Data300MB), 2405464);
expect(result1MB).to.be.above(10000);
expect(result300MB).to.be.below(200);
diff --git a/test/unit/test/streaming/streaming.utils.BoxParser.js b/test/unit/test/streaming/streaming.utils.BoxParser.js
index d2ea057931..13a6ccc617 100644
--- a/test/unit/test/streaming/streaming.utils.BoxParser.js
+++ b/test/unit/test/streaming/streaming.utils.BoxParser.js
@@ -129,7 +129,8 @@ describe('BoxParser', function () {
const res = boxParser.findLastTopIsoBoxCompleted(['moov'], data.buffer);
expect(res.found).to.be.false;
- expect(res.lastCompletedOffset).to.equal(36);
+ expect(res.startOffsetOfLastCompletedBox).to.equal(28);
+ expect(res.sizeOfLastCompletedBox).to.equal(8);
});
it('should not return null when looking for init range in a completed init segment', () => {