@jxstjh/jhvideo
Version:
HTML5 jhvideo base on MPEG2-TS Stream Player
343 lines • 13.3 kB
JavaScript
/*
* Copyright (C) 2016 Bilibili. All Rights Reserved.
*
* @author zheng qian <xqq@xqq.im>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
import Log from '../utils/logger.js';
import SpeedSampler from './speed-sampler.js';
import { BaseLoader, LoaderStatus, LoaderErrors } from './loader.js';
import { RuntimeException } from '../utils/exception.js';
// Universal IO Loader, implemented by adding Range header in xhr's request header
var RangeLoader = /** @class */ (function (_super) {
__extends(RangeLoader, _super);
function RangeLoader(seekHandler, config) {
var _this = _super.call(this, 'xhr-range-loader') || this;
_this.TAG = 'RangeLoader';
_this._seekHandler = seekHandler;
_this._config = config;
_this._needStash = false;
_this._chunkSizeKBList = [
128, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096, 5120, 6144, 7168, 8192
];
_this._currentChunkSizeKB = 384;
_this._currentSpeedNormalized = 0;
_this._zeroSpeedChunkCount = 0;
_this._xhr = null;
_this._speedSampler = new SpeedSampler();
_this._requestAbort = false;
_this._waitForTotalLength = false;
_this._totalLengthReceived = false;
_this._currentRequestURL = null;
_this._currentRedirectedURL = null;
_this._currentRequestRange = null;
_this._totalLength = null; // size of the entire file
_this._contentLength = null; // Content-Length of entire request range
_this._receivedLength = 0; // total received bytes
_this._lastTimeLoaded = 0; // received bytes of current request sub-range
return _this;
}
RangeLoader.isSupported = function () {
try {
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://example.com', true);
xhr.responseType = 'arraybuffer';
return (xhr.responseType === 'arraybuffer');
}
catch (e) {
Log.w('RangeLoader', e.message);
return false;
}
};
RangeLoader.prototype.destroy = function () {
if (this.isWorking()) {
this.abort();
}
if (this._xhr) {
this._xhr.onreadystatechange = null;
this._xhr.onprogress = null;
this._xhr.onload = null;
this._xhr.onerror = null;
this._xhr = null;
}
_super.prototype.destroy.call(this);
};
Object.defineProperty(RangeLoader.prototype, "currentSpeed", {
get: function () {
return this._speedSampler.lastSecondKBps;
},
enumerable: false,
configurable: true
});
RangeLoader.prototype.open = function (dataSource, range) {
this._dataSource = dataSource;
this._range = range;
this._status = LoaderStatus.kConnecting;
var useRefTotalLength = false;
if (this._dataSource.filesize != undefined && this._dataSource.filesize !== 0) {
useRefTotalLength = true;
this._totalLength = this._dataSource.filesize;
}
if (!this._totalLengthReceived && !useRefTotalLength) {
// We need total filesize
this._waitForTotalLength = true;
this._internalOpen(this._dataSource, { from: 0, to: -1 });
}
else {
// We have filesize, start loading
this._openSubRange();
}
};
RangeLoader.prototype._openSubRange = function () {
var chunkSize = this._currentChunkSizeKB * 1024;
var from = this._range.from + this._receivedLength;
var to = from + chunkSize;
if (this._contentLength != null) {
if (to - this._range.from >= this._contentLength) {
to = this._range.from + this._contentLength - 1;
}
}
this._currentRequestRange = { from: from, to: to };
this._internalOpen(this._dataSource, this._currentRequestRange);
};
RangeLoader.prototype._internalOpen = function (dataSource, range) {
this._lastTimeLoaded = 0;
var sourceURL = dataSource.url;
if (this._config.reuseRedirectedURL) {
if (this._currentRedirectedURL != undefined) {
sourceURL = this._currentRedirectedURL;
}
else if (dataSource.redirectedURL != undefined) {
sourceURL = dataSource.redirectedURL;
}
}
var seekConfig = this._seekHandler.getConfig(sourceURL, range);
this._currentRequestURL = seekConfig.url;
var xhr = this._xhr = new XMLHttpRequest();
xhr.open('GET', seekConfig.url, true);
xhr.responseType = 'arraybuffer';
xhr.onreadystatechange = this._onReadyStateChange.bind(this);
xhr.onprogress = this._onProgress.bind(this);
xhr.onload = this._onLoad.bind(this);
xhr.onerror = this._onXhrError.bind(this);
if (dataSource.withCredentials) {
xhr.withCredentials = true;
}
if (typeof seekConfig.headers === 'object') {
var headers = seekConfig.headers;
for (var key in headers) {
if (headers.hasOwnProperty(key)) {
xhr.setRequestHeader(key, headers[key]);
}
}
}
// add additional headers
if (typeof this._config.headers === 'object') {
var headers = this._config.headers;
for (var key in headers) {
if (headers.hasOwnProperty(key)) {
xhr.setRequestHeader(key, headers[key]);
}
}
}
xhr.send();
};
RangeLoader.prototype.abort = function () {
this._requestAbort = true;
this._internalAbort();
this._status = LoaderStatus.kComplete;
};
RangeLoader.prototype._internalAbort = function () {
if (this._xhr) {
this._xhr.onreadystatechange = null;
this._xhr.onprogress = null;
this._xhr.onload = null;
this._xhr.onerror = null;
this._xhr.abort();
this._xhr = null;
}
};
RangeLoader.prototype._onReadyStateChange = function (e) {
var xhr = e.target;
if (xhr.readyState === 2) { // HEADERS_RECEIVED
if (xhr.responseURL != undefined) { // if the browser support this property
var redirectedURL = this._seekHandler.removeURLParameters(xhr.responseURL);
if (xhr.responseURL !== this._currentRequestURL && redirectedURL !== this._currentRedirectedURL) {
this._currentRedirectedURL = redirectedURL;
if (this._onURLRedirect) {
this._onURLRedirect(redirectedURL);
}
}
}
if ((xhr.status >= 200 && xhr.status <= 299)) {
if (this._waitForTotalLength) {
return;
}
this._status = LoaderStatus.kBuffering;
}
else {
this._status = LoaderStatus.kError;
if (this._onError) {
this._onError(LoaderErrors.HTTP_STATUS_CODE_INVALID, { code: xhr.status, msg: xhr.statusText });
}
else {
throw new RuntimeException('RangeLoader: Http code invalid, ' + xhr.status + ' ' + xhr.statusText);
}
}
}
};
RangeLoader.prototype._onProgress = function (e) {
if (this._status === LoaderStatus.kError) {
// Ignore error response
return;
}
if (this._contentLength === null) {
var openNextRange = false;
if (this._waitForTotalLength) {
this._waitForTotalLength = false;
this._totalLengthReceived = true;
openNextRange = true;
var total = e.total;
this._internalAbort();
if (total != null & total !== 0) {
this._totalLength = total;
}
}
// calculate currrent request range's contentLength
if (this._range.to === -1) {
this._contentLength = this._totalLength - this._range.from;
}
else { // to !== -1
this._contentLength = this._range.to - this._range.from + 1;
}
if (openNextRange) {
this._openSubRange();
return;
}
if (this._onContentLengthKnown) {
this._onContentLengthKnown(this._contentLength);
}
}
var delta = e.loaded - this._lastTimeLoaded;
this._lastTimeLoaded = e.loaded;
this._speedSampler.addBytes(delta);
};
RangeLoader.prototype._normalizeSpeed = function (input) {
var list = this._chunkSizeKBList;
var last = list.length - 1;
var mid = 0;
var lbound = 0;
var ubound = last;
if (input < list[0]) {
return list[0];
}
while (lbound <= ubound) {
mid = lbound + Math.floor((ubound - lbound) / 2);
if (mid === last || (input >= list[mid] && input < list[mid + 1])) {
return list[mid];
}
else if (list[mid] < input) {
lbound = mid + 1;
}
else {
ubound = mid - 1;
}
}
};
RangeLoader.prototype._onLoad = function (e) {
if (this._status === LoaderStatus.kError) {
// Ignore error response
return;
}
if (this._waitForTotalLength) {
this._waitForTotalLength = false;
return;
}
this._lastTimeLoaded = 0;
var KBps = this._speedSampler.lastSecondKBps;
if (KBps === 0) {
this._zeroSpeedChunkCount++;
if (this._zeroSpeedChunkCount >= 3) {
// Try get currentKBps after 3 chunks
KBps = this._speedSampler.currentKBps;
}
}
if (KBps !== 0) {
var normalized = this._normalizeSpeed(KBps);
if (this._currentSpeedNormalized !== normalized) {
this._currentSpeedNormalized = normalized;
this._currentChunkSizeKB = normalized;
}
}
var chunk = e.target.response;
var byteStart = this._range.from + this._receivedLength;
this._receivedLength += chunk.byteLength;
var reportComplete = false;
if (this._contentLength != null && this._receivedLength < this._contentLength) {
// continue load next chunk
this._openSubRange();
}
else {
reportComplete = true;
}
// dispatch received chunk
if (this._onDataArrival) {
this._onDataArrival(chunk, byteStart, this._receivedLength);
}
if (reportComplete) {
this._status = LoaderStatus.kComplete;
if (this._onComplete) {
this._onComplete(this._range.from, this._range.from + this._receivedLength - 1);
}
}
};
RangeLoader.prototype._onXhrError = function (e) {
this._status = LoaderStatus.kError;
var type = 0;
var info = null;
if (this._contentLength && this._receivedLength > 0
&& this._receivedLength < this._contentLength) {
type = LoaderErrors.EARLY_EOF;
info = { code: -1, msg: 'RangeLoader meet Early-Eof' };
}
else {
type = LoaderErrors.EXCEPTION;
info = { code: -1, msg: e.constructor.name + ' ' + e.type };
}
if (this._onError) {
this._onError(type, info);
}
else {
throw new RuntimeException(info.msg);
}
};
return RangeLoader;
}(BaseLoader));
export default RangeLoader;
//# sourceMappingURL=xhr-range-loader.js.map