@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,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;
|