@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.
Files changed (121) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +158 -0
  3. package/README_ja.md +153 -0
  4. package/README_zh.md +157 -0
  5. package/d.ts/mpegts.d.ts +524 -0
  6. package/d.ts/src/core/mse-events.d.ts +9 -0
  7. package/d.ts/src/core/transmuxing-events.d.ts +24 -0
  8. package/d.ts/src/demux/aac.d.ts +44 -0
  9. package/d.ts/src/demux/ac3.d.ts +70 -0
  10. package/d.ts/src/demux/av1-parser.d.ts +77 -0
  11. package/d.ts/src/demux/av1.d.ts +11 -0
  12. package/d.ts/src/demux/base-demuxer.d.ts +55 -0
  13. package/d.ts/src/demux/h264.d.ts +40 -0
  14. package/d.ts/src/demux/h265.d.ts +65 -0
  15. package/d.ts/src/demux/klv.d.ts +17 -0
  16. package/d.ts/src/demux/mp3.d.ts +6 -0
  17. package/d.ts/src/demux/mpeg4-audio.d.ts +28 -0
  18. package/d.ts/src/demux/pat-pmt-pes.d.ts +106 -0
  19. package/d.ts/src/demux/patpmt.d.ts +40 -0
  20. package/d.ts/src/demux/pes-private-data.d.ts +14 -0
  21. package/d.ts/src/demux/pgs-data.d.ts +9 -0
  22. package/d.ts/src/demux/scte35.d.ts +250 -0
  23. package/d.ts/src/demux/sei.d.ts +8 -0
  24. package/d.ts/src/demux/smpte2038.d.ts +22 -0
  25. package/d.ts/src/demux/ts-demuxer.d.ts +124 -0
  26. package/d.ts/src/player/live-latency-chaser.d.ts +10 -0
  27. package/d.ts/src/player/live-latency-synchronizer.d.ts +10 -0
  28. package/d.ts/src/player/loading-controller.d.ts +19 -0
  29. package/d.ts/src/player/mse-player.d.ts +30 -0
  30. package/d.ts/src/player/player-engine-dedicated-thread-worker.d.ts +2 -0
  31. package/d.ts/src/player/player-engine-dedicated-thread.d.ts +48 -0
  32. package/d.ts/src/player/player-engine-main-thread.d.ts +50 -0
  33. package/d.ts/src/player/player-engine-worker-cmd-def.d.ts +25 -0
  34. package/d.ts/src/player/player-engine-worker-msg-def.d.ts +54 -0
  35. package/d.ts/src/player/player-engine-worker.d.ts +2 -0
  36. package/d.ts/src/player/player-engine.d.ts +16 -0
  37. package/d.ts/src/player/player-events.d.ts +21 -0
  38. package/d.ts/src/player/seeking-handler.d.ts +22 -0
  39. package/d.ts/src/player/startup-stall-jumper.d.ts +14 -0
  40. package/d.ts/src/utils/typedarray-equality.d.ts +2 -0
  41. package/dist/mpegts.js +3 -0
  42. package/dist/mpegts.js.LICENSE.txt +7 -0
  43. package/dist/mpegts.js.map +1 -0
  44. package/package.json +53 -0
  45. package/src/config.js +67 -0
  46. package/src/core/features.js +88 -0
  47. package/src/core/media-info.js +127 -0
  48. package/src/core/media-segment-info.js +230 -0
  49. package/src/core/mse-controller.js +599 -0
  50. package/src/core/mse-events.ts +28 -0
  51. package/src/core/transmuxer.js +346 -0
  52. package/src/core/transmuxing-controller.js +628 -0
  53. package/src/core/transmuxing-events.ts +43 -0
  54. package/src/core/transmuxing-worker.js +286 -0
  55. package/src/demux/aac.ts +397 -0
  56. package/src/demux/ac3.ts +335 -0
  57. package/src/demux/amf-parser.js +243 -0
  58. package/src/demux/av1-parser.ts +629 -0
  59. package/src/demux/av1.ts +103 -0
  60. package/src/demux/base-demuxer.ts +69 -0
  61. package/src/demux/demux-errors.js +26 -0
  62. package/src/demux/exp-golomb.js +116 -0
  63. package/src/demux/flv-demuxer.js +1854 -0
  64. package/src/demux/h264.ts +187 -0
  65. package/src/demux/h265-parser.js +501 -0
  66. package/src/demux/h265.ts +214 -0
  67. package/src/demux/klv.ts +40 -0
  68. package/src/demux/mp3.ts +7 -0
  69. package/src/demux/mpeg4-audio.ts +45 -0
  70. package/src/demux/pat-pmt-pes.ts +132 -0
  71. package/src/demux/pes-private-data.ts +16 -0
  72. package/src/demux/pgs-data.ts +11 -0
  73. package/src/demux/scte35.ts +723 -0
  74. package/src/demux/sei.ts +99 -0
  75. package/src/demux/smpte2038.ts +89 -0
  76. package/src/demux/sps-parser.js +298 -0
  77. package/src/demux/ts-demuxer.ts +2405 -0
  78. package/src/index.js +4 -0
  79. package/src/io/fetch-stream-loader.js +266 -0
  80. package/src/io/io-controller.js +647 -0
  81. package/src/io/loader.js +134 -0
  82. package/src/io/param-seek-handler.js +85 -0
  83. package/src/io/range-seek-handler.js +52 -0
  84. package/src/io/speed-sampler.js +93 -0
  85. package/src/io/websocket-loader.js +151 -0
  86. package/src/io/xhr-moz-chunked-loader.js +211 -0
  87. package/src/io/xhr-msstream-loader.js +307 -0
  88. package/src/io/xhr-range-loader.js +366 -0
  89. package/src/mpegts.js +95 -0
  90. package/src/player/live-latency-chaser.ts +66 -0
  91. package/src/player/live-latency-synchronizer.ts +79 -0
  92. package/src/player/loading-controller.ts +142 -0
  93. package/src/player/mse-player.ts +150 -0
  94. package/src/player/native-player.js +262 -0
  95. package/src/player/player-engine-dedicated-thread.ts +479 -0
  96. package/src/player/player-engine-main-thread.ts +463 -0
  97. package/src/player/player-engine-worker-cmd-def.ts +62 -0
  98. package/src/player/player-engine-worker-msg-def.ts +102 -0
  99. package/src/player/player-engine-worker.ts +370 -0
  100. package/src/player/player-engine.ts +35 -0
  101. package/src/player/player-errors.js +39 -0
  102. package/src/player/player-events.ts +40 -0
  103. package/src/player/seeking-handler.ts +205 -0
  104. package/src/player/startup-stall-jumper.ts +86 -0
  105. package/src/remux/aac-silent.js +56 -0
  106. package/src/remux/mp4-generator.js +866 -0
  107. package/src/remux/mp4-remuxer.js +778 -0
  108. package/src/utils/browser.js +128 -0
  109. package/src/utils/exception.js +73 -0
  110. package/src/utils/logger.js +140 -0
  111. package/src/utils/logging-control.js +165 -0
  112. package/src/utils/polyfill.js +68 -0
  113. package/src/utils/typedarray-equality.ts +69 -0
  114. package/src/utils/utf8-conv.js +84 -0
  115. package/src/utils/webworkify-webpack.js +202 -0
  116. package/tsconfig.json +16 -0
  117. package/tslint.json +1 -0
  118. package/types/index.d.ts +3 -0
  119. package/types/test-flv.ts +8 -0
  120. package/types/tsconfig.json +24 -0
  121. package/webpack.config.js +55 -0
@@ -0,0 +1,599 @@
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 EventEmitter from 'events';
20
+ import Log from '../utils/logger.js';
21
+ import Browser from '../utils/browser.js';
22
+ import MSEEvents from './mse-events';
23
+ import {IllegalStateException} from '../utils/exception.js';
24
+
25
+ // Media Source Extensions controller
26
+ class MSEController {
27
+
28
+ constructor(config) {
29
+ this.TAG = 'MSEController';
30
+
31
+ this._config = config;
32
+ this._emitter = new EventEmitter();
33
+
34
+ if (this._config.isLive && this._config.autoCleanupSourceBuffer == undefined) {
35
+ // For live stream, do auto cleanup by default
36
+ this._config.autoCleanupSourceBuffer = true;
37
+ }
38
+
39
+ this.e = {
40
+ onSourceOpen: this._onSourceOpen.bind(this),
41
+ onSourceEnded: this._onSourceEnded.bind(this),
42
+ onSourceClose: this._onSourceClose.bind(this),
43
+ onStartStreaming: this._onStartStreaming.bind(this),
44
+ onEndStreaming: this._onEndStreaming.bind(this),
45
+ onQualityChange: this._onQualityChange.bind(this),
46
+ onSourceBufferError: this._onSourceBufferError.bind(this),
47
+ onSourceBufferUpdateEnd: this._onSourceBufferUpdateEnd.bind(this)
48
+ };
49
+
50
+ // Use ManagedMediaSource only if w3c MediaSource is not available (e.g. iOS Safari)
51
+ this._useManagedMediaSource = ('ManagedMediaSource' in self) && !('MediaSource' in self);
52
+
53
+ this._mediaSource = null;
54
+ this._mediaSourceObjectURL = null;
55
+
56
+ this._mediaElementProxy = null;
57
+
58
+ this._isBufferFull = false;
59
+ this._hasPendingEos = false;
60
+
61
+ this._requireSetMediaDuration = false;
62
+ this._pendingMediaDuration = 0;
63
+
64
+ this._pendingSourceBufferInit = [];
65
+ this._mimeTypes = {
66
+ video: null,
67
+ audio: null
68
+ };
69
+ this._sourceBuffers = {
70
+ video: null,
71
+ audio: null
72
+ };
73
+ this._lastInitSegments = {
74
+ video: null,
75
+ audio: null
76
+ };
77
+ this._pendingSegments = {
78
+ video: [],
79
+ audio: []
80
+ };
81
+ this._pendingRemoveRanges = {
82
+ video: [],
83
+ audio: []
84
+ };
85
+ }
86
+
87
+ destroy() {
88
+ if (this._mediaSource) {
89
+ this.shutdown();
90
+ }
91
+ if (this._mediaSourceObjectURL) {
92
+ this.revokeObjectURL();
93
+ }
94
+ this.e = null;
95
+ this._emitter.removeAllListeners();
96
+ this._emitter = null;
97
+ }
98
+
99
+ on(event, listener) {
100
+ this._emitter.addListener(event, listener);
101
+ }
102
+
103
+ off(event, listener) {
104
+ this._emitter.removeListener(event, listener);
105
+ }
106
+
107
+ initialize(mediaElementProxy) {
108
+ if (this._mediaSource) {
109
+ throw new IllegalStateException('MediaSource has been attached to an HTMLMediaElement!');
110
+ }
111
+
112
+ if (this._useManagedMediaSource) {
113
+ Log.v(this.TAG, 'Using ManagedMediaSource');
114
+ }
115
+
116
+ let ms = this._mediaSource = this._useManagedMediaSource ? new self.ManagedMediaSource() : new self.MediaSource();
117
+ ms.addEventListener('sourceopen', this.e.onSourceOpen);
118
+ ms.addEventListener('sourceended', this.e.onSourceEnded);
119
+ ms.addEventListener('sourceclose', this.e.onSourceClose);
120
+
121
+ if (this._useManagedMediaSource) {
122
+ ms.addEventListener('startstreaming', this.e.onStartStreaming);
123
+ ms.addEventListener('endstreaming', this.e.onEndStreaming);
124
+ ms.addEventListener('qualitychange', this.e.onQualityChange);
125
+ }
126
+
127
+ this._mediaElementProxy = mediaElementProxy;
128
+ }
129
+
130
+ shutdown() {
131
+ if (this._mediaSource) {
132
+ let ms = this._mediaSource;
133
+ for (let type in this._sourceBuffers) {
134
+ // pending segments should be discard
135
+ let ps = this._pendingSegments[type];
136
+ ps.splice(0, ps.length);
137
+ this._pendingSegments[type] = null;
138
+ this._pendingRemoveRanges[type] = null;
139
+ this._lastInitSegments[type] = null;
140
+
141
+ // remove all sourcebuffers
142
+ let sb = this._sourceBuffers[type];
143
+ if (sb) {
144
+ if (ms.readyState !== 'closed') {
145
+ // ms edge can throw an error: Unexpected call to method or property access
146
+ try {
147
+ ms.removeSourceBuffer(sb);
148
+ } catch (error) {
149
+ Log.e(this.TAG, error.message);
150
+ }
151
+ sb.removeEventListener('error', this.e.onSourceBufferError);
152
+ sb.removeEventListener('updateend', this.e.onSourceBufferUpdateEnd);
153
+ }
154
+ this._mimeTypes[type] = null;
155
+ this._sourceBuffers[type] = null;
156
+ }
157
+ }
158
+ if (ms.readyState === 'open') {
159
+ try {
160
+ ms.endOfStream();
161
+ } catch (error) {
162
+ Log.e(this.TAG, error.message);
163
+ }
164
+ }
165
+ this._mediaElementProxy = null;
166
+ ms.removeEventListener('sourceopen', this.e.onSourceOpen);
167
+ ms.removeEventListener('sourceended', this.e.onSourceEnded);
168
+ ms.removeEventListener('sourceclose', this.e.onSourceClose);
169
+ if (this._useManagedMediaSource) {
170
+ ms.removeEventListener('startstreaming', this.e.onStartStreaming);
171
+ ms.removeEventListener('endstreaming', this.e.onEndStreaming);
172
+ ms.removeEventListener('qualitychange', this.e.onQualityChange);
173
+ }
174
+ this._pendingSourceBufferInit = [];
175
+ this._isBufferFull = false;
176
+ this._mediaSource = null;
177
+ }
178
+ }
179
+
180
+ isManagedMediaSource() {
181
+ return this._useManagedMediaSource;
182
+ }
183
+
184
+ getObject() {
185
+ if (!this._mediaSource) {
186
+ throw new IllegalStateException('MediaSource has not been initialized yet!');
187
+ }
188
+ return this._mediaSource;
189
+ }
190
+
191
+ getHandle() {
192
+ if (!this._mediaSource) {
193
+ throw new IllegalStateException('MediaSource has not been initialized yet!');
194
+ }
195
+ return this._mediaSource.handle;
196
+ }
197
+
198
+ getObjectURL() {
199
+ if (!this._mediaSource) {
200
+ throw new IllegalStateException('MediaSource has not been initialized yet!');
201
+ }
202
+
203
+ if (this._mediaSourceObjectURL == null) {
204
+ this._mediaSourceObjectURL = URL.createObjectURL(this._mediaSource);
205
+ }
206
+ return this._mediaSourceObjectURL;
207
+ }
208
+
209
+ revokeObjectURL() {
210
+ if (this._mediaSourceObjectURL) {
211
+ URL.revokeObjectURL(this._mediaSourceObjectURL);
212
+ this._mediaSourceObjectURL = null;
213
+ }
214
+ }
215
+
216
+ appendInitSegment(initSegment, deferred = undefined) {
217
+ if (!this._mediaSource || this._mediaSource.readyState !== 'open' || this._mediaSource.streaming === false) {
218
+ // sourcebuffer creation requires mediaSource.readyState === 'open'
219
+ // so we defer the sourcebuffer creation, until sourceopen event triggered
220
+ this._pendingSourceBufferInit.push(initSegment);
221
+ // make sure that this InitSegment is in the front of pending segments queue
222
+ this._pendingSegments[initSegment.type].push(initSegment);
223
+ return;
224
+ }
225
+
226
+ let is = initSegment;
227
+ let mimeType = `${is.container}`;
228
+ if (is.codec && is.codec.length > 0) {
229
+ if (is.codec === 'opus' && Browser.safari) {
230
+ is.codec = 'Opus';
231
+ }
232
+ mimeType += `;codecs=${is.codec}`;
233
+ }
234
+
235
+ let firstInitSegment = false;
236
+
237
+ Log.v(this.TAG, 'Received Initialization Segment, mimeType: ' + mimeType);
238
+ this._lastInitSegments[is.type] = is;
239
+
240
+ if (mimeType !== this._mimeTypes[is.type]) {
241
+ if (!this._mimeTypes[is.type]) { // empty, first chance create sourcebuffer
242
+ firstInitSegment = true;
243
+ try {
244
+ let sb = this._sourceBuffers[is.type] = this._mediaSource.addSourceBuffer(mimeType);
245
+ sb.addEventListener('error', this.e.onSourceBufferError);
246
+ sb.addEventListener('updateend', this.e.onSourceBufferUpdateEnd);
247
+ } catch (error) {
248
+ Log.e(this.TAG, error.message);
249
+ this._emitter.emit(MSEEvents.ERROR, {code: error.code, msg: error.message});
250
+ return;
251
+ }
252
+ } else {
253
+ Log.v(this.TAG, `Notice: ${is.type} mimeType changed, origin: ${this._mimeTypes[is.type]}, target: ${mimeType}`);
254
+ }
255
+ this._mimeTypes[is.type] = mimeType;
256
+ }
257
+
258
+ if (!deferred) {
259
+ // deferred means this InitSegment has been pushed to pendingSegments queue
260
+ this._pendingSegments[is.type].push(is);
261
+ }
262
+ if (!firstInitSegment) { // append immediately only if init segment in subsequence
263
+ if (this._sourceBuffers[is.type] && !this._sourceBuffers[is.type].updating) {
264
+ this._doAppendSegments();
265
+ }
266
+ }
267
+ if (Browser.safari && is.container === 'audio/mpeg' && is.mediaDuration > 0) {
268
+ // 'audio/mpeg' track under Safari may cause MediaElement's duration to be NaN
269
+ // Manually correct MediaSource.duration to make progress bar seekable, and report right duration
270
+ this._requireSetMediaDuration = true;
271
+ this._pendingMediaDuration = is.mediaDuration / 1000; // in seconds
272
+ this._updateMediaSourceDuration();
273
+ }
274
+ }
275
+
276
+ appendMediaSegment(mediaSegment) {
277
+ let ms = mediaSegment;
278
+ this._pendingSegments[ms.type].push(ms);
279
+
280
+ if (this._config.autoCleanupSourceBuffer && this._needCleanupSourceBuffer()) {
281
+ this._doCleanupSourceBuffer();
282
+ }
283
+
284
+ let sb = this._sourceBuffers[ms.type];
285
+ if (sb && !sb.updating && !this._hasPendingRemoveRanges()) {
286
+ this._doAppendSegments();
287
+ }
288
+ }
289
+
290
+ flush() {
291
+ // remove all appended buffers
292
+ for (let type in this._sourceBuffers) {
293
+ if (!this._sourceBuffers[type]) {
294
+ continue;
295
+ }
296
+
297
+ // abort current buffer append algorithm
298
+ let sb = this._sourceBuffers[type];
299
+ if (this._mediaSource.readyState === 'open') {
300
+ try {
301
+ // If range removal algorithm is running, InvalidStateError will be throwed
302
+ // Ignore it.
303
+ sb.abort();
304
+ } catch (error) {
305
+ Log.e(this.TAG, error.message);
306
+ }
307
+ }
308
+
309
+ // pending segments should be discard
310
+ let ps = this._pendingSegments[type];
311
+ ps.splice(0, ps.length);
312
+
313
+ if (this._mediaSource.readyState === 'closed') {
314
+ // Parent MediaSource object has been detached from HTMLMediaElement
315
+ continue;
316
+ }
317
+
318
+ // record ranges to be remove from SourceBuffer
319
+ for (let i = 0; i < sb.buffered.length; i++) {
320
+ let start = sb.buffered.start(i);
321
+ let end = sb.buffered.end(i);
322
+ this._pendingRemoveRanges[type].push({start, end});
323
+ }
324
+
325
+ // if sb is not updating, let's remove ranges now!
326
+ if (!sb.updating) {
327
+ this._doRemoveRanges();
328
+ }
329
+
330
+ // Safari 10 may get InvalidStateError in the later appendBuffer() after SourceBuffer.remove() call
331
+ // Internal parser's state may be invalid at this time. Re-append last InitSegment to workaround.
332
+ // Related issue: https://bugs.webkit.org/show_bug.cgi?id=159230
333
+ if (Browser.safari) {
334
+ let lastInitSegment = this._lastInitSegments[type];
335
+ if (lastInitSegment) {
336
+ this._pendingSegments[type].push(lastInitSegment);
337
+ if (!sb.updating) {
338
+ this._doAppendSegments();
339
+ }
340
+ }
341
+ }
342
+ }
343
+ }
344
+
345
+ endOfStream() {
346
+ let ms = this._mediaSource;
347
+ let sb = this._sourceBuffers;
348
+ if (!ms || ms.readyState !== 'open') {
349
+ if (ms && ms.readyState === 'closed' && this._hasPendingSegments()) {
350
+ // If MediaSource hasn't turned into open state, and there're pending segments
351
+ // Mark pending endOfStream, defer call until all pending segments appended complete
352
+ this._hasPendingEos = true;
353
+ }
354
+ return;
355
+ }
356
+ if (sb.video && sb.video.updating || sb.audio && sb.audio.updating) {
357
+ // If any sourcebuffer is updating, defer endOfStream operation
358
+ // See _onSourceBufferUpdateEnd()
359
+ this._hasPendingEos = true;
360
+ } else {
361
+ this._hasPendingEos = false;
362
+ // Notify media data loading complete
363
+ // This is helpful for correcting total duration to match last media segment
364
+ // Otherwise MediaElement's ended event may not be triggered
365
+ ms.endOfStream();
366
+ }
367
+ }
368
+
369
+ _needCleanupSourceBuffer() {
370
+ if (!this._config.autoCleanupSourceBuffer) {
371
+ return false;
372
+ }
373
+
374
+ let currentTime = this._mediaElementProxy.getCurrentTime();
375
+
376
+ for (let type in this._sourceBuffers) {
377
+ let sb = this._sourceBuffers[type];
378
+ if (sb) {
379
+ let buffered = sb.buffered;
380
+ if (buffered.length >= 1) {
381
+ if (currentTime - buffered.start(0) >= this._config.autoCleanupMaxBackwardDuration) {
382
+ return true;
383
+ }
384
+ }
385
+ }
386
+ }
387
+
388
+ return false;
389
+ }
390
+
391
+ _doCleanupSourceBuffer() {
392
+ let currentTime = this._mediaElementProxy.getCurrentTime();
393
+
394
+ for (let type in this._sourceBuffers) {
395
+ let sb = this._sourceBuffers[type];
396
+ if (sb) {
397
+ let buffered = sb.buffered;
398
+ let doRemove = false;
399
+
400
+ for (let i = 0; i < buffered.length; i++) {
401
+ let start = buffered.start(i);
402
+ let end = buffered.end(i);
403
+
404
+ if (start <= currentTime && currentTime < end + 3) { // padding 3 seconds
405
+ if (currentTime - start >= this._config.autoCleanupMaxBackwardDuration) {
406
+ doRemove = true;
407
+ let removeEnd = currentTime - this._config.autoCleanupMinBackwardDuration;
408
+ this._pendingRemoveRanges[type].push({start: start, end: removeEnd});
409
+ }
410
+ } else if (end < currentTime) {
411
+ doRemove = true;
412
+ this._pendingRemoveRanges[type].push({start: start, end: end});
413
+ }
414
+ }
415
+
416
+ if (doRemove && !sb.updating) {
417
+ this._doRemoveRanges();
418
+ }
419
+ }
420
+ }
421
+ }
422
+
423
+ _updateMediaSourceDuration() {
424
+ let sb = this._sourceBuffers;
425
+ if (this._mediaElementProxy.getReadyState() === 0 || this._mediaSource.readyState !== 'open') {
426
+ return;
427
+ }
428
+ if ((sb.video && sb.video.updating) || (sb.audio && sb.audio.updating)) {
429
+ return;
430
+ }
431
+
432
+ let current = this._mediaSource.duration;
433
+ let target = this._pendingMediaDuration;
434
+
435
+ if (target > 0 && (isNaN(current) || target > current)) {
436
+ Log.v(this.TAG, `Update MediaSource duration from ${current} to ${target}`);
437
+ this._mediaSource.duration = target;
438
+ }
439
+
440
+ this._requireSetMediaDuration = false;
441
+ this._pendingMediaDuration = 0;
442
+ }
443
+
444
+ _doRemoveRanges() {
445
+ for (let type in this._pendingRemoveRanges) {
446
+ if (!this._sourceBuffers[type] || this._sourceBuffers[type].updating) {
447
+ continue;
448
+ }
449
+ let sb = this._sourceBuffers[type];
450
+ let ranges = this._pendingRemoveRanges[type];
451
+ while (ranges.length && !sb.updating) {
452
+ let range = ranges.shift();
453
+ sb.remove(range.start, range.end);
454
+ }
455
+ }
456
+ }
457
+
458
+ _doAppendSegments() {
459
+ let pendingSegments = this._pendingSegments;
460
+
461
+ for (let type in pendingSegments) {
462
+ if (!this._sourceBuffers[type] || this._sourceBuffers[type].updating || this._mediaSource.streaming === false) {
463
+ continue;
464
+ }
465
+
466
+ if (pendingSegments[type].length > 0) {
467
+ let segment = pendingSegments[type].shift();
468
+
469
+ if (typeof segment.timestampOffset === 'number' && isFinite(segment.timestampOffset)) {
470
+ // For MPEG audio stream in MSE, if unbuffered-seeking occurred
471
+ // We need explicitly set timestampOffset to the desired point in timeline for mpeg SourceBuffer.
472
+ let currentOffset = this._sourceBuffers[type].timestampOffset;
473
+ let targetOffset = segment.timestampOffset / 1000; // in seconds
474
+
475
+ let delta = Math.abs(currentOffset - targetOffset);
476
+ if (delta > 0.1) { // If time delta > 100ms
477
+ Log.v(this.TAG, `Update MPEG audio timestampOffset from ${currentOffset} to ${targetOffset}`);
478
+ this._sourceBuffers[type].timestampOffset = targetOffset;
479
+ }
480
+ delete segment.timestampOffset;
481
+ }
482
+
483
+ if (!segment.data || segment.data.byteLength === 0) {
484
+ // Ignore empty buffer
485
+ continue;
486
+ }
487
+
488
+ try {
489
+ this._sourceBuffers[type].appendBuffer(segment.data);
490
+ this._isBufferFull = false;
491
+ } catch (error) {
492
+ this._pendingSegments[type].unshift(segment);
493
+ if (error.code === 22) { // QuotaExceededError
494
+ /* Notice that FireFox may not throw QuotaExceededError if SourceBuffer is full
495
+ * Currently we can only do lazy-load to avoid SourceBuffer become scattered.
496
+ * SourceBuffer eviction policy may be changed in future version of FireFox.
497
+ *
498
+ * Related issues:
499
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1279885
500
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1280023
501
+ */
502
+
503
+ // report buffer full, abort network IO
504
+ if (!this._isBufferFull) {
505
+ this._emitter.emit(MSEEvents.BUFFER_FULL);
506
+ }
507
+ this._isBufferFull = true;
508
+ } else {
509
+ Log.e(this.TAG, error.message);
510
+ this._emitter.emit(MSEEvents.ERROR, {code: error.code, msg: error.message});
511
+ }
512
+ }
513
+ }
514
+ }
515
+ }
516
+
517
+ _onSourceOpen() {
518
+ Log.v(this.TAG, 'MediaSource onSourceOpen');
519
+ this._mediaSource.removeEventListener('sourceopen', this.e.onSourceOpen);
520
+ // deferred sourcebuffer creation / initialization
521
+ if (this._pendingSourceBufferInit.length > 0) {
522
+ let pendings = this._pendingSourceBufferInit;
523
+ while (pendings.length) {
524
+ let segment = pendings.shift();
525
+ this.appendInitSegment(segment, true);
526
+ }
527
+ }
528
+ // there may be some pending media segments, append them
529
+ if (this._hasPendingSegments()) {
530
+ this._doAppendSegments();
531
+ }
532
+ this._emitter.emit(MSEEvents.SOURCE_OPEN);
533
+ }
534
+
535
+ _onStartStreaming() {
536
+ Log.v(this.TAG, 'ManagedMediaSource onStartStreaming');
537
+ this._emitter.emit(MSEEvents.START_STREAMING);
538
+ }
539
+
540
+ _onEndStreaming() {
541
+ Log.v(this.TAG, 'ManagedMediaSource onEndStreaming');
542
+ this._emitter.emit(MSEEvents.END_STREAMING);
543
+ }
544
+
545
+ _onQualityChange() {
546
+ Log.v(this.TAG, 'ManagedMediaSource onQualityChange');
547
+ }
548
+
549
+ _onSourceEnded() {
550
+ // fired on endOfStream
551
+ Log.v(this.TAG, 'MediaSource onSourceEnded');
552
+ }
553
+
554
+ _onSourceClose() {
555
+ // fired on detaching from media element
556
+ Log.v(this.TAG, 'MediaSource onSourceClose');
557
+ if (this._mediaSource && this.e != null) {
558
+ this._mediaSource.removeEventListener('sourceopen', this.e.onSourceOpen);
559
+ this._mediaSource.removeEventListener('sourceended', this.e.onSourceEnded);
560
+ this._mediaSource.removeEventListener('sourceclose', this.e.onSourceClose);
561
+ if (this._useManagedMediaSource) {
562
+ this._mediaSource.removeEventListener('startstreaming', this.e.onStartStreaming);
563
+ this._mediaSource.removeEventListener('endstreaming', this.e.onEndStreaming);
564
+ this._mediaSource.removeEventListener('qualitychange', this.e.onQualityChange);
565
+ }
566
+ }
567
+ }
568
+
569
+ _hasPendingSegments() {
570
+ let ps = this._pendingSegments;
571
+ return ps.video.length > 0 || ps.audio.length > 0;
572
+ }
573
+
574
+ _hasPendingRemoveRanges() {
575
+ let prr = this._pendingRemoveRanges;
576
+ return prr.video.length > 0 || prr.audio.length > 0;
577
+ }
578
+
579
+ _onSourceBufferUpdateEnd() {
580
+ if (this._requireSetMediaDuration) {
581
+ this._updateMediaSourceDuration();
582
+ } else if (this._hasPendingRemoveRanges()) {
583
+ this._doRemoveRanges();
584
+ } else if (this._hasPendingSegments()) {
585
+ this._doAppendSegments();
586
+ } else if (this._hasPendingEos) {
587
+ this.endOfStream();
588
+ }
589
+ this._emitter.emit(MSEEvents.UPDATE_END);
590
+ }
591
+
592
+ _onSourceBufferError(e) {
593
+ Log.e(this.TAG, `SourceBuffer Error: ${e}`);
594
+ // this error might not always be fatal, just ignore it
595
+ }
596
+
597
+ }
598
+
599
+ export default MSEController;
@@ -0,0 +1,28 @@
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
+ enum MSEEvents {
20
+ ERROR = 'error',
21
+ SOURCE_OPEN = 'source_open',
22
+ UPDATE_END = 'update_end',
23
+ BUFFER_FULL = 'buffer_full',
24
+ START_STREAMING = 'start_streaming',
25
+ END_STREAMING = 'end_streaming',
26
+ };
27
+
28
+ export default MSEEvents;