invidious/assets/js/sabr_scheme_plugin.js

711 lines
27 KiB
JavaScript

// Port of FreeTube's SabrSchemePlugin.js
// Registers the 'sabr' networking scheme with Shaka.
// Reads shaka from window.shaka, googlevideo symbols from window.googlevideo.
// Invidious is browser-sandboxed, so outbound fetches are routed through
// SABRHelpers.fetchWithProxy (the Crystal /proxy route) instead of hitting
// googlevideo directly.
// Fixes the latent SABR_REDIRECT bug (write sabrStreamState.sabrUrl, not currentState.sabrUrl).
// Exports window.setupSabrScheme.
'use strict';
(function () {
// Lazily resolve dependencies at call time so load order is flexible.
function shaka() { return window.shaka; }
function gv() { return window.googlevideo; }
function deepCopy(obj) {
return JSON.parse(JSON.stringify(obj));
}
function formatIdFromString(str) {
var parts = str.split('-');
return {
itag: parseInt(parts[0], 10),
lastModified: parts[1],
xtags: parts[2]
};
}
function createBufferedRange(formatId, buffered, segmentIndex) {
var endSegmentIndex = segmentIndex.find(buffered.end);
if (endSegmentIndex == null) {
endSegmentIndex = segmentIndex.getNumReferences() - 1;
}
return {
formatId: formatId,
startTimeMs: String(Math.round(buffered.start * 1000)),
durationMs: String(Math.round((buffered.end - buffered.start) * 1000)),
startSegmentIndex: segmentIndex.find(buffered.start),
endSegmentIndex: endSegmentIndex
};
}
function createFullBufferRange(formatId) {
var MAX_INT32_VALUE = gv().utils.MAX_INT32_VALUE;
return {
formatId: formatId,
durationMs: MAX_INT32_VALUE,
startTimeMs: '0',
startSegmentIndex: parseInt(MAX_INT32_VALUE, 10),
endSegmentIndex: parseInt(MAX_INT32_VALUE, 10),
timeRange: {
durationTicks: MAX_INT32_VALUE,
startTicks: '0',
timescale: 1000
}
};
}
function fillBufferedRanges(player, manifest, audioFormatsActive, streamIsVideo, streamIsAudio, bufferedRanges, activeVariant) {
var bufferedInfo = player.getBufferedInfo();
if (bufferedInfo.audio.length === 0 && bufferedInfo.video.length === 0) return;
var activeManifestVariant;
if (audioFormatsActive) {
activeManifestVariant = manifest.variants.find(function (variant) {
return variant.audio.originalId === activeVariant.originalAudioId;
});
} else {
activeManifestVariant = manifest.variants.find(function (variant) {
return variant.audio.originalId === activeVariant.originalAudioId &&
variant.video.originalId === activeVariant.originalVideoId;
});
}
var audioFormatId = formatIdFromString(activeVariant.originalAudioId);
var audioSegmentIndex = activeManifestVariant.audio.segmentIndex;
if (streamIsVideo) {
bufferedRanges.push(createFullBufferRange(audioFormatId));
} else {
for (var i = 0; i < bufferedInfo.audio.length; i++) {
bufferedRanges.push(createBufferedRange(audioFormatId, bufferedInfo.audio[i], audioSegmentIndex));
}
}
var videoFormatId;
var videoSegmentIndex;
if (streamIsAudio && bufferedInfo.video.length > 0) {
videoFormatId = formatIdFromString(activeVariant.originalVideoId);
bufferedRanges.push(createFullBufferRange(videoFormatId));
} else {
for (var j = 0; j < bufferedInfo.video.length; j++) {
var buffered = bufferedInfo.video[j];
if (!videoFormatId) {
videoFormatId = formatIdFromString(activeVariant.originalVideoId);
}
if (!videoSegmentIndex) {
videoSegmentIndex = activeManifestVariant.video.segmentIndex;
}
bufferedRanges.push(createBufferedRange(videoFormatId, buffered, videoSegmentIndex));
}
}
}
function createCacheResponse(ShakaAbortableOperation, uri, request, data) {
return ShakaAbortableOperation.completed({
data: data,
fromCache: true,
headers: {},
originalRequest: request,
originalUri: uri,
uri: uri
});
}
function createRecoverableNetworkError(code) {
var ShakaError = shaka().util.Error;
var args = Array.prototype.slice.call(arguments, 1);
return new ShakaError(ShakaError.Severity.RECOVERABLE, ShakaError.Category.NETWORK, code, args);
}
function prepareSabrContexts(sabrStreamState) {
var sabrContexts = [];
var unsentSabrContexts = [];
sabrStreamState.sabrContexts.forEach(function (ctxUpdate) {
if (sabrStreamState.activeSabrContextTypes.has(ctxUpdate.type)) {
sabrContexts.push(ctxUpdate);
} else {
unsentSabrContexts.push(ctxUpdate.type);
}
});
return { sabrContexts: sabrContexts, unsentSabrContexts: unsentSabrContexts };
}
function decodePart(part, decoder) {
if (!part.data.chunks.length) return undefined;
try {
var concatenateChunks = gv().utils.concatenateChunks;
var chunk = part.data.chunks.length === 1 ? part.data.chunks[0] : concatenateChunks(part.data.chunks);
return decoder.decode(chunk);
} catch (e) {
return undefined;
}
}
function createTimeoutController(callback, timeoutMs) {
return {
_timeout: setTimeout(callback, timeoutMs),
_resetCount: 0,
resetTimeoutOnce: function () {
if (this._resetCount > 0) return;
this.clearTimeout();
this._timeout = setTimeout(callback, timeoutMs);
this._resetCount++;
},
clearTimeout: function () {
clearTimeout(this._timeout);
}
};
}
// Wrap the youtube/googlevideo fetch URL through the Invidious /proxy route,
// forwarding the UMP headers youtube needs. Returns a fetch-compatible RequestInit.
function proxyFetch(url, requestInit) {
var headers = new Headers(requestInit.headers || {});
// Ensure these headers survive the proxy hop.
if (!headers.has('x-youtube-client-name')) {
headers.set('x-youtube-client-name', '3');
}
return SABRHelpers.fetchWithProxy(url, Object.assign({}, requestInit, { headers: headers }));
}
async function doRequest(operationInputs, currentState) {
var ShakaError = shaka().util.Error;
var protos = gv().protos;
var ump = gv().ump;
var utils = gv().utils;
var UmpReader = ump.UmpReader;
var CompositeBuffer = ump.CompositeBuffer;
var UMPPartId = protos.UMPPartId;
var VideoPlaybackAbrRequest = protos.VideoPlaybackAbrRequest;
var StreamProtectionStatus = protos.StreamProtectionStatus;
var SabrError = protos.SabrError;
var SabrRedirect = protos.SabrRedirect;
var MediaHeader = protos.MediaHeader;
var SabrContextSendingPolicy = protos.SabrContextSendingPolicy;
var SabrContextUpdate = protos.SabrContextUpdate;
var SabrContextWritePolicy = protos.SabrContextWritePolicy;
var NextRequestPolicy = protos.NextRequestPolicy;
var PlaybackCookie = protos.PlaybackCookie;
var ReloadPlaybackContext = protos.ReloadPlaybackContext;
var response;
var chunkedDataBuffer = null;
var responseDataChunks = [];
var segmentComplete = false;
var shouldRetry = false;
var shouldRetryDueToNextRequestPolicy = false;
var invalidPoToken = false;
var error;
if (currentState.sabrStreamState.playerReloadRequested) {
throw createRecoverableNetworkError(ShakaError.Code.OPERATION_ABORTED, operationInputs.uri, operationInputs.requestType);
}
try {
var shouldReloadDueToBackoffLoop = false;
if ((currentState.sabrStreamState.nextRequestPolicy?.backoffTimeMs || 0) > 0) {
var currentBackoffTimeMs = currentState.sabrStreamState.nextRequestPolicy.backoffTimeMs;
currentState.eventEmitter.emit('backoff-requested', { backoffMs: currentBackoffTimeMs });
await new Promise(function (resolve, reject) {
setTimeout(resolve, currentBackoffTimeMs);
currentState.abortController.signal.addEventListener('abort', reject);
});
currentState.timeoutController?.resetTimeoutOnce();
currentState.cumulativeBackOffTimeMs += currentState.sabrStreamState.nextRequestPolicy.backoffTimeMs;
currentState.cumulativeBackOffRequested += 1;
var timeoutMs = operationInputs.request.retryParameters.timeout;
if (currentState.cumulativeBackOffRequested >= 3 || (timeoutMs > 0 && timeoutMs <= (currentState.cumulativeBackOffTimeMs + currentBackoffTimeMs))) {
shouldReloadDueToBackoffLoop = true;
}
}
if (shouldReloadDueToBackoffLoop || currentState.cumulativeRetryDueToNextRequestPolicy >= 100) {
currentState.sabrStreamState.playerReloadRequested = true;
if (!currentState.abortController.signal.aborted) {
currentState.abortController.abort();
currentState.eventEmitter.emit('reload');
}
}
var sabrURL = new URL(currentState.sabrStreamState.sabrUrl);
sabrURL.searchParams.set('rn', String(currentState.sabrStreamState.requestNumber++));
// Stream through the Invidious proxy (CSP + CORS), honoring requestInit headers/body.
response = await proxyFetch(sabrURL.toString(), currentState.requestInit);
operationInputs.headersReceived({});
var fmtId = formatIdFromString(operationInputs.formatIdString);
var itag = fmtId.itag;
var lastModified = fmtId.lastModified;
var xtags = fmtId.xtags;
var mediaHeaderId;
var reader = response.body.getReader();
var readObj = await reader.read();
while (!readObj.done && !currentState.abortStatus.finished) {
if (chunkedDataBuffer) {
chunkedDataBuffer.append(readObj.value);
} else {
chunkedDataBuffer = new CompositeBuffer([readObj.value]);
}
var remainingData = new UmpReader(chunkedDataBuffer).read(function (part) {
switch (part.type) {
case UMPPartId.STREAM_PROTECTION_STATUS: {
var streamProtectionStatus = decodePart(part, StreamProtectionStatus);
if (streamProtectionStatus && streamProtectionStatus.status === 3) {
invalidPoToken = true;
}
break;
}
case UMPPartId.SABR_ERROR: {
var sabrError = decodePart(part, SabrError);
if (!sabrError) break;
error = 'SABR Error: type: ' + sabrError.type + ', code: ' + sabrError.code;
break;
}
case UMPPartId.SABR_REDIRECT: {
var sabrRedirect = decodePart(part, SabrRedirect);
if (!sabrRedirect) break;
// BUGFIX (vs FreeTube): the read site reads sabrStreamState.sabrUrl,
// so write there, not currentState.sabrUrl.
currentState.sabrStreamState.sabrUrl = sabrRedirect.url;
shouldRetry = true;
break;
}
case UMPPartId.MEDIA_HEADER: {
if (mediaHeaderId === undefined) {
var mediaHeader = decodePart(part, MediaHeader);
if (!mediaHeader) break;
if (
mediaHeader.formatId.itag === itag &&
mediaHeader.formatId.lastModified === lastModified &&
mediaHeader.formatId.xtags === xtags
) {
if (operationInputs.isInit && mediaHeader.isInitSeg) {
mediaHeaderId = mediaHeader.headerId;
} else if (!operationInputs.isInit && mediaHeader.sequenceNumber === operationInputs.sequenceNumber) {
mediaHeaderId = mediaHeader.headerId;
}
}
}
break;
}
case UMPPartId.MEDIA: {
if (mediaHeaderId === part.data.getUint8(0)) {
var split = part.data.split(1);
var remaining = split.remainingBuffer;
for (var k = 0; k < remaining.chunks.length; k++) {
responseDataChunks.push(remaining.chunks[k]);
}
}
break;
}
case UMPPartId.MEDIA_END: {
if (mediaHeaderId === part.data.getUint8(0)) {
segmentComplete = true;
currentState.abortStatus.finished = true;
currentState.abortController.abort();
}
break;
}
case UMPPartId.NEXT_REQUEST_POLICY: {
var nextRequestPolicy = decodePart(part, NextRequestPolicy);
shouldRetry = true;
shouldRetryDueToNextRequestPolicy = true;
currentState.sabrStreamState.nextRequestPolicy = nextRequestPolicy;
currentState.abrRequest.streamerContext.playbackCookie = nextRequestPolicy?.playbackCookie ? PlaybackCookie.encode(nextRequestPolicy.playbackCookie).finish() : undefined;
currentState.abrRequest.streamerContext.backoffTimeMs = nextRequestPolicy?.backoffTimeMs;
break;
}
case UMPPartId.FORMAT_INITIALIZATION_METADATA: {
break;
}
case UMPPartId.SABR_CONTEXT_UPDATE: {
var sabrContextUpdate = decodePart(part, SabrContextUpdate);
if (!sabrContextUpdate) break;
if (sabrContextUpdate.type !== undefined && sabrContextUpdate.value?.length) {
if (
sabrContextUpdate.writePolicy === SabrContextWritePolicy.KEEP_EXISTING &&
currentState.sabrStreamState.sabrContexts.has(sabrContextUpdate.type)
) {
break;
}
currentState.sabrStreamState.sabrContexts.set(sabrContextUpdate.type, sabrContextUpdate);
if (sabrContextUpdate.sendByDefault) {
currentState.sabrStreamState.activeSabrContextTypes.add(sabrContextUpdate.type);
}
}
break;
}
case UMPPartId.SABR_CONTEXT_SENDING_POLICY: {
var sabrContextSendingPolicy = decodePart(part, SabrContextSendingPolicy);
if (!sabrContextSendingPolicy) break;
for (var i = 0; i < sabrContextSendingPolicy.startPolicy.length; i++) {
var startPolicy = sabrContextSendingPolicy.startPolicy[i];
if (!currentState.sabrStreamState.activeSabrContextTypes.has(startPolicy)) {
currentState.sabrStreamState.activeSabrContextTypes.add(startPolicy);
}
}
for (var j = 0; j < sabrContextSendingPolicy.stopPolicy.length; j++) {
var stopPolicy = sabrContextSendingPolicy.stopPolicy[j];
if (currentState.sabrStreamState.activeSabrContextTypes.has(stopPolicy)) {
currentState.sabrStreamState.activeSabrContextTypes.delete(stopPolicy);
}
}
for (var m = 0; m < sabrContextSendingPolicy.discardPolicy.length; m++) {
var discardPolicy = sabrContextSendingPolicy.discardPolicy[m];
if (currentState.sabrStreamState.sabrContexts.has(discardPolicy)) {
currentState.sabrStreamState.sabrContexts.delete(discardPolicy);
}
}
break;
}
case UMPPartId.RELOAD_PLAYER_RESPONSE: {
var reloadPlaybackContext = decodePart(part, ReloadPlaybackContext);
if (!reloadPlaybackContext) break;
currentState.sabrStreamState.playerReloadRequested = true;
if (!currentState.abortController.signal.aborted) {
currentState.abortController.abort();
currentState.eventEmitter.emit('reload');
}
break;
}
default: {
break;
}
}
});
if (!currentState.abortStatus.finished) {
if (remainingData) {
chunkedDataBuffer = remainingData.data;
} else {
chunkedDataBuffer = null;
}
readObj = await reader.read();
}
}
} catch (err) {
if (currentState.abortStatus.cancelled) {
throw createRecoverableNetworkError(ShakaError.Code.OPERATION_ABORTED, operationInputs.uri, operationInputs.requestType);
} else if (currentState.abortStatus.timedOut) {
throw createRecoverableNetworkError(ShakaError.Code.TIMEOUT, operationInputs.uri, operationInputs.requestType);
} else if (!currentState.abortStatus.finished) {
throw createRecoverableNetworkError(ShakaError.Code.HTTP_ERROR, operationInputs.uri, err, operationInputs.requestType);
}
}
if (currentState.abortStatus.cancelled) {
throw createRecoverableNetworkError(ShakaError.Code.OPERATION_ABORTED, operationInputs.uri, operationInputs.requestType);
} else if (currentState.abortStatus.timedOut) {
throw createRecoverableNetworkError(ShakaError.Code.TIMEOUT, operationInputs.uri, operationInputs.requestType);
}
if (responseDataChunks.length > 0 && segmentComplete) {
var concatenateChunks = utils.concatenateChunks;
var data = concatenateChunks(responseDataChunks);
if (operationInputs.isInit) {
currentState.initDataCache.set(operationInputs.formatIdString, data);
}
return {
uri: operationInputs.uri,
originalUri: operationInputs.uri,
data: data,
status: response.status,
headers: {},
fromCache: false,
originalRequest: operationInputs.request
};
} else if (shouldRetry) {
if (shouldRetryDueToNextRequestPolicy) {
currentState.cumulativeRetryDueToNextRequestPolicy += 1;
}
var prepared = prepareSabrContexts(currentState.sabrStreamState);
currentState.abrRequest.streamerContext.sabrContexts = prepared.sabrContexts;
currentState.abrRequest.streamerContext.unsentSabrContexts = prepared.unsentSabrContexts;
var body;
try {
body = VideoPlaybackAbrRequest.encode(currentState.abrRequest).finish();
} catch (e) {
console.error('Invalid VideoPlaybackAbrRequest data', currentState.abrRequest);
throw e;
}
currentState.requestInit = {
body: body,
method: 'POST',
headers: {
'content-type': 'application/x-protobuf',
'accept-encoding': 'identity',
'accept': 'application/vnd.yt-ump'
},
signal: currentState.abortController.signal
};
currentState.abortStatus.timedOut = false;
currentState.abortStatus.finished = false;
return doRequest(operationInputs, currentState);
} else if (invalidPoToken) {
throw new ShakaError(
ShakaError.Severity.CRITICAL,
ShakaError.Category.NETWORK,
ShakaError.Code.HTTP_ERROR,
operationInputs.uri,
new Error('Invalid PO token'),
operationInputs.requestType
);
} else if (error) {
throw createRecoverableNetworkError(ShakaError.Code.HTTP_ERROR, operationInputs.uri, new Error(error), operationInputs.requestType);
} else if (responseDataChunks.length > 0 && !segmentComplete) {
throw createRecoverableNetworkError(
ShakaError.Code.HTTP_ERROR,
operationInputs.uri,
new Error('Incomplete segment, missing MEDIA_END part'),
operationInputs.requestType
);
} else if (response.status === 200) {
throw createRecoverableNetworkError(
ShakaError.Code.HTTP_ERROR,
operationInputs.uri,
new Error('Empty response, this should not happen'),
operationInputs.requestType
);
} else {
var severity = response.status === 401 || response.status === 403
? ShakaError.Severity.CRITICAL
: ShakaError.Severity.RECOVERABLE;
throw new ShakaError(
severity,
ShakaError.Category.NETWORK,
ShakaError.Code.BAD_HTTP_STATUS,
operationInputs.uri,
response.status,
'',
{},
operationInputs.requestType,
operationInputs.uri
);
}
}
function setupSabrScheme(sabrData, getPlayer, getManifest, getWidth, getHeight) {
var ShakaAbortableOperation = shaka().util.AbortableOperation;
var ShakaError = shaka().util.Error;
var protos = gv().protos;
var utils = gv().utils;
var base64ToU8 = utils.base64ToU8;
var VideoPlaybackAbrRequest = protos.VideoPlaybackAbrRequest;
var PlaybackCookie = protos.PlaybackCookie;
var EventEmitterLike = utils.EventEmitterLike;
var eventEmitter = new EventEmitterLike();
var initDataCache = new Map();
var poToken = base64ToU8(sabrData.poToken);
var videoPlaybackUstreamerConfig = base64ToU8(sabrData.ustreamerConfig);
var clientInfo = deepCopy(sabrData.clientInfo);
var sabrStreamState = {
sabrUrl: sabrData.url,
activeSabrContextTypes: new Set(),
sabrContexts: new Map(),
nextRequestPolicy: undefined,
playerReloadRequested: false,
requestNumber: 0
};
shaka().net.NetworkingEngine.registerScheme('sabr', function (uri, request, requestType, _progressUpdated, headersReceived, _config) {
var player = getPlayer();
if (player == null) {
return new ShakaAbortableOperation(Promise.resolve());
}
var isAudioOnly = player.isAudioOnly();
var url = new URL(request.uris[0]);
var isInit = url.searchParams.has('init');
var formatIdString = url.searchParams.get('formatId');
if (isInit && initDataCache.has(formatIdString)) {
return createCacheResponse(ShakaAbortableOperation, uri, request, initDataCache.get(formatIdString));
}
var variantTracks = player.getVariantTracks();
var activeVariant = null;
for (var i = 0; i < variantTracks.length; i++) {
if (variantTracks[i].active) { activeVariant = variantTracks[i]; break; }
}
var streamIsAudio = url.pathname === 'audio';
var streamIsVideo = url.pathname === 'video';
var audioFormatId;
var videoFormatId;
if (streamIsAudio) {
audioFormatId = formatIdFromString(formatIdString);
if (isAudioOnly) {
videoFormatId = formatIdFromString(url.searchParams.get('videoFormatId'));
} else {
videoFormatId = formatIdFromString((activeVariant || variantTracks[0]).originalVideoId);
}
} else if (streamIsVideo) {
videoFormatId = formatIdFromString(formatIdString);
if (activeVariant) {
audioFormatId = formatIdFromString(activeVariant.originalAudioId);
} else {
var candidates = variantTracks.filter(function (track) {
return track.audioRoles.indexOf('main') !== -1;
});
var probableAudioFormat = candidates.reduce(function (previous, current) {
return current.audioBandwidth >= previous.audioBandwidth ? current : previous;
}, candidates[0]);
audioFormatId = formatIdFromString(probableAudioFormat.originalAudioId);
}
}
var bufferedRanges = [];
if (!isInit && activeVariant) {
fillBufferedRanges(player, getManifest(), isAudioOnly, streamIsVideo, streamIsAudio, bufferedRanges, activeVariant);
}
var playerTimeMs = '0';
if (url.searchParams.has('startTimeMs')) {
playerTimeMs = url.searchParams.get('startTimeMs');
}
var drcEnabled = url.searchParams.has('drc') || !!(activeVariant && activeVariant.audioRoles.indexOf('drc') !== -1);
var enableVoiceBoost = url.searchParams.has('vb') || !!(activeVariant && activeVariant.audioRoles.indexOf('vb') !== -1);
var resolution = streamIsVideo ? parseInt(url.searchParams.get('resolution'), 10) : undefined;
var prepared = prepareSabrContexts(sabrStreamState);
var requestData = {
clientAbrState: {
bandwidthEstimate: String(Math.round(player.getStats().estimatedBandwidth)),
timeSinceLastManualFormatSelectionMs: streamIsVideo ? '0' : undefined,
stickyResolution: resolution,
lastManualSelectedResolution: resolution,
playbackRate: player.getPlaybackRate(),
enabledTrackTypesBitfield: streamIsAudio ? 1 : 0,
drcEnabled: drcEnabled,
enableVoiceBoost: enableVoiceBoost,
playerTimeMs: playerTimeMs,
clientViewportWidth: getWidth(),
clientViewportHeight: getHeight(),
clientViewportIsFlexible: false
},
preferredAudioFormatIds: [audioFormatId],
preferredVideoFormatIds: [videoFormatId],
preferredSubtitleFormatIds: [],
selectedFormatIds: isInit ? [] : [audioFormatId, videoFormatId],
bufferedRanges: bufferedRanges,
streamerContext: {
poToken: poToken,
clientInfo: clientInfo,
sabrContexts: prepared.sabrContexts,
unsentSabrContexts: prepared.unsentSabrContexts,
playbackCookie: sabrStreamState.nextRequestPolicy?.playbackCookie ? PlaybackCookie.encode(sabrStreamState.nextRequestPolicy.playbackCookie).finish() : undefined
},
field1000: [],
videoPlaybackUstreamerConfig: videoPlaybackUstreamerConfig
};
var body;
try {
body = VideoPlaybackAbrRequest.encode(requestData).finish();
} catch (e) {
console.error('Invalid VideoPlaybackAbrRequest data', requestData);
throw e;
}
var sequenceNumber = parseInt(url.searchParams.get('sq'), 10);
var opInputs = {
uri: uri,
request: request,
requestType: requestType,
headersReceived: headersReceived,
formatIdString: formatIdString,
isInit: isInit,
sequenceNumber: isNaN(sequenceNumber) ? undefined : sequenceNumber
};
var abortController = new AbortController();
var init = {
body: body,
method: 'POST',
headers: {
'content-type': 'application/x-protobuf',
'accept-encoding': 'identity',
'accept': 'application/vnd.yt-ump'
},
signal: abortController.signal
};
var abortStatus = { cancelled: false, timedOut: false, finished: false };
var timeoutMs = request.retryParameters.timeout;
var timeoutController = null;
if (timeoutMs) {
timeoutController = createTimeoutController(function () {
abortStatus.timedOut = true;
abortController.abort();
}, timeoutMs);
}
var currentState = {
initDataCache: initDataCache,
abrRequest: requestData,
requestInit: init,
abortStatus: abortStatus,
abortController: abortController,
sabrStreamState: sabrStreamState,
timeoutController: timeoutController,
eventEmitter: eventEmitter,
cumulativeBackOffTimeMs: 0,
cumulativeBackOffRequested: 0,
cumulativeRetryDueToNextRequestPolicy: 0
};
var pendingRequest = doRequest(opInputs, currentState);
var op = new ShakaAbortableOperation(pendingRequest, function () {
abortStatus.cancelled = true;
abortController.abort();
return Promise.resolve();
});
if (timeoutController) {
op.finally(function () {
timeoutController.clearTimeout();
});
}
return op;
});
function cleanup() {
shaka().net.NetworkingEngine.unregisterScheme('sabr');
initDataCache.clear();
}
return {
onBackoffRequested: function (callback) {
eventEmitter.on('backoff-requested', callback);
},
onReloadOnce: function (callback) {
eventEmitter.once('reload', callback);
},
cleanup: cleanup
};
}
window.setupSabrScheme = setupSabrScheme;
})();