mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-16 16:16:40 +03:00
d36ff6553c
By limiting progress events to only ones with minimal chunk size, we might end up with feeding better default ABR implementation, which should result in more accurate adaptation in case of network throttle.
175 lines
6.0 KiB
JavaScript
175 lines
6.0 KiB
JavaScript
/*! @license
|
|
* Shaka Player
|
|
* Copyright 2016 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
goog.provide('shaka.net.HttpXHRPlugin');
|
|
|
|
goog.require('goog.asserts');
|
|
goog.require('shaka.net.HttpPluginUtils');
|
|
goog.require('shaka.net.NetworkingEngine');
|
|
goog.require('shaka.util.AbortableOperation');
|
|
goog.require('shaka.util.Error');
|
|
|
|
|
|
/**
|
|
* @summary A networking plugin to handle http and https URIs via XHR.
|
|
* @export
|
|
*/
|
|
shaka.net.HttpXHRPlugin = class {
|
|
/**
|
|
* @param {string} uri
|
|
* @param {shaka.extern.Request} request
|
|
* @param {shaka.net.NetworkingEngine.RequestType} requestType
|
|
* @param {shaka.extern.ProgressUpdated} progressUpdated Called when a
|
|
* progress event happened.
|
|
* @param {shaka.extern.HeadersReceived} headersReceived Called when the
|
|
* headers for the download are received, but before the body is.
|
|
* @param {shaka.extern.SchemePluginConfig} config
|
|
* @return {!shaka.extern.IAbortableOperation.<shaka.extern.Response>}
|
|
* @export
|
|
*/
|
|
static parse(uri, request, requestType, progressUpdated, headersReceived,
|
|
config) {
|
|
const xhr = new shaka.net.HttpXHRPlugin.Xhr_();
|
|
|
|
// Last time stamp when we got a progress event.
|
|
let lastTime = Date.now();
|
|
// Last number of bytes loaded, from progress event.
|
|
let lastLoaded = 0;
|
|
|
|
const promise = new Promise(((resolve, reject) => {
|
|
xhr.open(request.method, uri, true);
|
|
xhr.responseType = 'arraybuffer';
|
|
xhr.timeout = request.retryParameters.timeout;
|
|
xhr.withCredentials = request.allowCrossSiteCredentials;
|
|
|
|
xhr.onabort = () => {
|
|
reject(new shaka.util.Error(
|
|
shaka.util.Error.Severity.RECOVERABLE,
|
|
shaka.util.Error.Category.NETWORK,
|
|
shaka.util.Error.Code.OPERATION_ABORTED,
|
|
uri, requestType));
|
|
};
|
|
let calledHeadersReceived = false;
|
|
xhr.onreadystatechange = (event) => {
|
|
// See if the readyState is 2 ("HEADERS_RECEIVED").
|
|
if (xhr.readyState == 2 && !calledHeadersReceived) {
|
|
const headers = shaka.net.HttpXHRPlugin.headersToGenericObject_(xhr);
|
|
headersReceived(headers);
|
|
// Don't send out this event twice.
|
|
calledHeadersReceived = true;
|
|
}
|
|
};
|
|
xhr.onload = (event) => {
|
|
const headers = shaka.net.HttpXHRPlugin.headersToGenericObject_(xhr);
|
|
goog.asserts.assert(xhr.response instanceof ArrayBuffer,
|
|
'XHR should have a response by now!');
|
|
const xhrResponse = xhr.response;
|
|
|
|
try {
|
|
const currentTime = Date.now();
|
|
progressUpdated(currentTime - lastTime, event.loaded - lastLoaded,
|
|
/* numBytesRemaining= */ 0);
|
|
const response = shaka.net.HttpPluginUtils.makeResponse(headers,
|
|
xhrResponse, xhr.status, uri, xhr.responseURL, requestType);
|
|
resolve(response);
|
|
} catch (error) {
|
|
goog.asserts.assert(error instanceof shaka.util.Error,
|
|
'Wrong error type!');
|
|
reject(error);
|
|
}
|
|
};
|
|
xhr.onerror = (event) => {
|
|
reject(new shaka.util.Error(
|
|
shaka.util.Error.Severity.RECOVERABLE,
|
|
shaka.util.Error.Category.NETWORK,
|
|
shaka.util.Error.Code.HTTP_ERROR,
|
|
uri, event, requestType));
|
|
};
|
|
xhr.ontimeout = (event) => {
|
|
reject(new shaka.util.Error(
|
|
shaka.util.Error.Severity.RECOVERABLE,
|
|
shaka.util.Error.Category.NETWORK,
|
|
shaka.util.Error.Code.TIMEOUT,
|
|
uri, requestType));
|
|
};
|
|
xhr.onprogress = (event) => {
|
|
const currentTime = Date.now();
|
|
// If the time between last time and this time we got progress event
|
|
// is long enough, or if a whole segment is downloaded, call
|
|
// progressUpdated().
|
|
const minBytes = config.minBytesForProgressEvents || 0;
|
|
const chunkSize = event.loaded - lastLoaded;
|
|
if ((currentTime - lastTime > 100 && chunkSize >= minBytes) ||
|
|
(event.lengthComputable && event.loaded == event.total)) {
|
|
const numBytesRemaining =
|
|
xhr.readyState == 4 ? 0 : event.total - event.loaded;
|
|
progressUpdated(currentTime - lastTime, chunkSize,
|
|
numBytesRemaining);
|
|
lastLoaded = event.loaded;
|
|
lastTime = currentTime;
|
|
}
|
|
};
|
|
|
|
for (const key in request.headers) {
|
|
// The Fetch API automatically normalizes outgoing header keys to
|
|
// lowercase. For consistency's sake, do it here too.
|
|
const lowercasedKey = key.toLowerCase();
|
|
xhr.setRequestHeader(lowercasedKey, request.headers[key]);
|
|
}
|
|
xhr.send(request.body);
|
|
}));
|
|
|
|
return new shaka.util.AbortableOperation(
|
|
promise,
|
|
() => {
|
|
xhr.abort();
|
|
return Promise.resolve();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param {!XMLHttpRequest} xhr
|
|
* @return {!Object.<string, string>}
|
|
* @private
|
|
*/
|
|
static headersToGenericObject_(xhr) {
|
|
// Since Edge incorrectly return the header with a leading new
|
|
// line character ('\n'), we trim the header here.
|
|
const headerLines = xhr.getAllResponseHeaders().trim().split('\r\n');
|
|
const headers = {};
|
|
for (const header of headerLines) {
|
|
/** @type {!Array.<string>} */
|
|
const parts = header.split(': ');
|
|
headers[parts[0].toLowerCase()] = parts.slice(1).join(': ');
|
|
}
|
|
return headers;
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Overridden in unit tests, but compiled out in production.
|
|
*
|
|
* @const {function(new: XMLHttpRequest)}
|
|
* @private
|
|
*/
|
|
shaka.net.HttpXHRPlugin.Xhr_ = window.XMLHttpRequest;
|
|
|
|
|
|
shaka.net.NetworkingEngine.registerScheme(
|
|
'http', shaka.net.HttpXHRPlugin.parse,
|
|
shaka.net.NetworkingEngine.PluginPriority.FALLBACK,
|
|
/* progressSupport= */ true);
|
|
shaka.net.NetworkingEngine.registerScheme(
|
|
'https', shaka.net.HttpXHRPlugin.parse,
|
|
shaka.net.NetworkingEngine.PluginPriority.FALLBACK,
|
|
/* progressSupport= */ true);
|
|
shaka.net.NetworkingEngine.registerScheme(
|
|
'blob', shaka.net.HttpXHRPlugin.parse,
|
|
shaka.net.NetworkingEngine.PluginPriority.FALLBACK,
|
|
/* progressSupport= */ true);
|
|
|