@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,778 @@
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 MP4 from './mp4-generator.js';
21
+ import AAC from './aac-silent.js';
22
+ import Browser from '../utils/browser.js';
23
+ import { SampleInfo, MediaSegmentInfo, MediaSegmentInfoList } from '../core/media-segment-info.js';
24
+ import { IllegalStateException } from '../utils/exception.js';
25
+
26
+
27
+ // Fragmented mp4 remuxer
28
+ class MP4Remuxer {
29
+
30
+ constructor(config) {
31
+ this.TAG = 'MP4Remuxer';
32
+
33
+ this._config = config;
34
+ this._isLive = (config.isLive === true) ? true : false;
35
+
36
+ this._dtsBase = -1;
37
+ this._dtsBaseInited = false;
38
+ this._audioDtsBase = Infinity;
39
+ this._videoDtsBase = Infinity;
40
+ this._audioNextDts = undefined;
41
+ this._videoNextDts = undefined;
42
+ this._audioStashedLastSample = null;
43
+ this._videoStashedLastSample = null;
44
+
45
+ this._audioMeta = null;
46
+ this._videoMeta = null;
47
+
48
+ this._audioSegmentInfoList = new MediaSegmentInfoList('audio');
49
+ this._videoSegmentInfoList = new MediaSegmentInfoList('video');
50
+
51
+ this._onInitSegment = null;
52
+ this._onMediaSegment = null;
53
+
54
+ // Workaround for chrome < 50: Always force first sample as a Random Access Point in media segment
55
+ // see https://bugs.chromium.org/p/chromium/issues/detail?id=229412
56
+ this._forceFirstIDR = (Browser.chrome &&
57
+ (Browser.version.major < 50 ||
58
+ (Browser.version.major === 50 && Browser.version.build < 2661))) ? true : false;
59
+
60
+ // Workaround for IE11/Edge: Fill silent aac frame after keyframe-seeking
61
+ // Make audio beginDts equals with video beginDts, in order to fix seek freeze
62
+ this._fillSilentAfterSeek = (Browser.msedge || Browser.msie);
63
+
64
+ // While only FireFox supports 'audio/mp4, codecs="mp3"', use 'audio/mpeg' for chrome, safari, ...
65
+ this._mp3UseMpegAudio = !Browser.firefox;
66
+
67
+ this._fillAudioTimestampGap = this._config.fixAudioTimestampGap;
68
+ }
69
+
70
+ destroy() {
71
+ this._dtsBase = -1;
72
+ this._dtsBaseInited = false;
73
+ this._audioMeta = null;
74
+ this._videoMeta = null;
75
+ this._audioSegmentInfoList.clear();
76
+ this._audioSegmentInfoList = null;
77
+ this._videoSegmentInfoList.clear();
78
+ this._videoSegmentInfoList = null;
79
+ this._onInitSegment = null;
80
+ this._onMediaSegment = null;
81
+ }
82
+
83
+ bindDataSource(producer) {
84
+ producer.onDataAvailable = this.remux.bind(this);
85
+ producer.onTrackMetadata = this._onTrackMetadataReceived.bind(this);
86
+ return this;
87
+ }
88
+
89
+ /* prototype: function onInitSegment(type: string, initSegment: ArrayBuffer): void
90
+ InitSegment: {
91
+ type: string,
92
+ data: ArrayBuffer,
93
+ codec: string,
94
+ container: string
95
+ }
96
+ */
97
+ get onInitSegment() {
98
+ return this._onInitSegment;
99
+ }
100
+
101
+ set onInitSegment(callback) {
102
+ this._onInitSegment = callback;
103
+ }
104
+
105
+ /* prototype: function onMediaSegment(type: string, mediaSegment: MediaSegment): void
106
+ MediaSegment: {
107
+ type: string,
108
+ data: ArrayBuffer,
109
+ sampleCount: int32
110
+ info: MediaSegmentInfo
111
+ }
112
+ */
113
+ get onMediaSegment() {
114
+ return this._onMediaSegment;
115
+ }
116
+
117
+ set onMediaSegment(callback) {
118
+ this._onMediaSegment = callback;
119
+ }
120
+
121
+ insertDiscontinuity() {
122
+ this._audioNextDts = this._videoNextDts = undefined;
123
+ }
124
+
125
+ seek(originalDts) {
126
+ this._audioStashedLastSample = null;
127
+ this._videoStashedLastSample = null;
128
+ this._videoSegmentInfoList.clear();
129
+ this._audioSegmentInfoList.clear();
130
+ }
131
+
132
+ remux(audioTrack, videoTrack) {
133
+ if (!this._onMediaSegment) {
134
+ throw new IllegalStateException('MP4Remuxer: onMediaSegment callback must be specificed!');
135
+ }
136
+ if (!this._dtsBaseInited) {
137
+ this._calculateDtsBase(audioTrack, videoTrack);
138
+ }
139
+ if (videoTrack) {
140
+ this._remuxVideo(videoTrack);
141
+ }
142
+ if (audioTrack) {
143
+ this._remuxAudio(audioTrack);
144
+ }
145
+ }
146
+
147
+ _onTrackMetadataReceived(type, metadata) {
148
+ let metabox = null;
149
+
150
+ let container = 'mp4';
151
+ let codec = metadata.codec;
152
+
153
+ if (type === 'audio') {
154
+ this._audioMeta = metadata;
155
+ if (metadata.codec === 'mp3' && this._mp3UseMpegAudio) {
156
+ // 'audio/mpeg' for MP3 audio track
157
+ container = 'mpeg';
158
+ codec = '';
159
+ metabox = new Uint8Array();
160
+ } else {
161
+ // 'audio/mp4, codecs="codec"'
162
+ metabox = MP4.generateInitSegment(metadata);
163
+ }
164
+ } else if (type === 'video') {
165
+ this._videoMeta = metadata;
166
+ metabox = MP4.generateInitSegment(metadata);
167
+ } else {
168
+ return;
169
+ }
170
+
171
+ // dispatch metabox (Initialization Segment)
172
+ if (!this._onInitSegment) {
173
+ throw new IllegalStateException('MP4Remuxer: onInitSegment callback must be specified!');
174
+ }
175
+ this._onInitSegment(type, {
176
+ type: type,
177
+ data: metabox.buffer,
178
+ codec: codec,
179
+ container: `${type}/${container}`,
180
+ mediaDuration: metadata.duration // in timescale 1000 (milliseconds)
181
+ });
182
+ }
183
+
184
+ _calculateDtsBase(audioTrack, videoTrack) {
185
+ if (this._dtsBaseInited) {
186
+ return;
187
+ }
188
+
189
+ if (audioTrack && audioTrack.samples && audioTrack.samples.length) {
190
+ this._audioDtsBase = audioTrack.samples[0].dts;
191
+ }
192
+ if (videoTrack && videoTrack.samples && videoTrack.samples.length) {
193
+ this._videoDtsBase = videoTrack.samples[0].dts;
194
+ }
195
+
196
+ this._dtsBase = Math.min(this._audioDtsBase, this._videoDtsBase);
197
+ this._dtsBaseInited = true;
198
+ }
199
+
200
+ getTimestampBase() {
201
+ if (!this._dtsBaseInited) {
202
+ return undefined;
203
+ }
204
+ return this._dtsBase;
205
+ }
206
+
207
+ flushStashedSamples() {
208
+ let videoSample = this._videoStashedLastSample;
209
+ let audioSample = this._audioStashedLastSample;
210
+
211
+ let videoTrack = {
212
+ type: 'video',
213
+ id: 1,
214
+ sequenceNumber: 0,
215
+ samples: [],
216
+ length: 0
217
+ };
218
+
219
+ if (videoSample != null) {
220
+ videoTrack.samples.push(videoSample);
221
+ videoTrack.length = videoSample.length;
222
+ }
223
+
224
+ let audioTrack = {
225
+ type: 'audio',
226
+ id: 2,
227
+ sequenceNumber: 0,
228
+ samples: [],
229
+ length: 0
230
+ };
231
+
232
+ if (audioSample != null) {
233
+ audioTrack.samples.push(audioSample);
234
+ audioTrack.length = audioSample.length;
235
+ }
236
+
237
+ this._videoStashedLastSample = null;
238
+ this._audioStashedLastSample = null;
239
+
240
+ this._remuxVideo(videoTrack, true);
241
+ this._remuxAudio(audioTrack, true);
242
+ }
243
+
244
+ _remuxAudio(audioTrack, force) {
245
+ if (this._audioMeta == null) {
246
+ return;
247
+ }
248
+
249
+ let track = audioTrack;
250
+ let samples = track.samples;
251
+ let dtsCorrection = undefined;
252
+ let firstDts = -1, lastDts = -1, lastPts = -1;
253
+ let refSampleDuration = this._audioMeta.refSampleDuration;
254
+
255
+ let mpegRawTrack = this._audioMeta.codec === 'mp3' && this._mp3UseMpegAudio;
256
+ let firstSegmentAfterSeek = this._dtsBaseInited && this._audioNextDts === undefined;
257
+
258
+ let insertPrefixSilentFrame = false;
259
+
260
+ if (!samples || samples.length === 0) {
261
+ return;
262
+ }
263
+ if (samples.length === 1 && !force) {
264
+ // If [sample count in current batch] === 1 && (force != true)
265
+ // Ignore and keep in demuxer's queue
266
+ return;
267
+ } // else if (force === true) do remux
268
+
269
+ let offset = 0;
270
+ let mdatbox = null;
271
+ let mdatBytes = 0;
272
+
273
+ // calculate initial mdat size
274
+ if (mpegRawTrack) {
275
+ // for raw mpeg buffer
276
+ offset = 0;
277
+ mdatBytes = track.length;
278
+ } else {
279
+ // for fmp4 mdat box
280
+ offset = 8; // size + type
281
+ mdatBytes = 8 + track.length;
282
+ }
283
+
284
+
285
+ let lastSample = null;
286
+
287
+ // Pop the lastSample and waiting for stash
288
+ if (samples.length > 1) {
289
+ lastSample = samples.pop();
290
+ mdatBytes -= lastSample.length;
291
+ }
292
+
293
+ // Insert [stashed lastSample in the previous batch] to the front
294
+ if (this._audioStashedLastSample != null) {
295
+ let sample = this._audioStashedLastSample;
296
+ this._audioStashedLastSample = null;
297
+ samples.unshift(sample);
298
+ mdatBytes += sample.length;
299
+ }
300
+
301
+ // Stash the lastSample of current batch, waiting for next batch
302
+ if (lastSample != null) {
303
+ this._audioStashedLastSample = lastSample;
304
+ }
305
+
306
+
307
+ let firstSampleOriginalDts = samples[0].dts - this._dtsBase;
308
+
309
+ // calculate dtsCorrection
310
+ if (this._audioNextDts) {
311
+ dtsCorrection = firstSampleOriginalDts - this._audioNextDts;
312
+ } else { // this._audioNextDts == undefined
313
+ if (this._audioSegmentInfoList.isEmpty()) {
314
+ dtsCorrection = 0;
315
+ if (this._fillSilentAfterSeek && !this._videoSegmentInfoList.isEmpty()) {
316
+ if (this._audioMeta.originalCodec !== 'mp3') {
317
+ insertPrefixSilentFrame = true;
318
+ }
319
+ }
320
+ } else {
321
+ let lastSample = this._audioSegmentInfoList.getLastSampleBefore(firstSampleOriginalDts);
322
+ if (lastSample != null) {
323
+ let distance = (firstSampleOriginalDts - (lastSample.originalDts + lastSample.duration));
324
+ if (distance <= 3) {
325
+ distance = 0;
326
+ }
327
+ let expectedDts = lastSample.dts + lastSample.duration + distance;
328
+ dtsCorrection = firstSampleOriginalDts - expectedDts;
329
+ } else { // lastSample == null, cannot found
330
+ dtsCorrection = 0;
331
+ }
332
+ }
333
+ }
334
+
335
+ if (insertPrefixSilentFrame) {
336
+ // align audio segment beginDts to match with current video segment's beginDts
337
+ let firstSampleDts = firstSampleOriginalDts - dtsCorrection;
338
+ let videoSegment = this._videoSegmentInfoList.getLastSegmentBefore(firstSampleOriginalDts);
339
+ if (videoSegment != null && videoSegment.beginDts < firstSampleDts) {
340
+ let silentUnit = AAC.getSilentFrame(this._audioMeta.originalCodec, this._audioMeta.channelCount);
341
+ if (silentUnit) {
342
+ let dts = videoSegment.beginDts;
343
+ let silentFrameDuration = firstSampleDts - videoSegment.beginDts;
344
+ Log.v(this.TAG, `InsertPrefixSilentAudio: dts: ${dts}, duration: ${silentFrameDuration}`);
345
+ samples.unshift({ unit: silentUnit, dts: dts, pts: dts });
346
+ mdatBytes += silentUnit.byteLength;
347
+ } // silentUnit == null: Cannot generate, skip
348
+ } else {
349
+ insertPrefixSilentFrame = false;
350
+ }
351
+ }
352
+
353
+ let mp4Samples = [];
354
+
355
+ // Correct dts for each sample, and calculate sample duration. Then output to mp4Samples
356
+ for (let i = 0; i < samples.length; i++) {
357
+ let sample = samples[i];
358
+ let unit = sample.unit;
359
+ let originalDts = sample.dts - this._dtsBase;
360
+ let dts = originalDts;
361
+ let needFillSilentFrames = false;
362
+ let silentFrames = null;
363
+ let sampleDuration = 0;
364
+
365
+ if (originalDts < -0.001) {
366
+ continue; //pass the first sample with the invalid dts
367
+ }
368
+
369
+ if (this._audioMeta.codec !== 'mp3' && refSampleDuration != null) {
370
+ // for AAC codec, we need to keep dts increase based on refSampleDuration
371
+ let curRefDts = originalDts;
372
+ const maxAudioFramesDrift = 3;
373
+ if (this._audioNextDts) {
374
+ curRefDts = this._audioNextDts;
375
+ }
376
+
377
+ dtsCorrection = originalDts - curRefDts;
378
+ if (dtsCorrection <= -maxAudioFramesDrift * refSampleDuration) {
379
+ // If we're overlapping by more than maxAudioFramesDrift number of frame, drop this sample
380
+ Log.w(this.TAG, `Dropping 1 audio frame (originalDts: ${originalDts} ms ,curRefDts: ${curRefDts} ms) due to dtsCorrection: ${dtsCorrection} ms overlap.`);
381
+ continue;
382
+ }
383
+ else if (dtsCorrection >= maxAudioFramesDrift * refSampleDuration && this._fillAudioTimestampGap && !Browser.safari) {
384
+ // Silent frame generation, if large timestamp gap detected && config.fixAudioTimestampGap
385
+ needFillSilentFrames = true;
386
+ // We need to insert silent frames to fill timestamp gap
387
+ let frameCount = Math.floor(dtsCorrection / refSampleDuration);
388
+ Log.w(this.TAG, 'Large audio timestamp gap detected, may cause AV sync to drift. ' +
389
+ 'Silent frames will be generated to avoid unsync.\n' +
390
+ `originalDts: ${originalDts} ms, curRefDts: ${curRefDts} ms, ` +
391
+ `dtsCorrection: ${Math.round(dtsCorrection)} ms, generate: ${frameCount} frames`);
392
+
393
+
394
+ dts = Math.floor(curRefDts);
395
+ sampleDuration = Math.floor(curRefDts + refSampleDuration) - dts;
396
+
397
+ let silentUnit = AAC.getSilentFrame(this._audioMeta.originalCodec, this._audioMeta.channelCount);
398
+ if (silentUnit == null) {
399
+ Log.w(this.TAG, 'Unable to generate silent frame for ' +
400
+ `${this._audioMeta.originalCodec} with ${this._audioMeta.channelCount} channels, repeat last frame`);
401
+ // Repeat last frame
402
+ silentUnit = unit;
403
+ }
404
+ silentFrames = [];
405
+
406
+ for (let j = 0; j < frameCount; j++) {
407
+ curRefDts = curRefDts + refSampleDuration;
408
+ let intDts = Math.floor(curRefDts); // change to integer
409
+ let intDuration = Math.floor(curRefDts + refSampleDuration) - intDts;
410
+ let frame = {
411
+ dts: intDts,
412
+ pts: intDts,
413
+ cts: 0,
414
+ unit: silentUnit,
415
+ size: silentUnit.byteLength,
416
+ duration: intDuration, // wait for next sample
417
+ originalDts: originalDts,
418
+ flags: {
419
+ isLeading: 0,
420
+ dependsOn: 1,
421
+ isDependedOn: 0,
422
+ hasRedundancy: 0
423
+ }
424
+ };
425
+ silentFrames.push(frame);
426
+ mdatBytes += frame.size;
427
+
428
+ }
429
+
430
+ this._audioNextDts = curRefDts + refSampleDuration;
431
+
432
+ } else {
433
+
434
+ dts = Math.floor(curRefDts);
435
+ sampleDuration = Math.floor(curRefDts + refSampleDuration) - dts;
436
+ this._audioNextDts = curRefDts + refSampleDuration;
437
+
438
+ }
439
+ } else {
440
+ // keep the original dts calculate algorithm for mp3
441
+ dts = originalDts - dtsCorrection;
442
+
443
+
444
+ if (i !== samples.length - 1) {
445
+ let nextDts = samples[i + 1].dts - this._dtsBase - dtsCorrection;
446
+ sampleDuration = nextDts - dts;
447
+ } else { // the last sample
448
+ if (lastSample != null) { // use stashed sample's dts to calculate sample duration
449
+ let nextDts = lastSample.dts - this._dtsBase - dtsCorrection;
450
+ sampleDuration = nextDts - dts;
451
+ } else if (mp4Samples.length >= 1) { // use second last sample duration
452
+ sampleDuration = mp4Samples[mp4Samples.length - 1].duration;
453
+ } else { // the only one sample, use reference sample duration
454
+ sampleDuration = Math.floor(refSampleDuration);
455
+ }
456
+ }
457
+ this._audioNextDts = dts + sampleDuration;
458
+ }
459
+
460
+ if (firstDts === -1) {
461
+ firstDts = dts;
462
+ }
463
+ mp4Samples.push({
464
+ dts: dts,
465
+ pts: dts,
466
+ cts: 0,
467
+ unit: sample.unit,
468
+ size: sample.unit.byteLength,
469
+ duration: sampleDuration,
470
+ originalDts: originalDts,
471
+ flags: {
472
+ isLeading: 0,
473
+ dependsOn: 1,
474
+ isDependedOn: 0,
475
+ hasRedundancy: 0
476
+ }
477
+ });
478
+
479
+ if (needFillSilentFrames) {
480
+ // Silent frames should be inserted after wrong-duration frame
481
+ mp4Samples.push.apply(mp4Samples, silentFrames);
482
+ }
483
+ }
484
+
485
+ if (mp4Samples.length === 0) {
486
+ //no samples need to remux
487
+ track.samples = [];
488
+ track.length = 0;
489
+ return;
490
+ }
491
+
492
+ // allocate mdatbox
493
+ if (mpegRawTrack) {
494
+ // allocate for raw mpeg buffer
495
+ mdatbox = new Uint8Array(mdatBytes);
496
+ } else {
497
+ // allocate for fmp4 mdat box
498
+ mdatbox = new Uint8Array(mdatBytes);
499
+ // size field
500
+ mdatbox[0] = (mdatBytes >>> 24) & 0xFF;
501
+ mdatbox[1] = (mdatBytes >>> 16) & 0xFF;
502
+ mdatbox[2] = (mdatBytes >>> 8) & 0xFF;
503
+ mdatbox[3] = (mdatBytes) & 0xFF;
504
+ // type field (fourCC)
505
+ mdatbox.set(MP4.types.mdat, 4);
506
+ }
507
+
508
+ // Write samples into mdatbox
509
+ for (let i = 0; i < mp4Samples.length; i++) {
510
+ let unit = mp4Samples[i].unit;
511
+ mdatbox.set(unit, offset);
512
+ offset += unit.byteLength;
513
+ }
514
+
515
+ let latest = mp4Samples[mp4Samples.length - 1];
516
+ lastDts = latest.dts + latest.duration;
517
+ //this._audioNextDts = lastDts;
518
+
519
+ // fill media segment info & add to info list
520
+ let info = new MediaSegmentInfo();
521
+ info.beginDts = firstDts;
522
+ info.endDts = lastDts;
523
+ info.beginPts = firstDts;
524
+ info.endPts = lastDts;
525
+ info.originalBeginDts = mp4Samples[0].originalDts;
526
+ info.originalEndDts = latest.originalDts + latest.duration;
527
+ info.firstSample = new SampleInfo(mp4Samples[0].dts,
528
+ mp4Samples[0].pts,
529
+ mp4Samples[0].duration,
530
+ mp4Samples[0].originalDts,
531
+ false);
532
+ info.lastSample = new SampleInfo(latest.dts,
533
+ latest.pts,
534
+ latest.duration,
535
+ latest.originalDts,
536
+ false);
537
+ if (!this._isLive) {
538
+ this._audioSegmentInfoList.append(info);
539
+ }
540
+
541
+ track.samples = mp4Samples;
542
+ track.sequenceNumber++;
543
+
544
+ let moofbox = null;
545
+
546
+ if (mpegRawTrack) {
547
+ // Generate empty buffer, because useless for raw mpeg
548
+ moofbox = new Uint8Array();
549
+ } else {
550
+ // Generate moof for fmp4 segment
551
+ moofbox = MP4.moof(track, firstDts);
552
+ }
553
+
554
+ track.samples = [];
555
+ track.length = 0;
556
+
557
+ let segment = {
558
+ type: 'audio',
559
+ data: this._mergeBoxes(moofbox, mdatbox).buffer,
560
+ sampleCount: mp4Samples.length,
561
+ info: info
562
+ };
563
+
564
+ if (mpegRawTrack && firstSegmentAfterSeek) {
565
+ // For MPEG audio stream in MSE, if seeking occurred, before appending new buffer
566
+ // We need explicitly set timestampOffset to the desired point in timeline for mpeg SourceBuffer.
567
+ segment.timestampOffset = firstDts;
568
+ }
569
+
570
+ this._onMediaSegment('audio', segment);
571
+ }
572
+
573
+ _remuxVideo(videoTrack, force) {
574
+ if (this._videoMeta == null) {
575
+ return;
576
+ }
577
+
578
+ let track = videoTrack;
579
+ let samples = track.samples;
580
+ let dtsCorrection = undefined;
581
+ let firstDts = -1, lastDts = -1;
582
+ let firstPts = -1, lastPts = -1;
583
+
584
+ if (!samples || samples.length === 0) {
585
+ return;
586
+ }
587
+ if (samples.length === 1 && !force) {
588
+ // If [sample count in current batch] === 1 && (force != true)
589
+ // Ignore and keep in demuxer's queue
590
+ return;
591
+ } // else if (force === true) do remux
592
+
593
+ let offset = 8;
594
+ let mdatbox = null;
595
+ let mdatBytes = 8 + videoTrack.length;
596
+
597
+
598
+ let lastSample = null;
599
+
600
+ // Pop the lastSample and waiting for stash
601
+ if (samples.length > 1) {
602
+ lastSample = samples.pop();
603
+ mdatBytes -= lastSample.length;
604
+ }
605
+
606
+ // Insert [stashed lastSample in the previous batch] to the front
607
+ if (this._videoStashedLastSample != null) {
608
+ let sample = this._videoStashedLastSample;
609
+ this._videoStashedLastSample = null;
610
+ samples.unshift(sample);
611
+ mdatBytes += sample.length;
612
+ }
613
+
614
+ // Stash the lastSample of current batch, waiting for next batch
615
+ if (lastSample != null) {
616
+ this._videoStashedLastSample = lastSample;
617
+ }
618
+
619
+
620
+ let firstSampleOriginalDts = samples[0].dts - this._dtsBase;
621
+
622
+ // calculate dtsCorrection
623
+ if (this._videoNextDts) {
624
+ dtsCorrection = firstSampleOriginalDts - this._videoNextDts;
625
+ } else { // this._videoNextDts == undefined
626
+ if (this._videoSegmentInfoList.isEmpty()) {
627
+ dtsCorrection = 0;
628
+ } else {
629
+ let lastSample = this._videoSegmentInfoList.getLastSampleBefore(firstSampleOriginalDts);
630
+ if (lastSample != null) {
631
+ let distance = (firstSampleOriginalDts - (lastSample.originalDts + lastSample.duration));
632
+ if (distance <= 3) {
633
+ distance = 0;
634
+ }
635
+ let expectedDts = lastSample.dts + lastSample.duration + distance;
636
+ dtsCorrection = firstSampleOriginalDts - expectedDts;
637
+ } else { // lastSample == null, cannot found
638
+ dtsCorrection = 0;
639
+ }
640
+ }
641
+ }
642
+
643
+ let info = new MediaSegmentInfo();
644
+ let mp4Samples = [];
645
+
646
+ // Correct dts for each sample, and calculate sample duration. Then output to mp4Samples
647
+ for (let i = 0; i < samples.length; i++) {
648
+ let sample = samples[i];
649
+ let originalDts = sample.dts - this._dtsBase;
650
+ let isKeyframe = sample.isKeyframe;
651
+ let dts = originalDts - dtsCorrection;
652
+ let cts = sample.cts;
653
+ let pts = dts + cts;
654
+
655
+ if (firstDts === -1) {
656
+ firstDts = dts;
657
+ firstPts = pts;
658
+ }
659
+
660
+ let sampleDuration = 0;
661
+
662
+ if (i !== samples.length - 1) {
663
+ let nextDts = samples[i + 1].dts - this._dtsBase - dtsCorrection;
664
+ sampleDuration = nextDts - dts;
665
+ } else { // the last sample
666
+ if (lastSample != null) { // use stashed sample's dts to calculate sample duration
667
+ let nextDts = lastSample.dts - this._dtsBase - dtsCorrection;
668
+ sampleDuration = nextDts - dts;
669
+ } else if (mp4Samples.length >= 1) { // use second last sample duration
670
+ sampleDuration = mp4Samples[mp4Samples.length - 1].duration;
671
+ } else { // the only one sample, use reference sample duration
672
+ sampleDuration = Math.floor(this._videoMeta.refSampleDuration);
673
+ }
674
+ }
675
+
676
+ if (isKeyframe) {
677
+ let syncPoint = new SampleInfo(dts, pts, sampleDuration, sample.dts, true);
678
+ syncPoint.fileposition = sample.fileposition;
679
+ info.appendSyncPoint(syncPoint);
680
+ }
681
+
682
+ mp4Samples.push({
683
+ dts: dts,
684
+ pts: pts,
685
+ cts: cts,
686
+ units: sample.units,
687
+ size: sample.length,
688
+ isKeyframe: isKeyframe,
689
+ duration: sampleDuration,
690
+ originalDts: originalDts,
691
+ flags: {
692
+ isLeading: 0,
693
+ dependsOn: isKeyframe ? 2 : 1,
694
+ isDependedOn: isKeyframe ? 1 : 0,
695
+ hasRedundancy: 0,
696
+ isNonSync: isKeyframe ? 0 : 1
697
+ }
698
+ });
699
+ }
700
+
701
+ // allocate mdatbox
702
+ mdatbox = new Uint8Array(mdatBytes);
703
+ mdatbox[0] = (mdatBytes >>> 24) & 0xFF;
704
+ mdatbox[1] = (mdatBytes >>> 16) & 0xFF;
705
+ mdatbox[2] = (mdatBytes >>> 8) & 0xFF;
706
+ mdatbox[3] = (mdatBytes) & 0xFF;
707
+ mdatbox.set(MP4.types.mdat, 4);
708
+
709
+ // Write samples into mdatbox
710
+ for (let i = 0; i < mp4Samples.length; i++) {
711
+ let units = mp4Samples[i].units;
712
+ while (units.length) {
713
+ let unit = units.shift();
714
+ let data = unit.data;
715
+ mdatbox.set(data, offset);
716
+ offset += data.byteLength;
717
+ }
718
+ }
719
+
720
+ let latest = mp4Samples[mp4Samples.length - 1];
721
+ lastDts = latest.dts + latest.duration;
722
+ lastPts = latest.pts + latest.duration;
723
+ this._videoNextDts = lastDts;
724
+
725
+ // fill media segment info & add to info list
726
+ info.beginDts = firstDts;
727
+ info.endDts = lastDts;
728
+ info.beginPts = firstPts;
729
+ info.endPts = lastPts;
730
+ info.originalBeginDts = mp4Samples[0].originalDts;
731
+ info.originalEndDts = latest.originalDts + latest.duration;
732
+ info.firstSample = new SampleInfo(mp4Samples[0].dts,
733
+ mp4Samples[0].pts,
734
+ mp4Samples[0].duration,
735
+ mp4Samples[0].originalDts,
736
+ mp4Samples[0].isKeyframe);
737
+ info.lastSample = new SampleInfo(latest.dts,
738
+ latest.pts,
739
+ latest.duration,
740
+ latest.originalDts,
741
+ latest.isKeyframe);
742
+ if (!this._isLive) {
743
+ this._videoSegmentInfoList.append(info);
744
+ }
745
+
746
+ track.samples = mp4Samples;
747
+ track.sequenceNumber++;
748
+
749
+ // workaround for chrome < 50: force first sample as a random access point
750
+ // see https://bugs.chromium.org/p/chromium/issues/detail?id=229412
751
+ if (this._forceFirstIDR) {
752
+ let flags = mp4Samples[0].flags;
753
+ flags.dependsOn = 2;
754
+ flags.isNonSync = 0;
755
+ }
756
+
757
+ let moofbox = MP4.moof(track, firstDts);
758
+ track.samples = [];
759
+ track.length = 0;
760
+
761
+ this._onMediaSegment('video', {
762
+ type: 'video',
763
+ data: this._mergeBoxes(moofbox, mdatbox).buffer,
764
+ sampleCount: mp4Samples.length,
765
+ info: info
766
+ });
767
+ }
768
+
769
+ _mergeBoxes(moof, mdat) {
770
+ let result = new Uint8Array(moof.byteLength + mdat.byteLength);
771
+ result.set(moof, 0);
772
+ result.set(mdat, moof.byteLength);
773
+ return result;
774
+ }
775
+
776
+ }
777
+
778
+ export default MP4Remuxer;