@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,366 @@
|
|
|
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 {BaseLoader, LoaderStatus, LoaderErrors} from './loader.js';
|
|
22
|
+
import {RuntimeException} from '../utils/exception.js';
|
|
23
|
+
|
|
24
|
+
// Universal IO Loader, implemented by adding Range header in xhr's request header
|
|
25
|
+
class RangeLoader extends BaseLoader {
|
|
26
|
+
|
|
27
|
+
static isSupported() {
|
|
28
|
+
try {
|
|
29
|
+
let xhr = new XMLHttpRequest();
|
|
30
|
+
xhr.open('GET', 'https://example.com', true);
|
|
31
|
+
xhr.responseType = 'arraybuffer';
|
|
32
|
+
return (xhr.responseType === 'arraybuffer');
|
|
33
|
+
} catch (e) {
|
|
34
|
+
Log.w('RangeLoader', e.message);
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
constructor(seekHandler, config) {
|
|
40
|
+
super('xhr-range-loader');
|
|
41
|
+
this.TAG = 'RangeLoader';
|
|
42
|
+
|
|
43
|
+
this._seekHandler = seekHandler;
|
|
44
|
+
this._config = config;
|
|
45
|
+
this._needStash = false;
|
|
46
|
+
|
|
47
|
+
this._chunkSizeKBList = [
|
|
48
|
+
128, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096, 5120, 6144, 7168, 8192
|
|
49
|
+
];
|
|
50
|
+
this._currentChunkSizeKB = 384;
|
|
51
|
+
this._currentSpeedNormalized = 0;
|
|
52
|
+
this._zeroSpeedChunkCount = 0;
|
|
53
|
+
|
|
54
|
+
this._xhr = null;
|
|
55
|
+
this._speedSampler = new SpeedSampler();
|
|
56
|
+
|
|
57
|
+
this._requestAbort = false;
|
|
58
|
+
this._waitForTotalLength = false;
|
|
59
|
+
this._totalLengthReceived = false;
|
|
60
|
+
|
|
61
|
+
this._currentRequestURL = null;
|
|
62
|
+
this._currentRedirectedURL = null;
|
|
63
|
+
this._currentRequestRange = null;
|
|
64
|
+
this._totalLength = null; // size of the entire file
|
|
65
|
+
this._contentLength = null; // Content-Length of entire request range
|
|
66
|
+
this._receivedLength = 0; // total received bytes
|
|
67
|
+
this._lastTimeLoaded = 0; // received bytes of current request sub-range
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
destroy() {
|
|
71
|
+
if (this.isWorking()) {
|
|
72
|
+
this.abort();
|
|
73
|
+
}
|
|
74
|
+
if (this._xhr) {
|
|
75
|
+
this._xhr.onreadystatechange = null;
|
|
76
|
+
this._xhr.onprogress = null;
|
|
77
|
+
this._xhr.onload = null;
|
|
78
|
+
this._xhr.onerror = null;
|
|
79
|
+
this._xhr = null;
|
|
80
|
+
}
|
|
81
|
+
super.destroy();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
get currentSpeed() {
|
|
85
|
+
return this._speedSampler.lastSecondKBps;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
open(dataSource, range) {
|
|
89
|
+
this._dataSource = dataSource;
|
|
90
|
+
this._range = range;
|
|
91
|
+
this._status = LoaderStatus.kConnecting;
|
|
92
|
+
|
|
93
|
+
let useRefTotalLength = false;
|
|
94
|
+
if (this._dataSource.filesize != undefined && this._dataSource.filesize !== 0) {
|
|
95
|
+
useRefTotalLength = true;
|
|
96
|
+
this._totalLength = this._dataSource.filesize;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!this._totalLengthReceived && !useRefTotalLength) {
|
|
100
|
+
// We need total filesize
|
|
101
|
+
this._waitForTotalLength = true;
|
|
102
|
+
this._internalOpen(this._dataSource, {from: 0, to: -1});
|
|
103
|
+
} else {
|
|
104
|
+
// We have filesize, start loading
|
|
105
|
+
this._openSubRange();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
_openSubRange() {
|
|
110
|
+
let chunkSize = this._currentChunkSizeKB * 1024;
|
|
111
|
+
|
|
112
|
+
let from = this._range.from + this._receivedLength;
|
|
113
|
+
let to = from + chunkSize;
|
|
114
|
+
|
|
115
|
+
if (this._contentLength != null) {
|
|
116
|
+
if (to - this._range.from >= this._contentLength) {
|
|
117
|
+
to = this._range.from + this._contentLength - 1;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
this._currentRequestRange = {from, to};
|
|
122
|
+
this._internalOpen(this._dataSource, this._currentRequestRange);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
_internalOpen(dataSource, range) {
|
|
126
|
+
this._lastTimeLoaded = 0;
|
|
127
|
+
|
|
128
|
+
let sourceURL = dataSource.url;
|
|
129
|
+
if (this._config.reuseRedirectedURL) {
|
|
130
|
+
if (this._currentRedirectedURL != undefined) {
|
|
131
|
+
sourceURL = this._currentRedirectedURL;
|
|
132
|
+
} else if (dataSource.redirectedURL != undefined) {
|
|
133
|
+
sourceURL = dataSource.redirectedURL;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let seekConfig = this._seekHandler.getConfig(sourceURL, range);
|
|
138
|
+
this._currentRequestURL = seekConfig.url;
|
|
139
|
+
|
|
140
|
+
let xhr = this._xhr = new XMLHttpRequest();
|
|
141
|
+
xhr.open('GET', seekConfig.url, true);
|
|
142
|
+
xhr.responseType = 'arraybuffer';
|
|
143
|
+
xhr.onreadystatechange = this._onReadyStateChange.bind(this);
|
|
144
|
+
xhr.onprogress = this._onProgress.bind(this);
|
|
145
|
+
xhr.onload = this._onLoad.bind(this);
|
|
146
|
+
xhr.onerror = this._onXhrError.bind(this);
|
|
147
|
+
|
|
148
|
+
if (dataSource.withCredentials) {
|
|
149
|
+
xhr.withCredentials = true;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (typeof seekConfig.headers === 'object') {
|
|
153
|
+
let headers = seekConfig.headers;
|
|
154
|
+
|
|
155
|
+
for (let key in headers) {
|
|
156
|
+
if (headers.hasOwnProperty(key)) {
|
|
157
|
+
xhr.setRequestHeader(key, headers[key]);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// add additional headers
|
|
163
|
+
if (typeof this._config.headers === 'object') {
|
|
164
|
+
let headers = this._config.headers;
|
|
165
|
+
|
|
166
|
+
for (let key in headers) {
|
|
167
|
+
if (headers.hasOwnProperty(key)) {
|
|
168
|
+
xhr.setRequestHeader(key, headers[key]);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
xhr.send();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
abort() {
|
|
177
|
+
this._requestAbort = true;
|
|
178
|
+
this._internalAbort();
|
|
179
|
+
this._status = LoaderStatus.kComplete;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
_internalAbort() {
|
|
183
|
+
if (this._xhr) {
|
|
184
|
+
this._xhr.onreadystatechange = null;
|
|
185
|
+
this._xhr.onprogress = null;
|
|
186
|
+
this._xhr.onload = null;
|
|
187
|
+
this._xhr.onerror = null;
|
|
188
|
+
this._xhr.abort();
|
|
189
|
+
this._xhr = null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
_onReadyStateChange(e) {
|
|
194
|
+
let xhr = e.target;
|
|
195
|
+
|
|
196
|
+
if (xhr.readyState === 2) { // HEADERS_RECEIVED
|
|
197
|
+
if (xhr.responseURL != undefined) { // if the browser support this property
|
|
198
|
+
let redirectedURL = this._seekHandler.removeURLParameters(xhr.responseURL);
|
|
199
|
+
if (xhr.responseURL !== this._currentRequestURL && redirectedURL !== this._currentRedirectedURL) {
|
|
200
|
+
this._currentRedirectedURL = redirectedURL;
|
|
201
|
+
if (this._onURLRedirect) {
|
|
202
|
+
this._onURLRedirect(redirectedURL);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if ((xhr.status >= 200 && xhr.status <= 299)) {
|
|
208
|
+
if (this._waitForTotalLength) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
this._status = LoaderStatus.kBuffering;
|
|
212
|
+
} else {
|
|
213
|
+
this._status = LoaderStatus.kError;
|
|
214
|
+
if (this._onError) {
|
|
215
|
+
this._onError(LoaderErrors.HTTP_STATUS_CODE_INVALID, {code: xhr.status, msg: xhr.statusText});
|
|
216
|
+
} else {
|
|
217
|
+
throw new RuntimeException('RangeLoader: Http code invalid, ' + xhr.status + ' ' + xhr.statusText);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
_onProgress(e) {
|
|
224
|
+
if (this._status === LoaderStatus.kError) {
|
|
225
|
+
// Ignore error response
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (this._contentLength === null) {
|
|
230
|
+
let openNextRange = false;
|
|
231
|
+
|
|
232
|
+
if (this._waitForTotalLength) {
|
|
233
|
+
this._waitForTotalLength = false;
|
|
234
|
+
this._totalLengthReceived = true;
|
|
235
|
+
openNextRange = true;
|
|
236
|
+
|
|
237
|
+
let total = e.total;
|
|
238
|
+
this._internalAbort();
|
|
239
|
+
if (total != null & total !== 0) {
|
|
240
|
+
this._totalLength = total;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// calculate currrent request range's contentLength
|
|
245
|
+
if (this._range.to === -1) {
|
|
246
|
+
this._contentLength = this._totalLength - this._range.from;
|
|
247
|
+
} else { // to !== -1
|
|
248
|
+
this._contentLength = this._range.to - this._range.from + 1;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (openNextRange) {
|
|
252
|
+
this._openSubRange();
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (this._onContentLengthKnown) {
|
|
256
|
+
this._onContentLengthKnown(this._contentLength);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
let delta = e.loaded - this._lastTimeLoaded;
|
|
261
|
+
this._lastTimeLoaded = e.loaded;
|
|
262
|
+
this._speedSampler.addBytes(delta);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
_normalizeSpeed(input) {
|
|
266
|
+
let list = this._chunkSizeKBList;
|
|
267
|
+
let last = list.length - 1;
|
|
268
|
+
let mid = 0;
|
|
269
|
+
let lbound = 0;
|
|
270
|
+
let ubound = last;
|
|
271
|
+
|
|
272
|
+
if (input < list[0]) {
|
|
273
|
+
return list[0];
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
while (lbound <= ubound) {
|
|
277
|
+
mid = lbound + Math.floor((ubound - lbound) / 2);
|
|
278
|
+
if (mid === last || (input >= list[mid] && input < list[mid + 1])) {
|
|
279
|
+
return list[mid];
|
|
280
|
+
} else if (list[mid] < input) {
|
|
281
|
+
lbound = mid + 1;
|
|
282
|
+
} else {
|
|
283
|
+
ubound = mid - 1;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
_onLoad(e) {
|
|
289
|
+
if (this._status === LoaderStatus.kError) {
|
|
290
|
+
// Ignore error response
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (this._waitForTotalLength) {
|
|
295
|
+
this._waitForTotalLength = false;
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
this._lastTimeLoaded = 0;
|
|
300
|
+
let KBps = this._speedSampler.lastSecondKBps;
|
|
301
|
+
if (KBps === 0) {
|
|
302
|
+
this._zeroSpeedChunkCount++;
|
|
303
|
+
if (this._zeroSpeedChunkCount >= 3) {
|
|
304
|
+
// Try get currentKBps after 3 chunks
|
|
305
|
+
KBps = this._speedSampler.currentKBps;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (KBps !== 0) {
|
|
310
|
+
let normalized = this._normalizeSpeed(KBps);
|
|
311
|
+
if (this._currentSpeedNormalized !== normalized) {
|
|
312
|
+
this._currentSpeedNormalized = normalized;
|
|
313
|
+
this._currentChunkSizeKB = normalized;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
let chunk = e.target.response;
|
|
318
|
+
let byteStart = this._range.from + this._receivedLength;
|
|
319
|
+
this._receivedLength += chunk.byteLength;
|
|
320
|
+
|
|
321
|
+
let reportComplete = false;
|
|
322
|
+
|
|
323
|
+
if (this._contentLength != null && this._receivedLength < this._contentLength) {
|
|
324
|
+
// continue load next chunk
|
|
325
|
+
this._openSubRange();
|
|
326
|
+
} else {
|
|
327
|
+
reportComplete = true;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// dispatch received chunk
|
|
331
|
+
if (this._onDataArrival) {
|
|
332
|
+
this._onDataArrival(chunk, byteStart, this._receivedLength);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (reportComplete) {
|
|
336
|
+
this._status = LoaderStatus.kComplete;
|
|
337
|
+
if (this._onComplete) {
|
|
338
|
+
this._onComplete(this._range.from, this._range.from + this._receivedLength - 1);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
_onXhrError(e) {
|
|
344
|
+
this._status = LoaderStatus.kError;
|
|
345
|
+
let type = 0;
|
|
346
|
+
let info = null;
|
|
347
|
+
|
|
348
|
+
if (this._contentLength && this._receivedLength > 0
|
|
349
|
+
&& this._receivedLength < this._contentLength) {
|
|
350
|
+
type = LoaderErrors.EARLY_EOF;
|
|
351
|
+
info = {code: -1, msg: 'RangeLoader meet Early-Eof'};
|
|
352
|
+
} else {
|
|
353
|
+
type = LoaderErrors.EXCEPTION;
|
|
354
|
+
info = {code: -1, msg: e.constructor.name + ' ' + e.type};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (this._onError) {
|
|
358
|
+
this._onError(type, info);
|
|
359
|
+
} else {
|
|
360
|
+
throw new RuntimeException(info.msg);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export default RangeLoader;
|
package/src/mpegts.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
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 Polyfill from './utils/polyfill.js';
|
|
20
|
+
import Features from './core/features.js';
|
|
21
|
+
import {BaseLoader, LoaderStatus, LoaderErrors} from './io/loader.js';
|
|
22
|
+
import MSEPlayer from './player/mse-player';
|
|
23
|
+
import NativePlayer from './player/native-player.js';
|
|
24
|
+
import PlayerEvents from './player/player-events';
|
|
25
|
+
import {ErrorTypes, ErrorDetails} from './player/player-errors.js';
|
|
26
|
+
import LoggingControl from './utils/logging-control.js';
|
|
27
|
+
import {InvalidArgumentException} from './utils/exception.js';
|
|
28
|
+
|
|
29
|
+
// here are all the interfaces
|
|
30
|
+
|
|
31
|
+
// install polyfills
|
|
32
|
+
Polyfill.install();
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
// factory method
|
|
36
|
+
function createPlayer(mediaDataSource, optionalConfig) {
|
|
37
|
+
let mds = mediaDataSource;
|
|
38
|
+
if (mds == null || typeof mds !== 'object') {
|
|
39
|
+
throw new InvalidArgumentException('MediaDataSource must be an javascript object!');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!mds.hasOwnProperty('type')) {
|
|
43
|
+
throw new InvalidArgumentException('MediaDataSource must has type field to indicate video file type!');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
switch (mds.type) {
|
|
47
|
+
case 'mse':
|
|
48
|
+
case 'mpegts':
|
|
49
|
+
case 'm2ts':
|
|
50
|
+
case 'flv':
|
|
51
|
+
return new MSEPlayer(mds, optionalConfig);
|
|
52
|
+
default:
|
|
53
|
+
return new NativePlayer(mds, optionalConfig);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
// feature detection
|
|
59
|
+
function isSupported() {
|
|
60
|
+
return Features.supportMSEH264Playback();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function getFeatureList() {
|
|
64
|
+
return Features.getFeatureList();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
// interfaces
|
|
69
|
+
let mpegts = {};
|
|
70
|
+
|
|
71
|
+
mpegts.createPlayer = createPlayer;
|
|
72
|
+
mpegts.isSupported = isSupported;
|
|
73
|
+
mpegts.getFeatureList = getFeatureList;
|
|
74
|
+
|
|
75
|
+
mpegts.BaseLoader = BaseLoader;
|
|
76
|
+
mpegts.LoaderStatus = LoaderStatus;
|
|
77
|
+
mpegts.LoaderErrors = LoaderErrors;
|
|
78
|
+
|
|
79
|
+
mpegts.Events = PlayerEvents;
|
|
80
|
+
mpegts.ErrorTypes = ErrorTypes;
|
|
81
|
+
mpegts.ErrorDetails = ErrorDetails;
|
|
82
|
+
|
|
83
|
+
mpegts.MSEPlayer = MSEPlayer;
|
|
84
|
+
mpegts.NativePlayer = NativePlayer;
|
|
85
|
+
mpegts.LoggingControl = LoggingControl;
|
|
86
|
+
|
|
87
|
+
Object.defineProperty(mpegts, 'version', {
|
|
88
|
+
enumerable: true,
|
|
89
|
+
get: function () {
|
|
90
|
+
// replaced by webpack.DefinePlugin
|
|
91
|
+
return __VERSION__;
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
export default mpegts;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2023 zheng qian. 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
|
+
// Live buffer latency chaser by directly adjusting HTMLMediaElement.currentTime (not recommended)
|
|
20
|
+
class LiveLatencyChaser {
|
|
21
|
+
|
|
22
|
+
private _config: any = null;
|
|
23
|
+
private _media_element: HTMLMediaElement = null;
|
|
24
|
+
private _on_direct_seek: (target: number) => void = null;
|
|
25
|
+
|
|
26
|
+
public constructor(config: any, media_element: HTMLMediaElement, on_direct_seek: (target: number) => void) {
|
|
27
|
+
this._config = config;
|
|
28
|
+
this._media_element = media_element;
|
|
29
|
+
this._on_direct_seek = on_direct_seek;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public destroy(): void {
|
|
33
|
+
this._on_direct_seek = null;
|
|
34
|
+
this._media_element = null;
|
|
35
|
+
this._config = null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public notifyBufferedRangeUpdate(): void {
|
|
39
|
+
this._chaseLiveLatency();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private _chaseLiveLatency(): void {
|
|
43
|
+
const buffered: TimeRanges = this._media_element.buffered;
|
|
44
|
+
const current_time: number = this._media_element.currentTime;
|
|
45
|
+
|
|
46
|
+
const paused = this._media_element.paused;
|
|
47
|
+
|
|
48
|
+
if (!this._config.isLive ||
|
|
49
|
+
!this._config.liveBufferLatencyChasing ||
|
|
50
|
+
buffered.length == 0 ||
|
|
51
|
+
(!this._config.liveBufferLatencyChasingOnPaused && paused)) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const buffered_end = buffered.end(buffered.length - 1);
|
|
56
|
+
if (buffered_end > this._config.liveBufferLatencyMaxLatency) {
|
|
57
|
+
if (buffered_end - current_time > this._config.liveBufferLatencyMaxLatency) {
|
|
58
|
+
let target_time = buffered_end - this._config.liveBufferLatencyMinRemain;
|
|
59
|
+
this._on_direct_seek(target_time);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export default LiveLatencyChaser;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2023 zheng qian. 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
|
+
// Live buffer latency synchronizer by increasing HTMLMediaElement.playbackRate
|
|
20
|
+
class LiveLatencySynchronizer {
|
|
21
|
+
|
|
22
|
+
private _config: any = null;
|
|
23
|
+
private _media_element: HTMLMediaElement = null;
|
|
24
|
+
|
|
25
|
+
private e?: any = null;
|
|
26
|
+
|
|
27
|
+
public constructor(config: any, media_element: HTMLMediaElement) {
|
|
28
|
+
this._config = config;
|
|
29
|
+
this._media_element = media_element;
|
|
30
|
+
|
|
31
|
+
this.e = {
|
|
32
|
+
onMediaTimeUpdate: this._onMediaTimeUpdate.bind(this),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
this._media_element.addEventListener('timeupdate', this.e.onMediaTimeUpdate);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public destroy(): void {
|
|
39
|
+
this._media_element.removeEventListener('timeupdate', this.e.onMediaTimeUpdate);
|
|
40
|
+
this._media_element = null;
|
|
41
|
+
this._config = null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private _onMediaTimeUpdate(e: Event): void {
|
|
45
|
+
if (!this._config.isLive || !this._config.liveSync) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const latency = this._getCurrentLatency();
|
|
50
|
+
|
|
51
|
+
if (latency > this._config.liveSyncMaxLatency) {
|
|
52
|
+
const playback_rate = Math.min(2, Math.max(1, this._config.liveSyncPlaybackRate));
|
|
53
|
+
this._media_element.playbackRate = playback_rate;
|
|
54
|
+
} else if (latency > this._config.liveSyncTargetLatency) {
|
|
55
|
+
// do nothing, keep playbackRate
|
|
56
|
+
} else if (this._media_element.playbackRate !== 1 && this._media_element.playbackRate !== 0) {
|
|
57
|
+
this._media_element.playbackRate = 1;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private _getCurrentLatency(): number {
|
|
62
|
+
if (!this._media_element) {
|
|
63
|
+
return 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const buffered = this._media_element.buffered;
|
|
67
|
+
const current_time = this._media_element.currentTime;
|
|
68
|
+
|
|
69
|
+
if (buffered.length == 0) {
|
|
70
|
+
return 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const buffered_end = buffered.end(buffered.length - 1);
|
|
74
|
+
return buffered_end - current_time;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export default LiveLatencySynchronizer;
|