UNPKG

@jxstjh/jhvideo

Version:

HTML5 jhvideo base on MPEG2-TS Stream Player

291 lines 11.7 kB
/* * 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 { BaseLoader, LoaderStatus, LoaderErrors } from './loader.js'; import { RuntimeException } from '../utils/exception.js'; /* Notice: ms-stream may cause IE/Edge browser crash if seek too frequently!!! * The browser may crash in wininet.dll. Disable for now. * * For IE11/Edge browser by microsoft which supports `xhr.responseType = 'ms-stream'` * Notice that ms-stream API sucks. The buffer is always expanding along with downloading. * * We need to abort the xhr if buffer size exceeded limit size (e.g. 16 MiB), then do reconnect. * in order to release previous ArrayBuffer to avoid memory leak * * Otherwise, the ArrayBuffer will increase to a terrible size that equals final file size. */ var MSStreamLoader = /** @class */ (function (_super) { __extends(MSStreamLoader, _super); function MSStreamLoader(seekHandler, config) { var _this = _super.call(this, 'xhr-msstream-loader') || this; _this.TAG = 'MSStreamLoader'; _this._seekHandler = seekHandler; _this._config = config; _this._needStash = true; _this._xhr = null; _this._reader = null; // MSStreamReader _this._totalRange = null; _this._currentRange = null; _this._currentRequestURL = null; _this._currentRedirectedURL = null; _this._contentLength = null; _this._receivedLength = 0; _this._bufferLimit = 16 * 1024 * 1024; // 16MB _this._lastTimeBufferSize = 0; _this._isReconnecting = false; return _this; } MSStreamLoader.isSupported = function () { try { if (typeof self.MSStream === 'undefined' || typeof self.MSStreamReader === 'undefined') { return false; } var xhr = new XMLHttpRequest(); xhr.open('GET', 'https://example.com', true); xhr.responseType = 'ms-stream'; return (xhr.responseType === 'ms-stream'); } catch (e) { Log.w('MSStreamLoader', e.message); return false; } }; MSStreamLoader.prototype.destroy = function () { if (this.isWorking()) { this.abort(); } if (this._reader) { this._reader.onprogress = null; this._reader.onload = null; this._reader.onerror = null; this._reader = null; } if (this._xhr) { this._xhr.onreadystatechange = null; this._xhr = null; } _super.prototype.destroy.call(this); }; MSStreamLoader.prototype.open = function (dataSource, range) { this._internalOpen(dataSource, range, false); }; MSStreamLoader.prototype._internalOpen = function (dataSource, range, isSubrange) { this._dataSource = dataSource; if (!isSubrange) { this._totalRange = range; } else { this._currentRange = range; } 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 reader = this._reader = new self.MSStreamReader(); reader.onprogress = this._msrOnProgress.bind(this); reader.onload = this._msrOnLoad.bind(this); reader.onerror = this._msrOnError.bind(this); var xhr = this._xhr = new XMLHttpRequest(); xhr.open('GET', seekConfig.url, true); xhr.responseType = 'ms-stream'; xhr.onreadystatechange = this._xhrOnReadyStateChange.bind(this); xhr.onerror = this._xhrOnError.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]); } } } if (this._isReconnecting) { this._isReconnecting = false; } else { this._status = LoaderStatus.kConnecting; } xhr.send(); }; MSStreamLoader.prototype.abort = function () { this._internalAbort(); this._status = LoaderStatus.kComplete; }; MSStreamLoader.prototype._internalAbort = function () { if (this._reader) { if (this._reader.readyState === 1) { // LOADING this._reader.abort(); } this._reader.onprogress = null; this._reader.onload = null; this._reader.onerror = null; this._reader = null; } if (this._xhr) { this._xhr.abort(); this._xhr.onreadystatechange = null; this._xhr = null; } }; MSStreamLoader.prototype._xhrOnReadyStateChange = function (e) { var xhr = e.target; if (xhr.readyState === 2) { // HEADERS_RECEIVED if (xhr.status >= 200 && xhr.status <= 299) { this._status = LoaderStatus.kBuffering; if (xhr.responseURL != undefined) { var redirectedURL = this._seekHandler.removeURLParameters(xhr.responseURL); if (xhr.responseURL !== this._currentRequestURL && redirectedURL !== this._currentRedirectedURL) { this._currentRedirectedURL = redirectedURL; if (this._onURLRedirect) { this._onURLRedirect(redirectedURL); } } } var lengthHeader = xhr.getResponseHeader('Content-Length'); if (lengthHeader != null && this._contentLength == null) { var length_1 = parseInt(lengthHeader); if (length_1 > 0) { this._contentLength = length_1; if (this._onContentLengthKnown) { this._onContentLengthKnown(this._contentLength); } } } } 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('MSStreamLoader: Http code invalid, ' + xhr.status + ' ' + xhr.statusText); } } } else if (xhr.readyState === 3) { // LOADING if (xhr.status >= 200 && xhr.status <= 299) { this._status = LoaderStatus.kBuffering; var msstream = xhr.response; this._reader.readAsArrayBuffer(msstream); } } }; MSStreamLoader.prototype._xhrOnError = function (e) { this._status = LoaderStatus.kError; var type = LoaderErrors.EXCEPTION; var info = { code: -1, msg: e.constructor.name + ' ' + e.type }; if (this._onError) { this._onError(type, info); } else { throw new RuntimeException(info.msg); } }; MSStreamLoader.prototype._msrOnProgress = function (e) { var reader = e.target; var bigbuffer = reader.result; if (bigbuffer == null) { // result may be null, workaround for buggy M$ this._doReconnectIfNeeded(); return; } var slice = bigbuffer.slice(this._lastTimeBufferSize); this._lastTimeBufferSize = bigbuffer.byteLength; var byteStart = this._totalRange.from + this._receivedLength; this._receivedLength += slice.byteLength; if (this._onDataArrival) { this._onDataArrival(slice, byteStart, this._receivedLength); } if (bigbuffer.byteLength >= this._bufferLimit) { Log.v(this.TAG, "MSStream buffer exceeded max size near ".concat(byteStart + slice.byteLength, ", reconnecting...")); this._doReconnectIfNeeded(); } }; MSStreamLoader.prototype._doReconnectIfNeeded = function () { if (this._contentLength == null || this._receivedLength < this._contentLength) { this._isReconnecting = true; this._lastTimeBufferSize = 0; this._internalAbort(); var range = { from: this._totalRange.from + this._receivedLength, to: -1 }; this._internalOpen(this._dataSource, range, true); } }; MSStreamLoader.prototype._msrOnLoad = function (e) { this._status = LoaderStatus.kComplete; if (this._onComplete) { this._onComplete(this._totalRange.from, this._totalRange.from + this._receivedLength - 1); } }; MSStreamLoader.prototype._msrOnError = function (e) { this._status = LoaderStatus.kError; var type = 0; var info = null; if (this._contentLength && this._receivedLength < this._contentLength) { type = LoaderErrors.EARLY_EOF; info = { code: -1, msg: 'MSStream meet Early-Eof' }; } else { type = LoaderErrors.EARLY_EOF; info = { code: -1, msg: e.constructor.name + ' ' + e.type }; } if (this._onError) { this._onError(type, info); } else { throw new RuntimeException(info.msg); } }; return MSStreamLoader; }(BaseLoader)); export default MSStreamLoader; //# sourceMappingURL=xhr-msstream-loader.js.map