@alessmicrosystems/mpegts.js 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +158 -0
- package/README_ja.md +153 -0
- package/README_zh.md +157 -0
- package/d.ts/mpegts.d.ts +524 -0
- package/d.ts/src/core/mse-events.d.ts +9 -0
- package/d.ts/src/core/transmuxing-events.d.ts +24 -0
- package/d.ts/src/demux/aac.d.ts +44 -0
- package/d.ts/src/demux/ac3.d.ts +70 -0
- package/d.ts/src/demux/av1-parser.d.ts +77 -0
- package/d.ts/src/demux/av1.d.ts +11 -0
- package/d.ts/src/demux/base-demuxer.d.ts +55 -0
- package/d.ts/src/demux/h264.d.ts +40 -0
- package/d.ts/src/demux/h265.d.ts +65 -0
- package/d.ts/src/demux/klv.d.ts +17 -0
- package/d.ts/src/demux/mp3.d.ts +6 -0
- package/d.ts/src/demux/mpeg4-audio.d.ts +28 -0
- package/d.ts/src/demux/pat-pmt-pes.d.ts +106 -0
- package/d.ts/src/demux/patpmt.d.ts +40 -0
- package/d.ts/src/demux/pes-private-data.d.ts +14 -0
- package/d.ts/src/demux/pgs-data.d.ts +9 -0
- package/d.ts/src/demux/scte35.d.ts +250 -0
- package/d.ts/src/demux/sei.d.ts +8 -0
- package/d.ts/src/demux/smpte2038.d.ts +22 -0
- package/d.ts/src/demux/ts-demuxer.d.ts +124 -0
- package/d.ts/src/player/live-latency-chaser.d.ts +10 -0
- package/d.ts/src/player/live-latency-synchronizer.d.ts +10 -0
- package/d.ts/src/player/loading-controller.d.ts +19 -0
- package/d.ts/src/player/mse-player.d.ts +30 -0
- package/d.ts/src/player/player-engine-dedicated-thread-worker.d.ts +2 -0
- package/d.ts/src/player/player-engine-dedicated-thread.d.ts +48 -0
- package/d.ts/src/player/player-engine-main-thread.d.ts +50 -0
- package/d.ts/src/player/player-engine-worker-cmd-def.d.ts +25 -0
- package/d.ts/src/player/player-engine-worker-msg-def.d.ts +54 -0
- package/d.ts/src/player/player-engine-worker.d.ts +2 -0
- package/d.ts/src/player/player-engine.d.ts +16 -0
- package/d.ts/src/player/player-events.d.ts +21 -0
- package/d.ts/src/player/seeking-handler.d.ts +22 -0
- package/d.ts/src/player/startup-stall-jumper.d.ts +14 -0
- package/d.ts/src/utils/typedarray-equality.d.ts +2 -0
- package/dist/mpegts.js +3 -0
- package/dist/mpegts.js.LICENSE.txt +7 -0
- package/dist/mpegts.js.map +1 -0
- package/package.json +53 -0
- package/src/config.js +67 -0
- package/src/core/features.js +88 -0
- package/src/core/media-info.js +127 -0
- package/src/core/media-segment-info.js +230 -0
- package/src/core/mse-controller.js +599 -0
- package/src/core/mse-events.ts +28 -0
- package/src/core/transmuxer.js +346 -0
- package/src/core/transmuxing-controller.js +628 -0
- package/src/core/transmuxing-events.ts +43 -0
- package/src/core/transmuxing-worker.js +286 -0
- package/src/demux/aac.ts +397 -0
- package/src/demux/ac3.ts +335 -0
- package/src/demux/amf-parser.js +243 -0
- package/src/demux/av1-parser.ts +629 -0
- package/src/demux/av1.ts +103 -0
- package/src/demux/base-demuxer.ts +69 -0
- package/src/demux/demux-errors.js +26 -0
- package/src/demux/exp-golomb.js +116 -0
- package/src/demux/flv-demuxer.js +1854 -0
- package/src/demux/h264.ts +187 -0
- package/src/demux/h265-parser.js +501 -0
- package/src/demux/h265.ts +214 -0
- package/src/demux/klv.ts +40 -0
- package/src/demux/mp3.ts +7 -0
- package/src/demux/mpeg4-audio.ts +45 -0
- package/src/demux/pat-pmt-pes.ts +132 -0
- package/src/demux/pes-private-data.ts +16 -0
- package/src/demux/pgs-data.ts +11 -0
- package/src/demux/scte35.ts +723 -0
- package/src/demux/sei.ts +99 -0
- package/src/demux/smpte2038.ts +89 -0
- package/src/demux/sps-parser.js +298 -0
- package/src/demux/ts-demuxer.ts +2405 -0
- package/src/index.js +4 -0
- package/src/io/fetch-stream-loader.js +266 -0
- package/src/io/io-controller.js +647 -0
- package/src/io/loader.js +134 -0
- package/src/io/param-seek-handler.js +85 -0
- package/src/io/range-seek-handler.js +52 -0
- package/src/io/speed-sampler.js +93 -0
- package/src/io/websocket-loader.js +151 -0
- package/src/io/xhr-moz-chunked-loader.js +211 -0
- package/src/io/xhr-msstream-loader.js +307 -0
- package/src/io/xhr-range-loader.js +366 -0
- package/src/mpegts.js +95 -0
- package/src/player/live-latency-chaser.ts +66 -0
- package/src/player/live-latency-synchronizer.ts +79 -0
- package/src/player/loading-controller.ts +142 -0
- package/src/player/mse-player.ts +150 -0
- package/src/player/native-player.js +262 -0
- package/src/player/player-engine-dedicated-thread.ts +479 -0
- package/src/player/player-engine-main-thread.ts +463 -0
- package/src/player/player-engine-worker-cmd-def.ts +62 -0
- package/src/player/player-engine-worker-msg-def.ts +102 -0
- package/src/player/player-engine-worker.ts +370 -0
- package/src/player/player-engine.ts +35 -0
- package/src/player/player-errors.js +39 -0
- package/src/player/player-events.ts +40 -0
- package/src/player/seeking-handler.ts +205 -0
- package/src/player/startup-stall-jumper.ts +86 -0
- package/src/remux/aac-silent.js +56 -0
- package/src/remux/mp4-generator.js +866 -0
- package/src/remux/mp4-remuxer.js +778 -0
- package/src/utils/browser.js +128 -0
- package/src/utils/exception.js +73 -0
- package/src/utils/logger.js +140 -0
- package/src/utils/logging-control.js +165 -0
- package/src/utils/polyfill.js +68 -0
- package/src/utils/typedarray-equality.ts +69 -0
- package/src/utils/utf8-conv.js +84 -0
- package/src/utils/webworkify-webpack.js +202 -0
- package/tsconfig.json +16 -0
- package/tslint.json +1 -0
- package/types/index.d.ts +3 -0
- package/types/test-flv.ts +8 -0
- package/types/tsconfig.json +24 -0
- package/webpack.config.js +55 -0
|
@@ -0,0 +1,647 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2016 Bilibili. All Rights Reserved.
|
|
3
|
+
*
|
|
4
|
+
* @author zheng qian <xqq@xqq.im>
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import Log from '../utils/logger.js';
|
|
20
|
+
import SpeedSampler from './speed-sampler.js';
|
|
21
|
+
import {LoaderStatus, LoaderErrors} from './loader.js';
|
|
22
|
+
import FetchStreamLoader from './fetch-stream-loader.js';
|
|
23
|
+
import MozChunkedLoader from './xhr-moz-chunked-loader.js';
|
|
24
|
+
import MSStreamLoader from './xhr-msstream-loader.js';
|
|
25
|
+
import RangeLoader from './xhr-range-loader.js';
|
|
26
|
+
import WebSocketLoader from './websocket-loader.js';
|
|
27
|
+
import RangeSeekHandler from './range-seek-handler.js';
|
|
28
|
+
import ParamSeekHandler from './param-seek-handler.js';
|
|
29
|
+
import {RuntimeException, IllegalStateException, InvalidArgumentException} from '../utils/exception.js';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* DataSource: {
|
|
33
|
+
* url: string,
|
|
34
|
+
* filesize: number,
|
|
35
|
+
* cors: boolean,
|
|
36
|
+
* withCredentials: boolean
|
|
37
|
+
* }
|
|
38
|
+
*
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
// Manage IO Loaders
|
|
42
|
+
class IOController {
|
|
43
|
+
|
|
44
|
+
constructor(dataSource, config, extraData) {
|
|
45
|
+
this.TAG = 'IOController';
|
|
46
|
+
|
|
47
|
+
this._config = config;
|
|
48
|
+
this._extraData = extraData;
|
|
49
|
+
|
|
50
|
+
this._stashInitialSize = 64 * 1024; // default initial size: 64KB
|
|
51
|
+
if (config.stashInitialSize != undefined && config.stashInitialSize > 0) {
|
|
52
|
+
// apply from config
|
|
53
|
+
this._stashInitialSize = config.stashInitialSize;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
this._stashUsed = 0;
|
|
57
|
+
this._stashSize = this._stashInitialSize;
|
|
58
|
+
this._bufferSize = Math.max(this._stashSize, 1024 * 1024 * 3); // initial size: 3MB at least
|
|
59
|
+
this._stashBuffer = new ArrayBuffer(this._bufferSize);
|
|
60
|
+
this._stashByteStart = 0;
|
|
61
|
+
this._enableStash = true;
|
|
62
|
+
if (config.enableStashBuffer === false) {
|
|
63
|
+
this._enableStash = false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
this._loader = null;
|
|
67
|
+
this._loaderClass = null;
|
|
68
|
+
this._seekHandler = null;
|
|
69
|
+
|
|
70
|
+
this._dataSource = dataSource;
|
|
71
|
+
this._isWebSocketURL = /wss?:\/\/(.+?)/.test(dataSource.url);
|
|
72
|
+
this._refTotalLength = dataSource.filesize ? dataSource.filesize : null;
|
|
73
|
+
this._totalLength = this._refTotalLength;
|
|
74
|
+
this._fullRequestFlag = false;
|
|
75
|
+
this._currentRange = null;
|
|
76
|
+
this._redirectedURL = null;
|
|
77
|
+
|
|
78
|
+
this._speedNormalized = 0;
|
|
79
|
+
this._speedSampler = new SpeedSampler();
|
|
80
|
+
this._speedNormalizeList = [32, 64, 96, 128, 192, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096];
|
|
81
|
+
|
|
82
|
+
this._isEarlyEofReconnecting = false;
|
|
83
|
+
|
|
84
|
+
this._paused = false;
|
|
85
|
+
this._resumeFrom = 0;
|
|
86
|
+
|
|
87
|
+
this._onDataArrival = null;
|
|
88
|
+
this._onSeeked = null;
|
|
89
|
+
this._onError = null;
|
|
90
|
+
this._onComplete = null;
|
|
91
|
+
this._onRedirect = null;
|
|
92
|
+
this._onRecoveredEarlyEof = null;
|
|
93
|
+
|
|
94
|
+
this._selectSeekHandler();
|
|
95
|
+
this._selectLoader();
|
|
96
|
+
this._createLoader();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
destroy() {
|
|
100
|
+
if (this._loader.isWorking()) {
|
|
101
|
+
this._loader.abort();
|
|
102
|
+
}
|
|
103
|
+
this._loader.destroy();
|
|
104
|
+
this._loader = null;
|
|
105
|
+
this._loaderClass = null;
|
|
106
|
+
this._dataSource = null;
|
|
107
|
+
this._stashBuffer = null;
|
|
108
|
+
this._stashUsed = this._stashSize = this._bufferSize = this._stashByteStart = 0;
|
|
109
|
+
this._currentRange = null;
|
|
110
|
+
this._speedSampler = null;
|
|
111
|
+
|
|
112
|
+
this._isEarlyEofReconnecting = false;
|
|
113
|
+
|
|
114
|
+
this._onDataArrival = null;
|
|
115
|
+
this._onSeeked = null;
|
|
116
|
+
this._onError = null;
|
|
117
|
+
this._onComplete = null;
|
|
118
|
+
this._onRedirect = null;
|
|
119
|
+
this._onRecoveredEarlyEof = null;
|
|
120
|
+
|
|
121
|
+
this._extraData = null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
isWorking() {
|
|
125
|
+
return this._loader && this._loader.isWorking() && !this._paused;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
isPaused() {
|
|
129
|
+
return this._paused;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
get status() {
|
|
133
|
+
return this._loader.status;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
get extraData() {
|
|
137
|
+
return this._extraData;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
set extraData(data) {
|
|
141
|
+
this._extraData = data;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// prototype: function onDataArrival(chunks: ArrayBuffer, byteStart: number): number
|
|
145
|
+
get onDataArrival() {
|
|
146
|
+
return this._onDataArrival;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
set onDataArrival(callback) {
|
|
150
|
+
this._onDataArrival = callback;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
get onSeeked() {
|
|
154
|
+
return this._onSeeked;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
set onSeeked(callback) {
|
|
158
|
+
this._onSeeked = callback;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// prototype: function onError(type: number, info: {code: number, msg: string}): void
|
|
162
|
+
get onError() {
|
|
163
|
+
return this._onError;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
set onError(callback) {
|
|
167
|
+
this._onError = callback;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
get onComplete() {
|
|
171
|
+
return this._onComplete;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
set onComplete(callback) {
|
|
175
|
+
this._onComplete = callback;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
get onRedirect() {
|
|
179
|
+
return this._onRedirect;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
set onRedirect(callback) {
|
|
183
|
+
this._onRedirect = callback;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
get onRecoveredEarlyEof() {
|
|
187
|
+
return this._onRecoveredEarlyEof;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
set onRecoveredEarlyEof(callback) {
|
|
191
|
+
this._onRecoveredEarlyEof = callback;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
get currentURL() {
|
|
195
|
+
return this._dataSource.url;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
get hasRedirect() {
|
|
199
|
+
return (this._redirectedURL != null || this._dataSource.redirectedURL != undefined);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
get currentRedirectedURL() {
|
|
203
|
+
return this._redirectedURL || this._dataSource.redirectedURL;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// in KB/s
|
|
207
|
+
get currentSpeed() {
|
|
208
|
+
if (this._loaderClass === RangeLoader) {
|
|
209
|
+
// SpeedSampler is inaccuracy if loader is RangeLoader
|
|
210
|
+
return this._loader.currentSpeed;
|
|
211
|
+
}
|
|
212
|
+
return this._speedSampler.lastSecondKBps;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
get loaderType() {
|
|
216
|
+
return this._loader.type;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
_selectSeekHandler() {
|
|
220
|
+
let config = this._config;
|
|
221
|
+
|
|
222
|
+
if (config.seekType === 'range') {
|
|
223
|
+
this._seekHandler = new RangeSeekHandler(this._config.rangeLoadZeroStart);
|
|
224
|
+
} else if (config.seekType === 'param') {
|
|
225
|
+
let paramStart = config.seekParamStart || 'bstart';
|
|
226
|
+
let paramEnd = config.seekParamEnd || 'bend';
|
|
227
|
+
|
|
228
|
+
this._seekHandler = new ParamSeekHandler(paramStart, paramEnd);
|
|
229
|
+
} else if (config.seekType === 'custom') {
|
|
230
|
+
if (typeof config.customSeekHandler !== 'function') {
|
|
231
|
+
throw new InvalidArgumentException('Custom seekType specified in config but invalid customSeekHandler!');
|
|
232
|
+
}
|
|
233
|
+
this._seekHandler = new config.customSeekHandler();
|
|
234
|
+
} else {
|
|
235
|
+
throw new InvalidArgumentException(`Invalid seekType in config: ${config.seekType}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
_selectLoader() {
|
|
240
|
+
if (this._config.customLoader != null) {
|
|
241
|
+
this._loaderClass = this._config.customLoader;
|
|
242
|
+
} else if (this._isWebSocketURL) {
|
|
243
|
+
this._loaderClass = WebSocketLoader;
|
|
244
|
+
} else if (FetchStreamLoader.isSupported()) {
|
|
245
|
+
this._loaderClass = FetchStreamLoader;
|
|
246
|
+
} else if (MozChunkedLoader.isSupported()) {
|
|
247
|
+
this._loaderClass = MozChunkedLoader;
|
|
248
|
+
} else if (RangeLoader.isSupported()) {
|
|
249
|
+
this._loaderClass = RangeLoader;
|
|
250
|
+
} else {
|
|
251
|
+
throw new RuntimeException('Your browser doesn\'t support xhr with arraybuffer responseType!');
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
_createLoader() {
|
|
256
|
+
this._loader = new this._loaderClass(this._seekHandler, this._config);
|
|
257
|
+
if (this._loader.needStashBuffer === false) {
|
|
258
|
+
this._enableStash = false;
|
|
259
|
+
}
|
|
260
|
+
this._loader.onContentLengthKnown = this._onContentLengthKnown.bind(this);
|
|
261
|
+
this._loader.onURLRedirect = this._onURLRedirect.bind(this);
|
|
262
|
+
this._loader.onDataArrival = this._onLoaderChunkArrival.bind(this);
|
|
263
|
+
this._loader.onComplete = this._onLoaderComplete.bind(this);
|
|
264
|
+
this._loader.onError = this._onLoaderError.bind(this);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
open(optionalFrom) {
|
|
268
|
+
this._currentRange = {from: 0, to: -1};
|
|
269
|
+
if (optionalFrom) {
|
|
270
|
+
this._currentRange.from = optionalFrom;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
this._speedSampler.reset();
|
|
274
|
+
if (!optionalFrom) {
|
|
275
|
+
this._fullRequestFlag = true;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
this._loader.open(this._dataSource, Object.assign({}, this._currentRange));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
abort() {
|
|
282
|
+
this._loader.abort();
|
|
283
|
+
|
|
284
|
+
if (this._paused) {
|
|
285
|
+
this._paused = false;
|
|
286
|
+
this._resumeFrom = 0;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
pause() {
|
|
291
|
+
if (this.isWorking()) {
|
|
292
|
+
this._loader.abort();
|
|
293
|
+
|
|
294
|
+
if (this._stashUsed !== 0) {
|
|
295
|
+
this._resumeFrom = this._stashByteStart;
|
|
296
|
+
this._currentRange.to = this._stashByteStart - 1;
|
|
297
|
+
} else {
|
|
298
|
+
this._resumeFrom = this._currentRange.to + 1;
|
|
299
|
+
}
|
|
300
|
+
this._stashUsed = 0;
|
|
301
|
+
this._stashByteStart = 0;
|
|
302
|
+
this._paused = true;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
resume() {
|
|
307
|
+
if (this._paused) {
|
|
308
|
+
this._paused = false;
|
|
309
|
+
let bytes = this._resumeFrom;
|
|
310
|
+
this._resumeFrom = 0;
|
|
311
|
+
this._internalSeek(bytes, true);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
seek(bytes) {
|
|
316
|
+
this._paused = false;
|
|
317
|
+
this._stashUsed = 0;
|
|
318
|
+
this._stashByteStart = 0;
|
|
319
|
+
this._internalSeek(bytes, true);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* When seeking request is from media seeking, unconsumed stash data should be dropped
|
|
324
|
+
* However, stash data shouldn't be dropped if seeking requested from http reconnection
|
|
325
|
+
*
|
|
326
|
+
* @dropUnconsumed: Ignore and discard all unconsumed data in stash buffer
|
|
327
|
+
*/
|
|
328
|
+
_internalSeek(bytes, dropUnconsumed) {
|
|
329
|
+
if (this._loader.isWorking()) {
|
|
330
|
+
this._loader.abort();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// dispatch & flush stash buffer before seek
|
|
334
|
+
this._flushStashBuffer(dropUnconsumed);
|
|
335
|
+
|
|
336
|
+
this._loader.destroy();
|
|
337
|
+
this._loader = null;
|
|
338
|
+
|
|
339
|
+
let requestRange = {from: bytes, to: -1};
|
|
340
|
+
this._currentRange = {from: requestRange.from, to: -1};
|
|
341
|
+
|
|
342
|
+
this._speedSampler.reset();
|
|
343
|
+
this._stashSize = this._stashInitialSize;
|
|
344
|
+
this._createLoader();
|
|
345
|
+
this._loader.open(this._dataSource, requestRange);
|
|
346
|
+
|
|
347
|
+
if (this._onSeeked) {
|
|
348
|
+
this._onSeeked();
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
updateUrl(url) {
|
|
353
|
+
if (!url || typeof url !== 'string' || url.length === 0) {
|
|
354
|
+
throw new InvalidArgumentException('Url must be a non-empty string!');
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
this._dataSource.url = url;
|
|
358
|
+
|
|
359
|
+
// TODO: replace with new url
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
_expandBuffer(expectedBytes) {
|
|
363
|
+
let bufferNewSize = this._stashSize;
|
|
364
|
+
while (bufferNewSize + 1024 * 1024 * 1 < expectedBytes) {
|
|
365
|
+
bufferNewSize *= 2;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
bufferNewSize += 1024 * 1024 * 1; // bufferSize = stashSize + 1MB
|
|
369
|
+
if (bufferNewSize === this._bufferSize) {
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
let newBuffer = new ArrayBuffer(bufferNewSize);
|
|
374
|
+
|
|
375
|
+
if (this._stashUsed > 0) { // copy existing data into new buffer
|
|
376
|
+
let stashOldArray = new Uint8Array(this._stashBuffer, 0, this._stashUsed);
|
|
377
|
+
let stashNewArray = new Uint8Array(newBuffer, 0, bufferNewSize);
|
|
378
|
+
stashNewArray.set(stashOldArray, 0);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
this._stashBuffer = newBuffer;
|
|
382
|
+
this._bufferSize = bufferNewSize;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
_normalizeSpeed(input) {
|
|
386
|
+
let list = this._speedNormalizeList;
|
|
387
|
+
let last = list.length - 1;
|
|
388
|
+
let mid = 0;
|
|
389
|
+
let lbound = 0;
|
|
390
|
+
let ubound = last;
|
|
391
|
+
|
|
392
|
+
if (input < list[0]) {
|
|
393
|
+
return list[0];
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// binary search
|
|
397
|
+
while (lbound <= ubound) {
|
|
398
|
+
mid = lbound + Math.floor((ubound - lbound) / 2);
|
|
399
|
+
if (mid === last || (input >= list[mid] && input < list[mid + 1])) {
|
|
400
|
+
return list[mid];
|
|
401
|
+
} else if (list[mid] < input) {
|
|
402
|
+
lbound = mid + 1;
|
|
403
|
+
} else {
|
|
404
|
+
ubound = mid - 1;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
_adjustStashSize(normalized) {
|
|
410
|
+
let stashSizeKB = 0;
|
|
411
|
+
|
|
412
|
+
if (this._config.isLive) {
|
|
413
|
+
// live stream: always use 1/8 normalized speed for size of stashSizeKB
|
|
414
|
+
stashSizeKB = normalized / 8;
|
|
415
|
+
} else {
|
|
416
|
+
if (normalized < 512) {
|
|
417
|
+
stashSizeKB = normalized;
|
|
418
|
+
} else if (normalized >= 512 && normalized <= 1024) {
|
|
419
|
+
stashSizeKB = Math.floor(normalized * 1.5);
|
|
420
|
+
} else {
|
|
421
|
+
stashSizeKB = normalized * 2;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (stashSizeKB > 8192) {
|
|
426
|
+
stashSizeKB = 8192;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
let bufferSize = stashSizeKB * 1024 + 1024 * 1024 * 1; // stashSize + 1MB
|
|
430
|
+
if (this._bufferSize < bufferSize) {
|
|
431
|
+
this._expandBuffer(bufferSize);
|
|
432
|
+
}
|
|
433
|
+
this._stashSize = stashSizeKB * 1024;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
_dispatchChunks(chunks, byteStart) {
|
|
437
|
+
this._currentRange.to = byteStart + chunks.byteLength - 1;
|
|
438
|
+
return this._onDataArrival(chunks, byteStart);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
_onURLRedirect(redirectedURL) {
|
|
442
|
+
this._redirectedURL = redirectedURL;
|
|
443
|
+
if (this._onRedirect) {
|
|
444
|
+
this._onRedirect(redirectedURL);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
_onContentLengthKnown(contentLength) {
|
|
449
|
+
if (contentLength && this._fullRequestFlag) {
|
|
450
|
+
this._totalLength = contentLength;
|
|
451
|
+
this._fullRequestFlag = false;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
_onLoaderChunkArrival(chunk, byteStart, receivedLength) {
|
|
456
|
+
if (!this._onDataArrival) {
|
|
457
|
+
throw new IllegalStateException('IOController: No existing consumer (onDataArrival) callback!');
|
|
458
|
+
}
|
|
459
|
+
if (this._paused) {
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
if (this._isEarlyEofReconnecting) {
|
|
463
|
+
// Auto-reconnect for EarlyEof succeed, notify to upper-layer by callback
|
|
464
|
+
this._isEarlyEofReconnecting = false;
|
|
465
|
+
if (this._onRecoveredEarlyEof) {
|
|
466
|
+
this._onRecoveredEarlyEof();
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
this._speedSampler.addBytes(chunk.byteLength);
|
|
471
|
+
|
|
472
|
+
// adjust stash buffer size according to network speed dynamically
|
|
473
|
+
let KBps = this._speedSampler.lastSecondKBps;
|
|
474
|
+
if (KBps !== 0) {
|
|
475
|
+
let normalized = this._normalizeSpeed(KBps);
|
|
476
|
+
if (this._speedNormalized !== normalized) {
|
|
477
|
+
this._speedNormalized = normalized;
|
|
478
|
+
this._adjustStashSize(normalized);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (!this._enableStash) { // disable stash
|
|
483
|
+
if (this._stashUsed === 0) {
|
|
484
|
+
// dispatch chunk directly to consumer;
|
|
485
|
+
// check ret value (consumed bytes) and stash unconsumed to stashBuffer
|
|
486
|
+
let consumed = this._dispatchChunks(chunk, byteStart);
|
|
487
|
+
if (consumed < chunk.byteLength) { // unconsumed data remain.
|
|
488
|
+
let remain = chunk.byteLength - consumed;
|
|
489
|
+
if (remain > this._bufferSize) {
|
|
490
|
+
this._expandBuffer(remain);
|
|
491
|
+
}
|
|
492
|
+
let stashArray = new Uint8Array(this._stashBuffer, 0, this._bufferSize);
|
|
493
|
+
stashArray.set(new Uint8Array(chunk, consumed), 0);
|
|
494
|
+
this._stashUsed += remain;
|
|
495
|
+
this._stashByteStart = byteStart + consumed;
|
|
496
|
+
}
|
|
497
|
+
} else {
|
|
498
|
+
// else: Merge chunk into stashBuffer, and dispatch stashBuffer to consumer.
|
|
499
|
+
if (this._stashUsed + chunk.byteLength > this._bufferSize) {
|
|
500
|
+
this._expandBuffer(this._stashUsed + chunk.byteLength);
|
|
501
|
+
}
|
|
502
|
+
let stashArray = new Uint8Array(this._stashBuffer, 0, this._bufferSize);
|
|
503
|
+
stashArray.set(new Uint8Array(chunk), this._stashUsed);
|
|
504
|
+
this._stashUsed += chunk.byteLength;
|
|
505
|
+
let consumed = this._dispatchChunks(this._stashBuffer.slice(0, this._stashUsed), this._stashByteStart);
|
|
506
|
+
if (consumed < this._stashUsed && consumed > 0) { // unconsumed data remain
|
|
507
|
+
let remainArray = new Uint8Array(this._stashBuffer, consumed);
|
|
508
|
+
stashArray.set(remainArray, 0);
|
|
509
|
+
}
|
|
510
|
+
this._stashUsed -= consumed;
|
|
511
|
+
this._stashByteStart += consumed;
|
|
512
|
+
}
|
|
513
|
+
} else { // enable stash
|
|
514
|
+
if (this._stashUsed === 0 && this._stashByteStart === 0) { // seeked? or init chunk?
|
|
515
|
+
// This is the first chunk after seek action
|
|
516
|
+
this._stashByteStart = byteStart;
|
|
517
|
+
}
|
|
518
|
+
if (this._stashUsed + chunk.byteLength <= this._stashSize) {
|
|
519
|
+
// just stash
|
|
520
|
+
let stashArray = new Uint8Array(this._stashBuffer, 0, this._stashSize);
|
|
521
|
+
stashArray.set(new Uint8Array(chunk), this._stashUsed);
|
|
522
|
+
this._stashUsed += chunk.byteLength;
|
|
523
|
+
} else { // stashUsed + chunkSize > stashSize, size limit exceeded
|
|
524
|
+
let stashArray = new Uint8Array(this._stashBuffer, 0, this._bufferSize);
|
|
525
|
+
if (this._stashUsed > 0) { // There're stash datas in buffer
|
|
526
|
+
// dispatch the whole stashBuffer, and stash remain data
|
|
527
|
+
// then append chunk to stashBuffer (stash)
|
|
528
|
+
let buffer = this._stashBuffer.slice(0, this._stashUsed);
|
|
529
|
+
let consumed = this._dispatchChunks(buffer, this._stashByteStart);
|
|
530
|
+
if (consumed < buffer.byteLength) {
|
|
531
|
+
if (consumed > 0) {
|
|
532
|
+
let remainArray = new Uint8Array(buffer, consumed);
|
|
533
|
+
stashArray.set(remainArray, 0);
|
|
534
|
+
this._stashUsed = remainArray.byteLength;
|
|
535
|
+
this._stashByteStart += consumed;
|
|
536
|
+
}
|
|
537
|
+
} else {
|
|
538
|
+
this._stashUsed = 0;
|
|
539
|
+
this._stashByteStart += consumed;
|
|
540
|
+
}
|
|
541
|
+
if (this._stashUsed + chunk.byteLength > this._bufferSize) {
|
|
542
|
+
this._expandBuffer(this._stashUsed + chunk.byteLength);
|
|
543
|
+
stashArray = new Uint8Array(this._stashBuffer, 0, this._bufferSize);
|
|
544
|
+
}
|
|
545
|
+
stashArray.set(new Uint8Array(chunk), this._stashUsed);
|
|
546
|
+
this._stashUsed += chunk.byteLength;
|
|
547
|
+
} else { // stash buffer empty, but chunkSize > stashSize (oh, holy shit)
|
|
548
|
+
// dispatch chunk directly and stash remain data
|
|
549
|
+
let consumed = this._dispatchChunks(chunk, byteStart);
|
|
550
|
+
if (consumed < chunk.byteLength) {
|
|
551
|
+
let remain = chunk.byteLength - consumed;
|
|
552
|
+
if (remain > this._bufferSize) {
|
|
553
|
+
this._expandBuffer(remain);
|
|
554
|
+
stashArray = new Uint8Array(this._stashBuffer, 0, this._bufferSize);
|
|
555
|
+
}
|
|
556
|
+
stashArray.set(new Uint8Array(chunk, consumed), 0);
|
|
557
|
+
this._stashUsed += remain;
|
|
558
|
+
this._stashByteStart = byteStart + consumed;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
_flushStashBuffer(dropUnconsumed) {
|
|
566
|
+
if (this._stashUsed > 0) {
|
|
567
|
+
let buffer = this._stashBuffer.slice(0, this._stashUsed);
|
|
568
|
+
let consumed = this._dispatchChunks(buffer, this._stashByteStart);
|
|
569
|
+
let remain = buffer.byteLength - consumed;
|
|
570
|
+
|
|
571
|
+
if (consumed < buffer.byteLength) {
|
|
572
|
+
if (dropUnconsumed) {
|
|
573
|
+
Log.w(this.TAG, `${remain} bytes unconsumed data remain when flush buffer, dropped`);
|
|
574
|
+
} else {
|
|
575
|
+
if (consumed > 0) {
|
|
576
|
+
let stashArray = new Uint8Array(this._stashBuffer, 0, this._bufferSize);
|
|
577
|
+
let remainArray = new Uint8Array(buffer, consumed);
|
|
578
|
+
stashArray.set(remainArray, 0);
|
|
579
|
+
this._stashUsed = remainArray.byteLength;
|
|
580
|
+
this._stashByteStart += consumed;
|
|
581
|
+
}
|
|
582
|
+
return 0;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
this._stashUsed = 0;
|
|
586
|
+
this._stashByteStart = 0;
|
|
587
|
+
return remain;
|
|
588
|
+
}
|
|
589
|
+
return 0;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
_onLoaderComplete(from, to) {
|
|
593
|
+
// Force-flush stash buffer, and drop unconsumed data
|
|
594
|
+
this._flushStashBuffer(true);
|
|
595
|
+
|
|
596
|
+
if (this._onComplete) {
|
|
597
|
+
this._onComplete(this._extraData);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
_onLoaderError(type, data) {
|
|
602
|
+
Log.e(this.TAG, `Loader error, code = ${data.code}, msg = ${data.msg}`);
|
|
603
|
+
|
|
604
|
+
this._flushStashBuffer(false);
|
|
605
|
+
|
|
606
|
+
if (this._isEarlyEofReconnecting) {
|
|
607
|
+
// Auto-reconnect for EarlyEof failed, throw UnrecoverableEarlyEof error to upper-layer
|
|
608
|
+
this._isEarlyEofReconnecting = false;
|
|
609
|
+
type = LoaderErrors.UNRECOVERABLE_EARLY_EOF;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
switch (type) {
|
|
613
|
+
case LoaderErrors.EARLY_EOF: {
|
|
614
|
+
if (!this._config.isLive) {
|
|
615
|
+
// Do internal http reconnect if not live stream
|
|
616
|
+
if (this._totalLength) {
|
|
617
|
+
let nextFrom = this._currentRange.to + 1;
|
|
618
|
+
if (nextFrom < this._totalLength) {
|
|
619
|
+
Log.w(this.TAG, 'Connection lost, trying reconnect...');
|
|
620
|
+
this._isEarlyEofReconnecting = true;
|
|
621
|
+
this._internalSeek(nextFrom, false);
|
|
622
|
+
}
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
// else: We don't know totalLength, throw UnrecoverableEarlyEof
|
|
626
|
+
}
|
|
627
|
+
// live stream: throw UnrecoverableEarlyEof error to upper-layer
|
|
628
|
+
type = LoaderErrors.UNRECOVERABLE_EARLY_EOF;
|
|
629
|
+
break;
|
|
630
|
+
}
|
|
631
|
+
case LoaderErrors.UNRECOVERABLE_EARLY_EOF:
|
|
632
|
+
case LoaderErrors.CONNECTING_TIMEOUT:
|
|
633
|
+
case LoaderErrors.HTTP_STATUS_CODE_INVALID:
|
|
634
|
+
case LoaderErrors.EXCEPTION:
|
|
635
|
+
break;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
if (this._onError) {
|
|
639
|
+
this._onError(type, data);
|
|
640
|
+
} else {
|
|
641
|
+
throw new RuntimeException('IOException: ' + data.msg);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
export default IOController;
|