@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,286 @@
|
|
|
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 LoggingControl from '../utils/logging-control.js';
|
|
21
|
+
import Polyfill from '../utils/polyfill.js';
|
|
22
|
+
import TransmuxingController from './transmuxing-controller.js';
|
|
23
|
+
import TransmuxingEvents from './transmuxing-events';
|
|
24
|
+
|
|
25
|
+
/* post message to worker:
|
|
26
|
+
data: {
|
|
27
|
+
cmd: string
|
|
28
|
+
param: any
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
receive message from worker:
|
|
32
|
+
data: {
|
|
33
|
+
msg: string,
|
|
34
|
+
data: any
|
|
35
|
+
}
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
let TransmuxingWorker = function (self) {
|
|
39
|
+
|
|
40
|
+
let TAG = 'TransmuxingWorker';
|
|
41
|
+
let controller = null;
|
|
42
|
+
let logcatListener = onLogcatCallback.bind(this);
|
|
43
|
+
|
|
44
|
+
Polyfill.install();
|
|
45
|
+
|
|
46
|
+
self.addEventListener('message', function (e) {
|
|
47
|
+
switch (e.data.cmd) {
|
|
48
|
+
case 'init':
|
|
49
|
+
controller = new TransmuxingController(e.data.param[0], e.data.param[1]);
|
|
50
|
+
controller.on(TransmuxingEvents.IO_ERROR, onIOError.bind(this));
|
|
51
|
+
controller.on(TransmuxingEvents.DEMUX_ERROR, onDemuxError.bind(this));
|
|
52
|
+
controller.on(TransmuxingEvents.INIT_SEGMENT, onInitSegment.bind(this));
|
|
53
|
+
controller.on(TransmuxingEvents.MEDIA_SEGMENT, onMediaSegment.bind(this));
|
|
54
|
+
controller.on(TransmuxingEvents.LOADING_COMPLETE, onLoadingComplete.bind(this));
|
|
55
|
+
controller.on(TransmuxingEvents.RECOVERED_EARLY_EOF, onRecoveredEarlyEof.bind(this));
|
|
56
|
+
controller.on(TransmuxingEvents.MEDIA_INFO, onMediaInfo.bind(this));
|
|
57
|
+
controller.on(TransmuxingEvents.METADATA_ARRIVED, onMetaDataArrived.bind(this));
|
|
58
|
+
controller.on(TransmuxingEvents.SCRIPTDATA_ARRIVED, onScriptDataArrived.bind(this));
|
|
59
|
+
controller.on(TransmuxingEvents.TIMED_ID3_METADATA_ARRIVED, onTimedID3MetadataArrived.bind(this));
|
|
60
|
+
controller.on(TransmuxingEvents.PGS_SUBTITLE_ARRIVED, onPGSSubtitleDataArrived.bind(this));
|
|
61
|
+
controller.on(TransmuxingEvents.SYNCHRONOUS_KLV_METADATA_ARRIVED, onSynchronousKLVMetadataArrived.bind(this));
|
|
62
|
+
controller.on(TransmuxingEvents.ASYNCHRONOUS_KLV_METADATA_ARRIVED, onAsynchronousKLVMetadataArrived.bind(this));
|
|
63
|
+
controller.on(TransmuxingEvents.SMPTE2038_METADATA_ARRIVED, onSMPTE2038MetadataArrived.bind(this));
|
|
64
|
+
controller.on(TransmuxingEvents.SEI_ARRIVED, onSEIArrived.bind(this));
|
|
65
|
+
controller.on(TransmuxingEvents.SCTE35_METADATA_ARRIVED, onSCTE35MetadataArrived.bind(this));
|
|
66
|
+
controller.on(TransmuxingEvents.PES_PRIVATE_DATA_DESCRIPTOR, onPESPrivateDataDescriptor.bind(this));
|
|
67
|
+
controller.on(TransmuxingEvents.PES_PRIVATE_DATA_ARRIVED, onPESPrivateDataArrived.bind(this));
|
|
68
|
+
controller.on(TransmuxingEvents.STATISTICS_INFO, onStatisticsInfo.bind(this));
|
|
69
|
+
controller.on(TransmuxingEvents.RECOMMEND_SEEKPOINT, onRecommendSeekpoint.bind(this));
|
|
70
|
+
break;
|
|
71
|
+
case 'destroy':
|
|
72
|
+
if (controller) {
|
|
73
|
+
controller.destroy();
|
|
74
|
+
controller = null;
|
|
75
|
+
}
|
|
76
|
+
self.postMessage({msg: 'destroyed'});
|
|
77
|
+
break;
|
|
78
|
+
case 'start':
|
|
79
|
+
controller.start();
|
|
80
|
+
break;
|
|
81
|
+
case 'stop':
|
|
82
|
+
controller.stop();
|
|
83
|
+
break;
|
|
84
|
+
case 'seek':
|
|
85
|
+
controller.seek(e.data.param);
|
|
86
|
+
break;
|
|
87
|
+
case 'pause':
|
|
88
|
+
controller.pause();
|
|
89
|
+
break;
|
|
90
|
+
case 'resume':
|
|
91
|
+
controller.resume();
|
|
92
|
+
break;
|
|
93
|
+
case 'logging_config': {
|
|
94
|
+
let config = e.data.param;
|
|
95
|
+
LoggingControl.applyConfig(config);
|
|
96
|
+
|
|
97
|
+
if (config.enableCallback === true) {
|
|
98
|
+
LoggingControl.addLogListener(logcatListener);
|
|
99
|
+
} else {
|
|
100
|
+
LoggingControl.removeLogListener(logcatListener);
|
|
101
|
+
}
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
function onInitSegment(type, initSegment) {
|
|
108
|
+
let obj = {
|
|
109
|
+
msg: TransmuxingEvents.INIT_SEGMENT,
|
|
110
|
+
data: {
|
|
111
|
+
type: type,
|
|
112
|
+
data: initSegment
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
self.postMessage(obj, [initSegment.data]); // data: ArrayBuffer
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function onMediaSegment(type, mediaSegment) {
|
|
119
|
+
let obj = {
|
|
120
|
+
msg: TransmuxingEvents.MEDIA_SEGMENT,
|
|
121
|
+
data: {
|
|
122
|
+
type: type,
|
|
123
|
+
data: mediaSegment
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
self.postMessage(obj, [mediaSegment.data]); // data: ArrayBuffer
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function onLoadingComplete() {
|
|
130
|
+
let obj = {
|
|
131
|
+
msg: TransmuxingEvents.LOADING_COMPLETE
|
|
132
|
+
};
|
|
133
|
+
self.postMessage(obj);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function onRecoveredEarlyEof() {
|
|
137
|
+
let obj = {
|
|
138
|
+
msg: TransmuxingEvents.RECOVERED_EARLY_EOF
|
|
139
|
+
};
|
|
140
|
+
self.postMessage(obj);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function onMediaInfo(mediaInfo) {
|
|
144
|
+
let obj = {
|
|
145
|
+
msg: TransmuxingEvents.MEDIA_INFO,
|
|
146
|
+
data: mediaInfo
|
|
147
|
+
};
|
|
148
|
+
self.postMessage(obj);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function onMetaDataArrived(metadata) {
|
|
152
|
+
let obj = {
|
|
153
|
+
msg: TransmuxingEvents.METADATA_ARRIVED,
|
|
154
|
+
data: metadata
|
|
155
|
+
};
|
|
156
|
+
self.postMessage(obj);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function onScriptDataArrived(data) {
|
|
160
|
+
let obj = {
|
|
161
|
+
msg: TransmuxingEvents.SCRIPTDATA_ARRIVED,
|
|
162
|
+
data: data
|
|
163
|
+
};
|
|
164
|
+
self.postMessage(obj);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function onTimedID3MetadataArrived (data) {
|
|
168
|
+
let obj = {
|
|
169
|
+
msg: TransmuxingEvents.TIMED_ID3_METADATA_ARRIVED,
|
|
170
|
+
data: data
|
|
171
|
+
};
|
|
172
|
+
self.postMessage(obj);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function onPGSSubtitleDataArrived (data) {
|
|
176
|
+
let obj = {
|
|
177
|
+
msg: TransmuxingEvents.PGS_SUBTITLE_ARRIVED,
|
|
178
|
+
data: data
|
|
179
|
+
};
|
|
180
|
+
self.postMessage(obj);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function onSynchronousKLVMetadataArrived (data) {
|
|
184
|
+
let obj = {
|
|
185
|
+
msg: TransmuxingEvents.SYNCHRONOUS_KLV_METADATA_ARRIVED,
|
|
186
|
+
data: data
|
|
187
|
+
};
|
|
188
|
+
self.postMessage(obj);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function onAsynchronousKLVMetadataArrived (data) {
|
|
192
|
+
let obj = {
|
|
193
|
+
msg: TransmuxingEvents.ASYNCHRONOUS_KLV_METADATA_ARRIVED,
|
|
194
|
+
data: data
|
|
195
|
+
};
|
|
196
|
+
self.postMessage(obj);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function onSMPTE2038MetadataArrived (data) {
|
|
200
|
+
let obj = {
|
|
201
|
+
msg: TransmuxingEvents.SMPTE2038_METADATA_ARRIVED,
|
|
202
|
+
data: data
|
|
203
|
+
};
|
|
204
|
+
self.postMessage(obj);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function onSEIArrived (data) {
|
|
208
|
+
let obj = {
|
|
209
|
+
msg: TransmuxingEvents.SEI_ARRIVED,
|
|
210
|
+
data: data
|
|
211
|
+
};
|
|
212
|
+
self.postMessage(obj);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function onSCTE35MetadataArrived (data) {
|
|
216
|
+
let obj = {
|
|
217
|
+
msg: TransmuxingEvents.SCTE35_METADATA_ARRIVED,
|
|
218
|
+
data: data
|
|
219
|
+
};
|
|
220
|
+
self.postMessage(obj);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function onPESPrivateDataDescriptor(data) {
|
|
224
|
+
let obj = {
|
|
225
|
+
msg: TransmuxingEvents.PES_PRIVATE_DATA_DESCRIPTOR,
|
|
226
|
+
data: data
|
|
227
|
+
};
|
|
228
|
+
self.postMessage(obj);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function onPESPrivateDataArrived(data) {
|
|
232
|
+
let obj = {
|
|
233
|
+
msg: TransmuxingEvents.PES_PRIVATE_DATA_ARRIVED,
|
|
234
|
+
data: data
|
|
235
|
+
};
|
|
236
|
+
self.postMessage(obj);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function onStatisticsInfo(statInfo) {
|
|
240
|
+
let obj = {
|
|
241
|
+
msg: TransmuxingEvents.STATISTICS_INFO,
|
|
242
|
+
data: statInfo
|
|
243
|
+
};
|
|
244
|
+
self.postMessage(obj);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function onIOError(type, info) {
|
|
248
|
+
self.postMessage({
|
|
249
|
+
msg: TransmuxingEvents.IO_ERROR,
|
|
250
|
+
data: {
|
|
251
|
+
type: type,
|
|
252
|
+
info: info
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function onDemuxError(type, info) {
|
|
258
|
+
self.postMessage({
|
|
259
|
+
msg: TransmuxingEvents.DEMUX_ERROR,
|
|
260
|
+
data: {
|
|
261
|
+
type: type,
|
|
262
|
+
info: info
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function onRecommendSeekpoint(milliseconds) {
|
|
268
|
+
self.postMessage({
|
|
269
|
+
msg: TransmuxingEvents.RECOMMEND_SEEKPOINT,
|
|
270
|
+
data: milliseconds
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function onLogcatCallback(type, str) {
|
|
275
|
+
self.postMessage({
|
|
276
|
+
msg: 'logcat_callback',
|
|
277
|
+
data: {
|
|
278
|
+
type: type,
|
|
279
|
+
logcat: str
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
export default TransmuxingWorker;
|
package/src/demux/aac.ts
ADDED
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
import Log from "../utils/logger";
|
|
2
|
+
import ExpGolomb from "./exp-golomb";
|
|
3
|
+
import { MPEG4AudioObjectTypes, MPEG4SamplingFrequencies, MPEG4SamplingFrequencyIndex } from "./mpeg4-audio";
|
|
4
|
+
|
|
5
|
+
export class AACFrame {
|
|
6
|
+
audio_object_type: MPEG4AudioObjectTypes;
|
|
7
|
+
sampling_freq_index: MPEG4SamplingFrequencyIndex;
|
|
8
|
+
sampling_frequency: number;
|
|
9
|
+
channel_config: number;
|
|
10
|
+
|
|
11
|
+
data: Uint8Array;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class LOASAACFrame extends AACFrame {
|
|
15
|
+
other_data_present: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class AACADTSParser {
|
|
19
|
+
|
|
20
|
+
private readonly TAG: string = "AACADTSParser";
|
|
21
|
+
|
|
22
|
+
private data_: Uint8Array;
|
|
23
|
+
private current_syncword_offset_: number;
|
|
24
|
+
private eof_flag_: boolean;
|
|
25
|
+
private has_last_incomplete_data: boolean;
|
|
26
|
+
|
|
27
|
+
public constructor(data: Uint8Array) {
|
|
28
|
+
this.data_ = data;
|
|
29
|
+
this.current_syncword_offset_ = this.findNextSyncwordOffset(0);
|
|
30
|
+
if (this.eof_flag_) {
|
|
31
|
+
Log.e(this.TAG, `Could not found ADTS syncword until payload end`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private findNextSyncwordOffset(syncword_offset: number): number {
|
|
36
|
+
let i = syncword_offset;
|
|
37
|
+
let data = this.data_;
|
|
38
|
+
|
|
39
|
+
while (true) {
|
|
40
|
+
if (i + 7 >= data.byteLength) {
|
|
41
|
+
this.eof_flag_ = true;
|
|
42
|
+
return data.byteLength;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// search 12-bit 0xFFF syncword
|
|
46
|
+
let syncword = ((data[i + 0] << 8) | data[i + 1]) >>> 4;
|
|
47
|
+
if (syncword === 0xFFF) {
|
|
48
|
+
return i;
|
|
49
|
+
} else {
|
|
50
|
+
i++;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public readNextAACFrame(): AACFrame | null {
|
|
56
|
+
let data = this.data_;
|
|
57
|
+
let aac_frame: AACFrame = null;
|
|
58
|
+
|
|
59
|
+
while (aac_frame == null) {
|
|
60
|
+
if (this.eof_flag_) {
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let syncword_offset = this.current_syncword_offset_;
|
|
65
|
+
let offset = syncword_offset;
|
|
66
|
+
|
|
67
|
+
// adts_fixed_header()
|
|
68
|
+
// syncword 0xFFF: 12-bit
|
|
69
|
+
let ID = (data[offset + 1] & 0x08) >>> 3;
|
|
70
|
+
let layer = (data[offset + 1] & 0x06) >>> 1;
|
|
71
|
+
let protection_absent = data[offset + 1] & 0x01;
|
|
72
|
+
let profile = (data[offset + 2] & 0xC0) >>> 6;
|
|
73
|
+
let sampling_frequency_index = (data[offset + 2] & 0x3C) >>> 2;
|
|
74
|
+
let channel_configuration = ((data[offset + 2] & 0x01) << 2)
|
|
75
|
+
| ((data[offset + 3] & 0xC0) >>> 6);
|
|
76
|
+
|
|
77
|
+
// adts_variable_header()
|
|
78
|
+
let aac_frame_length = ((data[offset + 3] & 0x03) << 11)
|
|
79
|
+
| (data[offset + 4] << 3)
|
|
80
|
+
| ((data[offset + 5] & 0xE0) >>> 5);
|
|
81
|
+
let number_of_raw_data_blocks_in_frame = data[offset + 6] & 0x03;
|
|
82
|
+
|
|
83
|
+
if (offset + aac_frame_length > this.data_.byteLength) {
|
|
84
|
+
// data not enough for extracting last sample
|
|
85
|
+
this.eof_flag_ = true;
|
|
86
|
+
this.has_last_incomplete_data = true;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let adts_header_length = (protection_absent === 1) ? 7 : 9;
|
|
91
|
+
let adts_frame_payload_length = aac_frame_length - adts_header_length;
|
|
92
|
+
|
|
93
|
+
offset += adts_header_length;
|
|
94
|
+
|
|
95
|
+
let next_syncword_offset = this.findNextSyncwordOffset(offset + adts_frame_payload_length);
|
|
96
|
+
this.current_syncword_offset_ = next_syncword_offset;
|
|
97
|
+
|
|
98
|
+
if ((ID !== 0 && ID !== 1) || layer !== 0) {
|
|
99
|
+
// invalid adts frame ?
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let frame_data = data.subarray(offset, offset + adts_frame_payload_length);
|
|
104
|
+
|
|
105
|
+
aac_frame = new AACFrame();
|
|
106
|
+
aac_frame.audio_object_type = (profile + 1) as MPEG4AudioObjectTypes;
|
|
107
|
+
aac_frame.sampling_freq_index = sampling_frequency_index as MPEG4SamplingFrequencyIndex;
|
|
108
|
+
aac_frame.sampling_frequency = MPEG4SamplingFrequencies[sampling_frequency_index];
|
|
109
|
+
aac_frame.channel_config = channel_configuration;
|
|
110
|
+
aac_frame.data = frame_data;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return aac_frame;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
public hasIncompleteData(): boolean {
|
|
117
|
+
return this.has_last_incomplete_data;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
public getIncompleteData(): Uint8Array {
|
|
121
|
+
if (!this.has_last_incomplete_data) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return this.data_.subarray(this.current_syncword_offset_);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export class AACLOASParser {
|
|
130
|
+
|
|
131
|
+
private readonly TAG: string = "AACLOASParser";
|
|
132
|
+
|
|
133
|
+
private data_: Uint8Array;
|
|
134
|
+
private current_syncword_offset_: number;
|
|
135
|
+
private eof_flag_: boolean;
|
|
136
|
+
private has_last_incomplete_data: boolean;
|
|
137
|
+
|
|
138
|
+
public constructor(data: Uint8Array) {
|
|
139
|
+
this.data_ = data;
|
|
140
|
+
this.current_syncword_offset_ = this.findNextSyncwordOffset(0);
|
|
141
|
+
if (this.eof_flag_) {
|
|
142
|
+
Log.e(this.TAG, `Could not found LOAS syncword until payload end`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private findNextSyncwordOffset(syncword_offset: number): number {
|
|
147
|
+
let i = syncword_offset;
|
|
148
|
+
let data = this.data_;
|
|
149
|
+
|
|
150
|
+
while (true) {
|
|
151
|
+
if (i + 1 >= data.byteLength) {
|
|
152
|
+
this.eof_flag_ = true;
|
|
153
|
+
return data.byteLength;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// search 12-bit 0xFFF syncword
|
|
157
|
+
let syncword = (data[i + 0] << 3) | (data[i + 1] >>> 5);
|
|
158
|
+
if (syncword === 0x2B7) {
|
|
159
|
+
return i;
|
|
160
|
+
} else {
|
|
161
|
+
i++;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private getLATMValue(gb: ExpGolomb) {
|
|
167
|
+
let bytesForValue = gb.readBits(2);
|
|
168
|
+
let value = 0;
|
|
169
|
+
for (let i = 0; i <= bytesForValue; i++) {
|
|
170
|
+
value = value << 8;
|
|
171
|
+
value = value | gb.readByte();
|
|
172
|
+
}
|
|
173
|
+
return value;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
public readNextAACFrame(privious?: LOASAACFrame): LOASAACFrame | null {
|
|
177
|
+
let data = this.data_;
|
|
178
|
+
let aac_frame: LOASAACFrame = null;
|
|
179
|
+
|
|
180
|
+
while (aac_frame == null) {
|
|
181
|
+
if (this.eof_flag_) {
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
let syncword_offset = this.current_syncword_offset_;
|
|
186
|
+
let offset = syncword_offset;
|
|
187
|
+
|
|
188
|
+
let audioMuxLengthBytes = ((data[offset + 1] & 0x1F) << 8) | data[offset + 2];
|
|
189
|
+
if (offset + 3 + audioMuxLengthBytes >= this.data_.byteLength) {
|
|
190
|
+
// data not enough for extracting last sample
|
|
191
|
+
this.eof_flag_ = true;
|
|
192
|
+
this.has_last_incomplete_data = true;
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// AudioMuxElement(1)
|
|
197
|
+
let gb = new ExpGolomb(data.subarray(offset + 3, offset + 3 + audioMuxLengthBytes));
|
|
198
|
+
let useSameStreamMux = gb.readBool();
|
|
199
|
+
let streamMuxConfig: LOASAACFrame | null = null;
|
|
200
|
+
if (!useSameStreamMux) {
|
|
201
|
+
let audioMuxVersion = gb.readBool();
|
|
202
|
+
let audioMuxVersionA = audioMuxVersion && gb.readBool();
|
|
203
|
+
if (audioMuxVersionA) {
|
|
204
|
+
Log.e(this.TAG, 'audioMuxVersionA is Not Supported');
|
|
205
|
+
gb.destroy();
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
if (audioMuxVersion) {
|
|
209
|
+
this.getLATMValue(gb);
|
|
210
|
+
}
|
|
211
|
+
let allStreamsSameTimeFraming = gb.readBool();
|
|
212
|
+
if (!allStreamsSameTimeFraming) {
|
|
213
|
+
Log.e(this.TAG, 'allStreamsSameTimeFraming zero is Not Supported');
|
|
214
|
+
gb.destroy();
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
let numSubFrames = gb.readBits(6);
|
|
218
|
+
if (numSubFrames !== 0) {
|
|
219
|
+
Log.e(this.TAG, 'more than 2 numSubFrames Not Supported');
|
|
220
|
+
gb.destroy();
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
let numProgram = gb.readBits(4);
|
|
224
|
+
if (numProgram !== 0) {
|
|
225
|
+
Log.e(this.TAG, 'more than 2 numProgram Not Supported');
|
|
226
|
+
gb.destroy();
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
let numLayer = gb.readBits(3);
|
|
230
|
+
if (numLayer !== 0) {
|
|
231
|
+
Log.e(this.TAG, 'more than 2 numLayer Not Supported');
|
|
232
|
+
gb.destroy();
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
let fillBits = audioMuxVersion ? this.getLATMValue(gb) : 0;
|
|
237
|
+
let audio_object_type = gb.readBits(5); fillBits -= 5;
|
|
238
|
+
let sampling_freq_index = gb.readBits(4);fillBits -= 4;
|
|
239
|
+
let channel_config = gb.readBits(4); fillBits -= 4;
|
|
240
|
+
gb.readBits(3); fillBits -= 3; // GA Specfic Config
|
|
241
|
+
if (fillBits > 0) { gb.readBits(fillBits); }
|
|
242
|
+
|
|
243
|
+
let frameLengthType = gb.readBits(3);
|
|
244
|
+
if (frameLengthType === 0) {
|
|
245
|
+
gb.readByte();
|
|
246
|
+
} else {
|
|
247
|
+
Log.e(this.TAG, `frameLengthType = ${frameLengthType}. Only frameLengthType = 0 Supported`);
|
|
248
|
+
gb.destroy();
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
let otherDataPresent = gb.readBool();
|
|
253
|
+
if (otherDataPresent) {
|
|
254
|
+
if (audioMuxVersion) {
|
|
255
|
+
this.getLATMValue(gb);
|
|
256
|
+
} else {
|
|
257
|
+
let otherDataLenBits = 0;
|
|
258
|
+
while (true) {
|
|
259
|
+
otherDataLenBits = otherDataLenBits << 8;
|
|
260
|
+
let otherDataLenEsc = gb.readBool();
|
|
261
|
+
let otherDataLenTmp = gb.readByte();
|
|
262
|
+
otherDataLenBits += otherDataLenTmp
|
|
263
|
+
if (!otherDataLenEsc) { break; }
|
|
264
|
+
}
|
|
265
|
+
console.log(otherDataLenBits)
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
let crcCheckPresent = gb.readBool();
|
|
270
|
+
if (crcCheckPresent) {
|
|
271
|
+
gb.readByte();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
streamMuxConfig = new LOASAACFrame();
|
|
275
|
+
streamMuxConfig.audio_object_type = audio_object_type;
|
|
276
|
+
streamMuxConfig.sampling_freq_index = sampling_freq_index;
|
|
277
|
+
streamMuxConfig.sampling_frequency = MPEG4SamplingFrequencies[streamMuxConfig.sampling_freq_index];
|
|
278
|
+
streamMuxConfig.channel_config = channel_config;
|
|
279
|
+
streamMuxConfig.other_data_present = otherDataPresent;
|
|
280
|
+
} else if (privious == null) {
|
|
281
|
+
Log.w(this.TAG, 'StreamMuxConfig Missing')
|
|
282
|
+
this.current_syncword_offset_ = this.findNextSyncwordOffset(offset + 3 + audioMuxLengthBytes);
|
|
283
|
+
gb.destroy();
|
|
284
|
+
continue;
|
|
285
|
+
} else {
|
|
286
|
+
streamMuxConfig = privious;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
let length = 0;
|
|
290
|
+
while (true) {
|
|
291
|
+
let tmp = gb.readByte();
|
|
292
|
+
length += tmp;
|
|
293
|
+
if (tmp !== 0xFF) { break; }
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
let aac_data = new Uint8Array(length);
|
|
297
|
+
for (let i = 0; i < length; i++) {
|
|
298
|
+
aac_data[i] = gb.readByte();
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
aac_frame = new LOASAACFrame();
|
|
302
|
+
aac_frame.audio_object_type = (streamMuxConfig.audio_object_type) as MPEG4AudioObjectTypes;
|
|
303
|
+
aac_frame.sampling_freq_index = (streamMuxConfig.sampling_freq_index) as MPEG4SamplingFrequencyIndex;
|
|
304
|
+
aac_frame.sampling_frequency = MPEG4SamplingFrequencies[streamMuxConfig.sampling_freq_index];
|
|
305
|
+
aac_frame.channel_config = streamMuxConfig.channel_config;
|
|
306
|
+
aac_frame.other_data_present = streamMuxConfig.other_data_present;
|
|
307
|
+
aac_frame.data = aac_data;
|
|
308
|
+
|
|
309
|
+
this.current_syncword_offset_ = this.findNextSyncwordOffset(offset + 3 + audioMuxLengthBytes);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return aac_frame;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
public hasIncompleteData(): boolean {
|
|
316
|
+
return this.has_last_incomplete_data;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
public getIncompleteData(): Uint8Array {
|
|
320
|
+
if (!this.has_last_incomplete_data) {
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return this.data_.subarray(this.current_syncword_offset_);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export class AudioSpecificConfig {
|
|
329
|
+
|
|
330
|
+
public config: Array<number>;
|
|
331
|
+
public sampling_rate: number;
|
|
332
|
+
public channel_count: number;
|
|
333
|
+
public codec_mimetype: string;
|
|
334
|
+
public original_codec_mimetype: string;
|
|
335
|
+
|
|
336
|
+
public constructor(frame: AACFrame) {
|
|
337
|
+
let config: Array<number> = null;
|
|
338
|
+
|
|
339
|
+
let original_audio_object_type = frame.audio_object_type;
|
|
340
|
+
let audio_object_type = frame.audio_object_type;
|
|
341
|
+
let sampling_index = frame.sampling_freq_index;
|
|
342
|
+
let channel_config = frame.channel_config;
|
|
343
|
+
let extension_sampling_index = 0;
|
|
344
|
+
|
|
345
|
+
let userAgent = navigator.userAgent.toLowerCase();
|
|
346
|
+
|
|
347
|
+
if (userAgent.indexOf('firefox') !== -1) {
|
|
348
|
+
// firefox: use SBR (HE-AAC) if freq less than 24kHz
|
|
349
|
+
if (sampling_index >= 6) {
|
|
350
|
+
audio_object_type = 5;
|
|
351
|
+
config = new Array(4);
|
|
352
|
+
extension_sampling_index = sampling_index - 3;
|
|
353
|
+
} else { // use LC-AAC
|
|
354
|
+
audio_object_type = 2;
|
|
355
|
+
config = new Array(2);
|
|
356
|
+
extension_sampling_index = sampling_index;
|
|
357
|
+
}
|
|
358
|
+
} else if (userAgent.indexOf('android') !== -1) {
|
|
359
|
+
// android: always use LC-AAC
|
|
360
|
+
audio_object_type = 2;
|
|
361
|
+
config = new Array(2);
|
|
362
|
+
extension_sampling_index = sampling_index;
|
|
363
|
+
} else {
|
|
364
|
+
// for other browsers, e.g. chrome...
|
|
365
|
+
// Always use HE-AAC to make it easier to switch aac codec profile
|
|
366
|
+
audio_object_type = 5;
|
|
367
|
+
extension_sampling_index = sampling_index;
|
|
368
|
+
config = new Array(4);
|
|
369
|
+
|
|
370
|
+
if (sampling_index >= 6) {
|
|
371
|
+
extension_sampling_index = sampling_index - 3;
|
|
372
|
+
} else if (channel_config === 1) { // Mono channel
|
|
373
|
+
audio_object_type = 2;
|
|
374
|
+
config = new Array(2);
|
|
375
|
+
extension_sampling_index = sampling_index;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
config[0] = audio_object_type << 3;
|
|
380
|
+
config[0] |= (sampling_index & 0x0F) >>> 1;
|
|
381
|
+
config[1] = (sampling_index & 0x0F) << 7;
|
|
382
|
+
config[1] |= (channel_config & 0x0F) << 3;
|
|
383
|
+
if (audio_object_type === 5) {
|
|
384
|
+
config[1] |= ((extension_sampling_index & 0x0F) >>> 1);
|
|
385
|
+
config[2] = (extension_sampling_index & 0x01) << 7;
|
|
386
|
+
// extended audio object type: force to 2 (LC-AAC)
|
|
387
|
+
config[2] |= (2 << 2);
|
|
388
|
+
config[3] = 0;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
this.config = config;
|
|
392
|
+
this.sampling_rate = MPEG4SamplingFrequencies[sampling_index];
|
|
393
|
+
this.channel_count = channel_config;
|
|
394
|
+
this.codec_mimetype = 'mp4a.40.' + audio_object_type;
|
|
395
|
+
this.original_codec_mimetype = 'mp4a.40.' + original_audio_object_type;
|
|
396
|
+
}
|
|
397
|
+
}
|