UNPKG

@jxstjh/jhvideo

Version:

HTML5 jhvideo base on MPEG2-TS Stream Player

343 lines 13.3 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 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