@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,2405 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2021 magicxqq. All Rights Reserved.
|
|
3
|
+
*
|
|
4
|
+
* @author magicxqq <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';
|
|
20
|
+
import DemuxErrors from './demux-errors';
|
|
21
|
+
import MediaInfo from '../core/media-info';
|
|
22
|
+
import {IllegalStateException} from '../utils/exception';
|
|
23
|
+
import BaseDemuxer from './base-demuxer';
|
|
24
|
+
import { PAT, PESData, SectionData, SliceQueue, PIDToSliceQueues, PMT, ProgramToPMTMap, StreamType } from './pat-pmt-pes';
|
|
25
|
+
import { AVCDecoderConfigurationRecord, H264AnnexBParser, H264NaluAVC1, H264NaluPayload, H264NaluType } from './h264';
|
|
26
|
+
import SPSParser from './sps-parser';
|
|
27
|
+
import { AACADTSParser, AACFrame, AACLOASParser, AudioSpecificConfig, LOASAACFrame } from './aac';
|
|
28
|
+
import { MPEG4AudioObjectTypes, MPEG4SamplingFrequencyIndex } from './mpeg4-audio';
|
|
29
|
+
import { PESPrivateData, PESPrivateDataDescriptor } from './pes-private-data';
|
|
30
|
+
import { parseSEI } from './sei';
|
|
31
|
+
import { readSCTE35, SCTE35Data } from './scte35';
|
|
32
|
+
import { H265AnnexBParser, H265NaluHVC1, H265NaluPayload, H265NaluType, HEVCDecoderConfigurationRecord } from './h265';
|
|
33
|
+
import H265Parser from './h265-parser';
|
|
34
|
+
import { SMPTE2038Data, smpte2038parse } from './smpte2038';
|
|
35
|
+
import { MP3Data } from './mp3';
|
|
36
|
+
import { AC3Config, AC3Frame, AC3Parser, EAC3Config, EAC3Frame, EAC3Parser } from './ac3';
|
|
37
|
+
import { KLVData, klv_parse } from './klv';
|
|
38
|
+
import AV1OBUInMpegTsParser from './av1';
|
|
39
|
+
import AV1OBUParser from './av1-parser';
|
|
40
|
+
import { PGSData } from './pgs-data';
|
|
41
|
+
|
|
42
|
+
type AdaptationFieldInfo = {
|
|
43
|
+
discontinuity_indicator?: number;
|
|
44
|
+
random_access_indicator?: number;
|
|
45
|
+
elementary_stream_priority_indicator?: number;
|
|
46
|
+
};
|
|
47
|
+
type AACAudioMetadata = {
|
|
48
|
+
codec: 'aac',
|
|
49
|
+
audio_object_type: MPEG4AudioObjectTypes;
|
|
50
|
+
sampling_freq_index: MPEG4SamplingFrequencyIndex;
|
|
51
|
+
sampling_frequency: number;
|
|
52
|
+
channel_config: number;
|
|
53
|
+
};
|
|
54
|
+
type AC3AudioMetadata = {
|
|
55
|
+
codec: 'ac-3',
|
|
56
|
+
sampling_frequency: number;
|
|
57
|
+
bit_stream_identification: number;
|
|
58
|
+
bit_stream_mode: number;
|
|
59
|
+
low_frequency_effects_channel_on: number;
|
|
60
|
+
channel_mode: number;
|
|
61
|
+
};
|
|
62
|
+
type EAC3AudioMetadata = {
|
|
63
|
+
codec: 'ec-3',
|
|
64
|
+
sampling_frequency: number;
|
|
65
|
+
bit_stream_identification: number;
|
|
66
|
+
low_frequency_effects_channel_on: number;
|
|
67
|
+
channel_mode: number;
|
|
68
|
+
num_blks: number;
|
|
69
|
+
};
|
|
70
|
+
type OpusAudioMetadata = {
|
|
71
|
+
codec: 'opus';
|
|
72
|
+
channel_count: number;
|
|
73
|
+
channel_config_code: number;
|
|
74
|
+
sample_rate: number;
|
|
75
|
+
}
|
|
76
|
+
type MP3AudioMetadata = {
|
|
77
|
+
codec: 'mp3',
|
|
78
|
+
object_type: number,
|
|
79
|
+
sample_rate: number,
|
|
80
|
+
channel_count: number;
|
|
81
|
+
};
|
|
82
|
+
type AudioData = {
|
|
83
|
+
codec: 'aac';
|
|
84
|
+
data: AACFrame;
|
|
85
|
+
} | {
|
|
86
|
+
codec: 'ac-3';
|
|
87
|
+
data: AC3Frame,
|
|
88
|
+
} | {
|
|
89
|
+
codec: 'ec-3';
|
|
90
|
+
data: EAC3Frame,
|
|
91
|
+
} | {
|
|
92
|
+
codec: 'opus';
|
|
93
|
+
meta: OpusAudioMetadata,
|
|
94
|
+
} | {
|
|
95
|
+
codec: 'mp3';
|
|
96
|
+
data: MP3Data;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
type StashedAudioPayload = {
|
|
100
|
+
codec: 'aac' | 'aac-loas' | 'ac-3' | 'ec-3' | 'opus' | 'mp3';
|
|
101
|
+
data: Uint8Array;
|
|
102
|
+
pts: number;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
class TSDemuxer extends BaseDemuxer {
|
|
106
|
+
|
|
107
|
+
private readonly TAG: string = 'TSDemuxer';
|
|
108
|
+
|
|
109
|
+
private config_: any;
|
|
110
|
+
private ts_packet_size_: number;
|
|
111
|
+
private sync_offset_: number;
|
|
112
|
+
private first_parse_: boolean = true;
|
|
113
|
+
|
|
114
|
+
private media_info_ = new MediaInfo();
|
|
115
|
+
|
|
116
|
+
private timescale_ = 90;
|
|
117
|
+
private duration_ = 0;
|
|
118
|
+
|
|
119
|
+
private pat_: PAT;
|
|
120
|
+
private current_program_: number;
|
|
121
|
+
private current_pmt_pid_: number = -1;
|
|
122
|
+
private pmt_: PMT;
|
|
123
|
+
private program_pmt_map_: ProgramToPMTMap = {};
|
|
124
|
+
|
|
125
|
+
private pes_slice_queues_: PIDToSliceQueues = {};
|
|
126
|
+
private section_slice_queues_: PIDToSliceQueues = {};
|
|
127
|
+
|
|
128
|
+
private video_metadata_: {
|
|
129
|
+
vps: H265NaluHVC1 | undefined,
|
|
130
|
+
sps: H264NaluAVC1 | H265NaluHVC1 | undefined,
|
|
131
|
+
pps: H264NaluAVC1 | H265NaluHVC1 | undefined,
|
|
132
|
+
av1c: Uint8Array | undefined,
|
|
133
|
+
details: any
|
|
134
|
+
} = {
|
|
135
|
+
vps: undefined,
|
|
136
|
+
sps: undefined,
|
|
137
|
+
pps: undefined,
|
|
138
|
+
av1c: undefined,
|
|
139
|
+
details: undefined
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
private audio_metadata_: AACAudioMetadata | AC3AudioMetadata | EAC3AudioMetadata | OpusAudioMetadata | MP3AudioMetadata = {
|
|
143
|
+
codec: undefined,
|
|
144
|
+
audio_object_type: undefined,
|
|
145
|
+
sampling_freq_index: undefined,
|
|
146
|
+
sampling_frequency: undefined,
|
|
147
|
+
channel_config: undefined
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
private last_pcr_: number | undefined;
|
|
151
|
+
private last_pcr_base_: number = NaN;
|
|
152
|
+
private timestamp_offset_: number = 0;
|
|
153
|
+
|
|
154
|
+
private audio_last_sample_pts_: number = undefined;
|
|
155
|
+
private aac_last_incomplete_data_: Uint8Array = null;
|
|
156
|
+
|
|
157
|
+
private has_video_ = false;
|
|
158
|
+
private has_audio_ = false;
|
|
159
|
+
private video_init_segment_dispatched_ = false;
|
|
160
|
+
private audio_init_segment_dispatched_ = false;
|
|
161
|
+
private video_metadata_changed_ = false;
|
|
162
|
+
private audio_metadata_changed_ = false;
|
|
163
|
+
private video_keyframe_seen_after_init_ = false;
|
|
164
|
+
private stashed_audio_before_video_init_: Array<StashedAudioPayload> = [];
|
|
165
|
+
private _last_dispatch_block_reason_: string = '';
|
|
166
|
+
private video_init_dispatch_time_: number = 0;
|
|
167
|
+
// The PID currently being decoded as the active audio stream.
|
|
168
|
+
// 0 means "use whatever common_pids picks" (default first audio).
|
|
169
|
+
private active_audio_pid_: number = 0;
|
|
170
|
+
private loas_previous_frame: LOASAACFrame | null = null;
|
|
171
|
+
|
|
172
|
+
private video_track_ = {type: 'video', id: 1, sequenceNumber: 0, samples: [], length: 0};
|
|
173
|
+
private audio_track_ = {type: 'audio', id: 2, sequenceNumber: 0, samples: [], length: 0};
|
|
174
|
+
|
|
175
|
+
public constructor(probe_data: any, config: any) {
|
|
176
|
+
super();
|
|
177
|
+
|
|
178
|
+
this.ts_packet_size_ = probe_data.ts_packet_size;
|
|
179
|
+
this.sync_offset_ = probe_data.sync_offset;
|
|
180
|
+
this.config_ = config;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
public destroy() {
|
|
184
|
+
this.media_info_ = null;
|
|
185
|
+
this.pes_slice_queues_ = null;
|
|
186
|
+
this.section_slice_queues_ = null;
|
|
187
|
+
|
|
188
|
+
this.video_metadata_ = null;
|
|
189
|
+
this.audio_metadata_ = null;
|
|
190
|
+
this.aac_last_incomplete_data_ = null;
|
|
191
|
+
|
|
192
|
+
this.video_track_ = null;
|
|
193
|
+
this.audio_track_ = null;
|
|
194
|
+
|
|
195
|
+
super.destroy();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
public static probe(buffer: ArrayBuffer) {
|
|
199
|
+
let data = new Uint8Array(buffer);
|
|
200
|
+
let sync_offset = -1;
|
|
201
|
+
let ts_packet_size = 188;
|
|
202
|
+
|
|
203
|
+
if (data.byteLength <= 3 * ts_packet_size) {
|
|
204
|
+
return {needMoreData: true};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
while (sync_offset === -1) {
|
|
208
|
+
let scan_window = Math.min(1000, data.byteLength - 3 * ts_packet_size);
|
|
209
|
+
|
|
210
|
+
for (let i = 0; i < scan_window; ) {
|
|
211
|
+
// sync_byte should all be 0x47
|
|
212
|
+
if (data[i] === 0x47
|
|
213
|
+
&& data[i + ts_packet_size] === 0x47
|
|
214
|
+
&& data[i + 2 * ts_packet_size] === 0x47) {
|
|
215
|
+
sync_offset = i;
|
|
216
|
+
break;
|
|
217
|
+
} else {
|
|
218
|
+
i++;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// find sync_offset failed in previous ts_packet_size
|
|
223
|
+
if (sync_offset === -1) {
|
|
224
|
+
if (ts_packet_size === 188) {
|
|
225
|
+
// try 192 packet size (BDAV, etc.)
|
|
226
|
+
ts_packet_size = 192;
|
|
227
|
+
} else if (ts_packet_size === 192) {
|
|
228
|
+
// try 204 packet size (European DVB, etc.)
|
|
229
|
+
ts_packet_size = 204;
|
|
230
|
+
} else {
|
|
231
|
+
// 192, 204 also failed, exit
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (sync_offset === -1) {
|
|
238
|
+
// both 188, 192, 204 failed, Non MPEG-TS
|
|
239
|
+
return {match: false};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (ts_packet_size === 192 && sync_offset >= 4) {
|
|
243
|
+
Log.v('TSDemuxer', `ts_packet_size = 192, m2ts mode`);
|
|
244
|
+
sync_offset -= 4;
|
|
245
|
+
} else if (ts_packet_size === 204) {
|
|
246
|
+
Log.v('TSDemuxer', `ts_packet_size = 204, RS encoded MPEG2-TS stream`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
match: true,
|
|
251
|
+
consumed: 0,
|
|
252
|
+
ts_packet_size,
|
|
253
|
+
sync_offset
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
public bindDataSource(loader) {
|
|
258
|
+
loader.onDataArrival = this.parseChunks.bind(this);
|
|
259
|
+
return this;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
public resetMediaInfo() {
|
|
263
|
+
this.media_info_ = new MediaInfo();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Switch to a different audio PID discovered in the PMT.
|
|
268
|
+
* Call with a pid from onTracksUpdated audioTracks, or 0 to revert to default.
|
|
269
|
+
* Resets audio init state so the new stream is initialised cleanly.
|
|
270
|
+
*/
|
|
271
|
+
public selectAudioPid(pid: number): void {
|
|
272
|
+
if (!this.pmt_) return;
|
|
273
|
+
const available = this.pmt_.all_audio_pids.map(a => a.pid);
|
|
274
|
+
if (pid !== 0 && !available.includes(pid)) {
|
|
275
|
+
Log.w(this.TAG, `[muvie] selectAudioPid: pid ${pid} not in available audio PIDs [${available.join(', ')}]`);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
Log.v(this.TAG, `[muvie] selectAudioPid: switching active audio PID to ${pid || '(default)'}`);
|
|
279
|
+
this.active_audio_pid_ = pid;
|
|
280
|
+
const pmt = this.pmt_;
|
|
281
|
+
pmt.common_pids.adts_aac = undefined;
|
|
282
|
+
pmt.common_pids.loas_aac = undefined;
|
|
283
|
+
pmt.common_pids.ac3 = undefined;
|
|
284
|
+
pmt.common_pids.eac3 = undefined;
|
|
285
|
+
pmt.common_pids.opus = undefined;
|
|
286
|
+
pmt.common_pids.mp3 = undefined;
|
|
287
|
+
|
|
288
|
+
let entry = pid !== 0 ? pmt.all_audio_pids.find(a => a.pid === pid) : undefined;
|
|
289
|
+
if (!entry) {
|
|
290
|
+
entry = pmt.all_audio_pids[0];
|
|
291
|
+
}
|
|
292
|
+
if (entry) {
|
|
293
|
+
if (entry.codec === 'aac') pmt.common_pids.adts_aac = entry.pid;
|
|
294
|
+
else if (entry.codec === 'aac-loas') pmt.common_pids.loas_aac = entry.pid;
|
|
295
|
+
else if (entry.codec === 'ac3') pmt.common_pids.ac3 = entry.pid;
|
|
296
|
+
else if (entry.codec === 'eac3') pmt.common_pids.eac3 = entry.pid;
|
|
297
|
+
else if (entry.codec === 'opus') pmt.common_pids.opus = entry.pid;
|
|
298
|
+
else if (entry.codec === 'mp3') pmt.common_pids.mp3 = entry.pid;
|
|
299
|
+
}
|
|
300
|
+
// Reset audio init so the new PID's audio spec config is properly dispatched
|
|
301
|
+
this.audio_init_segment_dispatched_ = false;
|
|
302
|
+
this.audio_metadata_ = {codec: undefined, audio_object_type: undefined, sampling_freq_index: undefined, sampling_frequency: undefined, channel_config: undefined};
|
|
303
|
+
(this.audio_track_ as any).samples = [];
|
|
304
|
+
(this.audio_track_ as any).length = 0;
|
|
305
|
+
this.audio_last_sample_pts_ = undefined;
|
|
306
|
+
this.aac_last_incomplete_data_ = null;
|
|
307
|
+
this.loas_previous_frame = null;
|
|
308
|
+
this.stashed_audio_before_video_init_ = [];
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** Store a PID preference before PMT arrives — applied by auto-fallback on first parse. */
|
|
312
|
+
public setPreferredAudioPid(pid: number): void {
|
|
313
|
+
this.active_audio_pid_ = pid;
|
|
314
|
+
Log.v(this.TAG, `[muvie] setPreferredAudioPid: ${pid}`);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/** Returns the current PMT track lists (same data as onTracksUpdated). */
|
|
318
|
+
public getAvailableTracks() {
|
|
319
|
+
if (!this.pmt_) return {audioTracks: [], subtitleTracks: []};
|
|
320
|
+
return {
|
|
321
|
+
audioTracks: this.pmt_.all_audio_pids,
|
|
322
|
+
subtitleTracks: this.pmt_.subtitle_pids,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
public parseChunks(chunk: ArrayBuffer, byte_start: number): number {
|
|
327
|
+
if (!this.onError
|
|
328
|
+
|| !this.onMediaInfo
|
|
329
|
+
|| !this.onTrackMetadata
|
|
330
|
+
|| !this.onDataAvailable) {
|
|
331
|
+
throw new IllegalStateException('onError & onMediaInfo & onTrackMetadata & onDataAvailable callback must be specified');
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
let offset = 0;
|
|
335
|
+
|
|
336
|
+
if (this.first_parse_) {
|
|
337
|
+
this.first_parse_ = false;
|
|
338
|
+
offset = this.sync_offset_;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
while (offset + this.ts_packet_size_ <= chunk.byteLength) {
|
|
342
|
+
let file_position = byte_start + offset;
|
|
343
|
+
|
|
344
|
+
if (this.ts_packet_size_ === 192) {
|
|
345
|
+
// skip ATS field (2-bits copy-control + 30-bits timestamp) for m2ts
|
|
346
|
+
offset += 4;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
let data = new Uint8Array(chunk, offset, 188);
|
|
350
|
+
|
|
351
|
+
let sync_byte = data[0];
|
|
352
|
+
if (sync_byte !== 0x47) {
|
|
353
|
+
Log.e(this.TAG, `sync_byte = ${sync_byte}, not 0x47`);
|
|
354
|
+
break;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
let payload_unit_start_indicator = (data[1] & 0x40) >>> 6;
|
|
358
|
+
let transport_priority = (data[1] & 0x20) >>> 5;
|
|
359
|
+
let pid = ((data[1] & 0x1F) << 8) | data[2];
|
|
360
|
+
let adaptation_field_control = (data[3] & 0x30) >>> 4;
|
|
361
|
+
let continuity_conunter = (data[3] & 0x0F);
|
|
362
|
+
|
|
363
|
+
let is_pcr_pid: boolean = (this.pmt_ && this.pmt_.pcr_pid === pid) ? true : false;
|
|
364
|
+
let adaptation_field_info: AdaptationFieldInfo = {};
|
|
365
|
+
let ts_payload_start_index = 4;
|
|
366
|
+
|
|
367
|
+
if (adaptation_field_control == 0x02 || adaptation_field_control == 0x03) {
|
|
368
|
+
// Adaptation field exists along with / without payload
|
|
369
|
+
let adaptation_field_length = data[4];
|
|
370
|
+
if (adaptation_field_length > 0 && (is_pcr_pid || adaptation_field_control == 0x03)) {
|
|
371
|
+
// Parse adaptation field
|
|
372
|
+
adaptation_field_info.discontinuity_indicator = (data[5] & 0x80) >>> 7;
|
|
373
|
+
adaptation_field_info.random_access_indicator = (data[5] & 0x40) >>> 6;
|
|
374
|
+
adaptation_field_info.elementary_stream_priority_indicator = (data[5] & 0x20) >>> 5;
|
|
375
|
+
|
|
376
|
+
let PCR_flag = (data[5] & 0x10) >>> 4;
|
|
377
|
+
if (PCR_flag) {
|
|
378
|
+
let pcr_base = this.getPcrBase(data);
|
|
379
|
+
let pcr_extension = ((data[10] & 0x01) << 8) | data[11];
|
|
380
|
+
let pcr = pcr_base * 300 + pcr_extension;
|
|
381
|
+
this.last_pcr_ = pcr;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
if (adaptation_field_control == 0x02 || 5 + adaptation_field_length === 188) {
|
|
385
|
+
// TS packet only has adaption field, jump to next
|
|
386
|
+
offset += 188;
|
|
387
|
+
if (this.ts_packet_size_ === 204) {
|
|
388
|
+
// skip parity word (16 bytes) for RS encoded TS
|
|
389
|
+
offset += 16;
|
|
390
|
+
}
|
|
391
|
+
continue;
|
|
392
|
+
} else {
|
|
393
|
+
// Point ts_payload_start_index to the start of payload
|
|
394
|
+
ts_payload_start_index = 4 + 1 + adaptation_field_length;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (adaptation_field_control == 0x01 || adaptation_field_control == 0x03) {
|
|
399
|
+
if (pid === 0 || // PAT (pid === 0)
|
|
400
|
+
pid === this.current_pmt_pid_ || // PMT
|
|
401
|
+
(this.pmt_ != undefined && this.pmt_.pid_stream_type[pid] === StreamType.kSCTE35)) { // SCTE35
|
|
402
|
+
let ts_payload_length = 188 - ts_payload_start_index;
|
|
403
|
+
|
|
404
|
+
this.handleSectionSlice(chunk,
|
|
405
|
+
offset + ts_payload_start_index,
|
|
406
|
+
ts_payload_length,
|
|
407
|
+
{
|
|
408
|
+
pid,
|
|
409
|
+
file_position,
|
|
410
|
+
payload_unit_start_indicator,
|
|
411
|
+
continuity_conunter,
|
|
412
|
+
random_access_indicator: adaptation_field_info.random_access_indicator
|
|
413
|
+
});
|
|
414
|
+
} else if (this.pmt_ != undefined && this.pmt_.pid_stream_type[pid] != undefined) {
|
|
415
|
+
// PES
|
|
416
|
+
let ts_payload_length = 188 - ts_payload_start_index;
|
|
417
|
+
let stream_type = this.pmt_.pid_stream_type[pid];
|
|
418
|
+
|
|
419
|
+
// process PES only for known common_pids
|
|
420
|
+
if (pid === this.pmt_.common_pids.h264
|
|
421
|
+
|| pid === this.pmt_.common_pids.h265
|
|
422
|
+
|| pid === this.pmt_.common_pids.av1
|
|
423
|
+
|| pid === this.pmt_.common_pids.adts_aac
|
|
424
|
+
|| pid === this.pmt_.common_pids.loas_aac
|
|
425
|
+
|| pid === this.pmt_.common_pids.ac3
|
|
426
|
+
|| pid === this.pmt_.common_pids.eac3
|
|
427
|
+
|| pid === this.pmt_.common_pids.opus
|
|
428
|
+
|| pid === this.pmt_.common_pids.mp3
|
|
429
|
+
|| this.pmt_.pes_private_data_pids[pid] === true
|
|
430
|
+
|| this.pmt_.timed_id3_pids[pid] === true
|
|
431
|
+
|| this.pmt_.pgs_pids[pid] === true
|
|
432
|
+
|| this.pmt_.synchronous_klv_pids[pid] === true
|
|
433
|
+
|| this.pmt_.asynchronous_klv_pids[pid] === true
|
|
434
|
+
) {
|
|
435
|
+
this.handlePESSlice(chunk,
|
|
436
|
+
offset + ts_payload_start_index,
|
|
437
|
+
ts_payload_length,
|
|
438
|
+
{
|
|
439
|
+
pid,
|
|
440
|
+
stream_type,
|
|
441
|
+
file_position,
|
|
442
|
+
payload_unit_start_indicator,
|
|
443
|
+
continuity_conunter,
|
|
444
|
+
random_access_indicator: adaptation_field_info.random_access_indicator
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
offset += 188;
|
|
451
|
+
|
|
452
|
+
if (this.ts_packet_size_ === 204) {
|
|
453
|
+
// skip parity word (16 bytes) for RS encoded TS
|
|
454
|
+
offset += 16;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// dispatch parsed frames to the remuxer (consumer)
|
|
459
|
+
this.dispatchAudioVideoMediaSegment();
|
|
460
|
+
|
|
461
|
+
return offset; // consumed bytes
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
private handleSectionSlice(buffer: ArrayBuffer, offset: number, length: number, misc: any): void {
|
|
465
|
+
let data = new Uint8Array(buffer, offset, length);
|
|
466
|
+
let slice_queue = this.section_slice_queues_[misc.pid];
|
|
467
|
+
|
|
468
|
+
if (misc.payload_unit_start_indicator) {
|
|
469
|
+
let pointer_field = data[0];
|
|
470
|
+
|
|
471
|
+
if (slice_queue != undefined && slice_queue.total_length !== 0) {
|
|
472
|
+
let remain_section = new Uint8Array(buffer, offset + 1, Math.min(length, pointer_field));
|
|
473
|
+
slice_queue.slices.push(remain_section);
|
|
474
|
+
slice_queue.total_length += remain_section.byteLength;
|
|
475
|
+
|
|
476
|
+
if (slice_queue.total_length === slice_queue.expected_length) {
|
|
477
|
+
this.emitSectionSlices(slice_queue, misc);
|
|
478
|
+
} else {
|
|
479
|
+
this.clearSlices(slice_queue, misc);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
for (let i = 1 + pointer_field; i < data.byteLength; ){
|
|
484
|
+
let table_id = data[i + 0];
|
|
485
|
+
if (table_id === 0xFF) { break; }
|
|
486
|
+
|
|
487
|
+
let section_length = ((data[i + 1] & 0x0F) << 8) | data[i + 2];
|
|
488
|
+
|
|
489
|
+
this.section_slice_queues_[misc.pid] = new SliceQueue();
|
|
490
|
+
slice_queue = this.section_slice_queues_[misc.pid];
|
|
491
|
+
|
|
492
|
+
slice_queue.expected_length = section_length + 3;
|
|
493
|
+
slice_queue.file_position = misc.file_position;
|
|
494
|
+
slice_queue.random_access_indicator = misc.random_access_indicator;
|
|
495
|
+
|
|
496
|
+
let remain_section = new Uint8Array(buffer, offset + i, Math.min(length - i, slice_queue.expected_length - slice_queue.total_length));
|
|
497
|
+
slice_queue.slices.push(remain_section);
|
|
498
|
+
slice_queue.total_length += remain_section.byteLength;
|
|
499
|
+
|
|
500
|
+
if (slice_queue.total_length === slice_queue.expected_length) {
|
|
501
|
+
this.emitSectionSlices(slice_queue, misc);
|
|
502
|
+
} else if (slice_queue.total_length >= slice_queue.expected_length) {
|
|
503
|
+
this.clearSlices(slice_queue, misc);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
i += remain_section.byteLength;
|
|
507
|
+
}
|
|
508
|
+
} else if (slice_queue != undefined && slice_queue.total_length !== 0) {
|
|
509
|
+
let remain_section = new Uint8Array(buffer, offset, Math.min(length, slice_queue.expected_length - slice_queue.total_length));
|
|
510
|
+
slice_queue.slices.push(remain_section);
|
|
511
|
+
slice_queue.total_length += remain_section.byteLength;
|
|
512
|
+
|
|
513
|
+
if (slice_queue.total_length === slice_queue.expected_length) {
|
|
514
|
+
this.emitSectionSlices(slice_queue, misc);
|
|
515
|
+
} else if (slice_queue.total_length >= slice_queue.expected_length) {
|
|
516
|
+
this.clearSlices(slice_queue, misc);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
private handlePESSlice(buffer: ArrayBuffer, offset: number, length: number, misc: any): void {
|
|
522
|
+
let data = new Uint8Array(buffer, offset, length);
|
|
523
|
+
|
|
524
|
+
let packet_start_code_prefix = (data[0] << 16) | (data[1] << 8) | (data[2]);
|
|
525
|
+
let stream_id = data[3];
|
|
526
|
+
let PES_packet_length = (data[4] << 8) | data[5];
|
|
527
|
+
|
|
528
|
+
if (misc.payload_unit_start_indicator) {
|
|
529
|
+
if (packet_start_code_prefix !== 1) {
|
|
530
|
+
Log.e(this.TAG, `handlePESSlice: packet_start_code_prefix should be 1 but with value ${packet_start_code_prefix}`);
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// handle queued PES slices:
|
|
535
|
+
// Merge into a big Uint8Array then call parsePES()
|
|
536
|
+
let slice_queue = this.pes_slice_queues_[misc.pid];
|
|
537
|
+
if (slice_queue) {
|
|
538
|
+
if (slice_queue.expected_length === 0 || slice_queue.expected_length === slice_queue.total_length) {
|
|
539
|
+
this.emitPESSlices(slice_queue, misc);
|
|
540
|
+
} else {
|
|
541
|
+
this.clearSlices(slice_queue, misc);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Make a new PES queue for new PES slices
|
|
546
|
+
this.pes_slice_queues_[misc.pid] = new SliceQueue();
|
|
547
|
+
this.pes_slice_queues_[misc.pid].file_position = misc.file_position;
|
|
548
|
+
this.pes_slice_queues_[misc.pid].random_access_indicator = misc.random_access_indicator;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
if (this.pes_slice_queues_[misc.pid] == undefined) {
|
|
552
|
+
// ignore PES slices without [PES slice that has payload_unit_start_indicator]
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// push subsequent PES slices into pes_queue
|
|
557
|
+
let slice_queue = this.pes_slice_queues_[misc.pid];
|
|
558
|
+
slice_queue.slices.push(data);
|
|
559
|
+
if (misc.payload_unit_start_indicator) {
|
|
560
|
+
slice_queue.expected_length = PES_packet_length === 0 ? 0 : PES_packet_length + 6;
|
|
561
|
+
}
|
|
562
|
+
slice_queue.total_length += data.byteLength;
|
|
563
|
+
|
|
564
|
+
if (slice_queue.expected_length > 0 && slice_queue.expected_length === slice_queue.total_length) {
|
|
565
|
+
this.emitPESSlices(slice_queue, misc);
|
|
566
|
+
} else if (slice_queue.expected_length > 0 && slice_queue.expected_length < slice_queue.total_length) {
|
|
567
|
+
this.clearSlices(slice_queue, misc);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
private emitSectionSlices(slice_queue: SliceQueue, misc: any): void {
|
|
572
|
+
let data = new Uint8Array(slice_queue.total_length);
|
|
573
|
+
for (let i = 0, offset = 0; i < slice_queue.slices.length; i++) {
|
|
574
|
+
let slice = slice_queue.slices[i];
|
|
575
|
+
data.set(slice, offset);
|
|
576
|
+
offset += slice.byteLength;
|
|
577
|
+
}
|
|
578
|
+
slice_queue.slices = [];
|
|
579
|
+
slice_queue.expected_length = -1;
|
|
580
|
+
slice_queue.total_length = 0;
|
|
581
|
+
|
|
582
|
+
let section_data = new SectionData();
|
|
583
|
+
section_data.pid = misc.pid;
|
|
584
|
+
section_data.data = data;
|
|
585
|
+
section_data.file_position = slice_queue.file_position;
|
|
586
|
+
section_data.random_access_indicator = slice_queue.random_access_indicator;
|
|
587
|
+
this.parseSection(section_data);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
private emitPESSlices(slice_queue: SliceQueue, misc: any): void {
|
|
591
|
+
let data = new Uint8Array(slice_queue.total_length);
|
|
592
|
+
for (let i = 0, offset = 0; i < slice_queue.slices.length; i++) {
|
|
593
|
+
let slice = slice_queue.slices[i];
|
|
594
|
+
data.set(slice, offset);
|
|
595
|
+
offset += slice.byteLength;
|
|
596
|
+
}
|
|
597
|
+
slice_queue.slices = [];
|
|
598
|
+
slice_queue.expected_length = -1;
|
|
599
|
+
slice_queue.total_length = 0;
|
|
600
|
+
|
|
601
|
+
let pes_data = new PESData();
|
|
602
|
+
pes_data.pid = misc.pid;
|
|
603
|
+
pes_data.data = data;
|
|
604
|
+
pes_data.stream_type = misc.stream_type;
|
|
605
|
+
pes_data.file_position = slice_queue.file_position;
|
|
606
|
+
pes_data.random_access_indicator = slice_queue.random_access_indicator;
|
|
607
|
+
this.parsePES(pes_data);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
private clearSlices(slice_queue: SliceQueue, misc: any): void {
|
|
611
|
+
slice_queue.slices = [];
|
|
612
|
+
slice_queue.expected_length = -1;
|
|
613
|
+
slice_queue.total_length = 0;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
private parseSection(section_data: SectionData): void {
|
|
617
|
+
let data = section_data.data;
|
|
618
|
+
let pid = section_data.pid;
|
|
619
|
+
|
|
620
|
+
if (pid === 0x00) {
|
|
621
|
+
this.parsePAT(data);
|
|
622
|
+
} else if (pid === this.current_pmt_pid_) {
|
|
623
|
+
this.parsePMT(data);
|
|
624
|
+
} else if (this.pmt_ != undefined && this.pmt_.scte_35_pids[pid]) {
|
|
625
|
+
this.parseSCTE35(data);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
private parsePES(pes_data: PESData): void {
|
|
630
|
+
let data = pes_data.data;
|
|
631
|
+
let packet_start_code_prefix = (data[0] << 16) | (data[1] << 8) | (data[2]);
|
|
632
|
+
let stream_id = data[3];
|
|
633
|
+
let PES_packet_length = (data[4] << 8) | data[5];
|
|
634
|
+
|
|
635
|
+
if (packet_start_code_prefix !== 1) {
|
|
636
|
+
Log.e(this.TAG, `parsePES: packet_start_code_prefix should be 1 but with value ${packet_start_code_prefix}`);
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
if (stream_id !== 0xBC // program_stream_map
|
|
641
|
+
&& stream_id !== 0xBE // padding_stream
|
|
642
|
+
&& stream_id !== 0xBF // private_stream_2
|
|
643
|
+
&& stream_id !== 0xF0 // ECM
|
|
644
|
+
&& stream_id !== 0xF1 // EMM
|
|
645
|
+
&& stream_id !== 0xFF // program_stream_directory
|
|
646
|
+
&& stream_id !== 0xF2 // DSMCC
|
|
647
|
+
&& stream_id !== 0xF8) {
|
|
648
|
+
let PES_scrambling_control = (data[6] & 0x30) >>> 4;
|
|
649
|
+
let PTS_DTS_flags = (data[7] & 0xC0) >>> 6;
|
|
650
|
+
let PES_header_data_length = data[8];
|
|
651
|
+
|
|
652
|
+
let pts: number | undefined;
|
|
653
|
+
let dts: number | undefined;
|
|
654
|
+
|
|
655
|
+
if (PTS_DTS_flags === 0x02 || PTS_DTS_flags === 0x03) {
|
|
656
|
+
pts = this.getTimestamp(data, 9);
|
|
657
|
+
dts = PTS_DTS_flags === 0x03 ? this.getTimestamp(data, 14) : pts;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
let payload_start_index = 6 + 3 + PES_header_data_length;
|
|
661
|
+
let payload_length: number;
|
|
662
|
+
|
|
663
|
+
if (PES_packet_length !== 0) {
|
|
664
|
+
if (PES_packet_length < 3 + PES_header_data_length) {
|
|
665
|
+
Log.v(this.TAG, `Malformed PES: PES_packet_length < 3 + PES_header_data_length`);
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
payload_length = PES_packet_length - 3 - PES_header_data_length;
|
|
669
|
+
} else { // PES_packet_length === 0
|
|
670
|
+
payload_length = data.byteLength - payload_start_index;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
let payload = data.subarray(payload_start_index, payload_start_index + payload_length);
|
|
674
|
+
|
|
675
|
+
switch (pes_data.stream_type) {
|
|
676
|
+
case StreamType.kMPEG1Audio:
|
|
677
|
+
case StreamType.kMPEG2Audio:
|
|
678
|
+
this.parseMP3Payload(payload, pts);
|
|
679
|
+
break;
|
|
680
|
+
case StreamType.kPESPrivateData:
|
|
681
|
+
if (this.pmt_.common_pids.av1 === pes_data.pid) {
|
|
682
|
+
this.parseAV1Payload(payload, pts, dts, pes_data.file_position, pes_data.random_access_indicator);
|
|
683
|
+
} else if (this.pmt_.common_pids.opus === pes_data.pid) {
|
|
684
|
+
this.parseOpusPayload(payload, pts);
|
|
685
|
+
} else if (this.pmt_.common_pids.ac3 === pes_data.pid) {
|
|
686
|
+
this.parseAC3Payload(payload, pts);
|
|
687
|
+
} else if (this.pmt_.common_pids.eac3 === pes_data.pid) {
|
|
688
|
+
this.parseEAC3Payload(payload, pts);
|
|
689
|
+
} else if (this.pmt_.asynchronous_klv_pids[pes_data.pid]) {
|
|
690
|
+
this.parseAsynchronousKLVMetadataPayload(payload, pes_data.pid, stream_id);
|
|
691
|
+
} else if (this.pmt_.smpte2038_pids[pes_data.pid]) {
|
|
692
|
+
this.parseSMPTE2038MetadataPayload(payload, pts, dts, pes_data.pid, stream_id);
|
|
693
|
+
} else {
|
|
694
|
+
this.parsePESPrivateDataPayload(payload, pts, dts, pes_data.pid, stream_id);
|
|
695
|
+
}
|
|
696
|
+
break;
|
|
697
|
+
case StreamType.kADTSAAC:
|
|
698
|
+
this.parseADTSAACPayload(payload, pts);
|
|
699
|
+
break;
|
|
700
|
+
case StreamType.kLOASAAC:
|
|
701
|
+
this.parseLOASAACPayload(payload, pts);
|
|
702
|
+
break;
|
|
703
|
+
case StreamType.kAC3:
|
|
704
|
+
this.parseAC3Payload(payload, pts);
|
|
705
|
+
break;
|
|
706
|
+
case StreamType.kEAC3:
|
|
707
|
+
this.parseEAC3Payload(payload, pts);
|
|
708
|
+
break;
|
|
709
|
+
case StreamType.kMetadata:
|
|
710
|
+
if (this.pmt_.timed_id3_pids[pes_data.pid]) {
|
|
711
|
+
this.parseTimedID3MetadataPayload(payload, pts, dts, pes_data.pid, stream_id);
|
|
712
|
+
} else if (this.pmt_.synchronous_klv_pids[pes_data.pid]) {
|
|
713
|
+
this.parseSynchronousKLVMetadataPayload(payload, pts, dts, pes_data.pid, stream_id);
|
|
714
|
+
}
|
|
715
|
+
break;
|
|
716
|
+
case StreamType.kPGS:
|
|
717
|
+
this.parsePGSPayload(payload, pts, dts, pes_data.pid, stream_id, this.pmt_.pgs_langs[pes_data.pid]);
|
|
718
|
+
break;
|
|
719
|
+
case StreamType.kH264:
|
|
720
|
+
this.parseH264Payload(payload, pts, dts, pes_data.file_position, pes_data.random_access_indicator);
|
|
721
|
+
break;
|
|
722
|
+
case StreamType.kH265:
|
|
723
|
+
this.parseH265Payload(payload, pts, dts, pes_data.file_position, pes_data.random_access_indicator);
|
|
724
|
+
break;
|
|
725
|
+
default:
|
|
726
|
+
break;
|
|
727
|
+
}
|
|
728
|
+
} else if (stream_id === 0xBC // program_stream_map
|
|
729
|
+
|| stream_id === 0xBF // private_stream_2
|
|
730
|
+
|| stream_id === 0xF0 // ECM
|
|
731
|
+
|| stream_id === 0xF1 // EMM
|
|
732
|
+
|| stream_id === 0xFF // program_stream_directory
|
|
733
|
+
|| stream_id === 0xF2 // DSMCC_stream
|
|
734
|
+
|| stream_id === 0xF8) { // ITU-T Rec. H.222.1 type E stream
|
|
735
|
+
if (pes_data.stream_type === StreamType.kPESPrivateData) {
|
|
736
|
+
let payload_start_index = 6;
|
|
737
|
+
let payload_length: number;
|
|
738
|
+
|
|
739
|
+
if (PES_packet_length !== 0) {
|
|
740
|
+
payload_length = PES_packet_length;
|
|
741
|
+
} else { // PES_packet_length === 0
|
|
742
|
+
payload_length = data.byteLength - payload_start_index;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
let payload = data.subarray(payload_start_index, payload_start_index + payload_length);
|
|
746
|
+
this.parsePESPrivateDataPayload(payload, undefined, undefined, pes_data.pid, stream_id);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
private parsePAT(data: Uint8Array): void {
|
|
752
|
+
let table_id = data[0];
|
|
753
|
+
if (table_id !== 0x00) {
|
|
754
|
+
Log.e(this.TAG, `parsePAT: table_id ${table_id} is not corresponded to PAT!`);
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
let section_length = ((data[1] & 0x0F) << 8) | data[2];
|
|
759
|
+
|
|
760
|
+
let transport_stream_id = (data[3] << 8) | data[4];
|
|
761
|
+
let version_number = (data[5] & 0x3E) >>> 1;
|
|
762
|
+
let current_next_indicator = data[5] & 0x01;
|
|
763
|
+
let section_number = data[6];
|
|
764
|
+
let last_section_number = data[7];
|
|
765
|
+
|
|
766
|
+
let pat: PAT = null;
|
|
767
|
+
|
|
768
|
+
if (current_next_indicator === 1 && section_number === 0) {
|
|
769
|
+
pat = new PAT();
|
|
770
|
+
pat.version_number = version_number;
|
|
771
|
+
} else {
|
|
772
|
+
pat = this.pat_;
|
|
773
|
+
if (pat == undefined) {
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
let program_start_index = 8;
|
|
779
|
+
let program_bytes = section_length - 5 - 4; // section_length - (headers + crc)
|
|
780
|
+
let first_program_number = -1;
|
|
781
|
+
let first_pmt_pid = -1;
|
|
782
|
+
|
|
783
|
+
for (let i = program_start_index; i < program_start_index + program_bytes; i += 4) {
|
|
784
|
+
let program_number = (data[i] << 8) | data[i + 1];
|
|
785
|
+
let pid = ((data[i + 2] & 0x1F) << 8) | data[i + 3];
|
|
786
|
+
|
|
787
|
+
if (program_number === 0) {
|
|
788
|
+
// network_PID
|
|
789
|
+
pat.network_pid = pid;
|
|
790
|
+
} else {
|
|
791
|
+
// program_map_PID
|
|
792
|
+
pat.program_pmt_pid[program_number] = pid;
|
|
793
|
+
|
|
794
|
+
if (first_program_number === -1) {
|
|
795
|
+
first_program_number = program_number;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
if (first_pmt_pid === -1) {
|
|
799
|
+
first_pmt_pid = pid;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// Currently we only deal with first appeared PMT pid
|
|
805
|
+
if (current_next_indicator === 1 && section_number === 0) {
|
|
806
|
+
if (this.pat_ == undefined) {
|
|
807
|
+
Log.v(this.TAG, `Parsed first PAT: ${JSON.stringify(pat)}`);
|
|
808
|
+
}
|
|
809
|
+
this.pat_ = pat;
|
|
810
|
+
this.current_program_ = first_program_number;
|
|
811
|
+
this.current_pmt_pid_ = first_pmt_pid;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
private parsePMT(data: Uint8Array): void {
|
|
816
|
+
let table_id = data[0];
|
|
817
|
+
if (table_id !== 0x02) {
|
|
818
|
+
Log.e(this.TAG, `parsePMT: table_id ${table_id} is not corresponded to PMT!`);
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
let section_length = ((data[1] & 0x0F) << 8) | data[2];
|
|
823
|
+
|
|
824
|
+
let program_number = (data[3] << 8) | data[4];
|
|
825
|
+
let version_number = (data[5] & 0x3E) >>> 1;
|
|
826
|
+
let current_next_indicator = data[5] & 0x01;
|
|
827
|
+
let section_number = data[6];
|
|
828
|
+
let last_section_number = data[7];
|
|
829
|
+
|
|
830
|
+
let pmt: PMT = null;
|
|
831
|
+
|
|
832
|
+
let existing_pmt = this.program_pmt_map_[program_number];
|
|
833
|
+
if (existing_pmt && existing_pmt.version_number === version_number) {
|
|
834
|
+
if (existing_pmt.parsed_sections && existing_pmt.parsed_sections[section_number]) {
|
|
835
|
+
// Already parsed this section of the PMT
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
let is_new_pmt = false;
|
|
841
|
+
if (current_next_indicator === 1 && section_number === 0) {
|
|
842
|
+
pmt = new PMT();
|
|
843
|
+
pmt.program_number = program_number;
|
|
844
|
+
pmt.version_number = version_number;
|
|
845
|
+
pmt.parsed_sections[section_number] = true;
|
|
846
|
+
this.program_pmt_map_[program_number] = pmt;
|
|
847
|
+
is_new_pmt = true;
|
|
848
|
+
} else {
|
|
849
|
+
pmt = this.program_pmt_map_[program_number];
|
|
850
|
+
if (pmt == undefined) {
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
pmt.parsed_sections[section_number] = true;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
pmt.pcr_pid = ((data[8] & 0x1F) << 8) | data[9];
|
|
857
|
+
let program_info_length = ((data[10] & 0x0F) << 8) | data[11];
|
|
858
|
+
|
|
859
|
+
let info_start_index = 12 + program_info_length;
|
|
860
|
+
let info_bytes = section_length - 9 - program_info_length - 4;
|
|
861
|
+
|
|
862
|
+
for (let i = info_start_index; i < info_start_index + info_bytes; ) {
|
|
863
|
+
let stream_type = data[i] as StreamType;
|
|
864
|
+
let elementary_PID = ((data[i + 1] & 0x1F) << 8) | data[i + 2];
|
|
865
|
+
let ES_info_length = ((data[i + 3] & 0x0F) << 8) | data[i + 4];
|
|
866
|
+
|
|
867
|
+
pmt.pid_stream_type[elementary_PID] = stream_type;
|
|
868
|
+
|
|
869
|
+
let already_has_video = pmt.common_pids.h264 || pmt.common_pids.h265;
|
|
870
|
+
let already_has_audio = pmt.common_pids.adts_aac || pmt.common_pids.loas_aac || pmt.common_pids.ac3 || pmt.common_pids.eac3 || pmt.common_pids.opus || pmt.common_pids.mp3;
|
|
871
|
+
|
|
872
|
+
// Parse ISO 639 language descriptor (tag 0x0A) from ES_info if present.
|
|
873
|
+
let lang: string | undefined;
|
|
874
|
+
for (let off = i + 5; off < i + 5 + ES_info_length; ) {
|
|
875
|
+
const tag = data[off]; const len = data[off + 1];
|
|
876
|
+
if (tag === 0x0A && len >= 3) {
|
|
877
|
+
lang = String.fromCharCode(data[off+2], data[off+3], data[off+4]).trim();
|
|
878
|
+
}
|
|
879
|
+
off += 2 + len;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
let codec_log = '';
|
|
883
|
+
if (stream_type === StreamType.kH264 && !already_has_video) {
|
|
884
|
+
pmt.common_pids.h264 = elementary_PID;
|
|
885
|
+
} else if (stream_type === StreamType.kH265 && !already_has_video) {
|
|
886
|
+
pmt.common_pids.h265 = elementary_PID;
|
|
887
|
+
} else if (stream_type === StreamType.kADTSAAC) {
|
|
888
|
+
if (!already_has_audio) pmt.common_pids.adts_aac = elementary_PID;
|
|
889
|
+
pmt.all_audio_pids.push({pid: elementary_PID, codec: 'aac', lang});
|
|
890
|
+
codec_log = 'aac';
|
|
891
|
+
} else if (stream_type === StreamType.kLOASAAC) {
|
|
892
|
+
if (!already_has_audio) pmt.common_pids.loas_aac = elementary_PID;
|
|
893
|
+
pmt.all_audio_pids.push({pid: elementary_PID, codec: 'aac-loas', lang});
|
|
894
|
+
codec_log = 'aac-loas';
|
|
895
|
+
} else if (stream_type === StreamType.kAC3) {
|
|
896
|
+
if (!already_has_audio) pmt.common_pids.ac3 = elementary_PID;
|
|
897
|
+
pmt.all_audio_pids.push({pid: elementary_PID, codec: 'ac3', lang});
|
|
898
|
+
codec_log = 'ac3';
|
|
899
|
+
} else if (stream_type === StreamType.kEAC3) {
|
|
900
|
+
if (!already_has_audio) pmt.common_pids.eac3 = elementary_PID;
|
|
901
|
+
pmt.all_audio_pids.push({pid: elementary_PID, codec: 'eac3', lang});
|
|
902
|
+
codec_log = 'eac3';
|
|
903
|
+
} else if (stream_type === StreamType.kMPEG1Audio || stream_type === StreamType.kMPEG2Audio) {
|
|
904
|
+
if (!already_has_audio) pmt.common_pids.mp3 = elementary_PID;
|
|
905
|
+
pmt.all_audio_pids.push({pid: elementary_PID, codec: 'mp3', lang});
|
|
906
|
+
codec_log = 'mp3';
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
if (codec_log && is_new_pmt) {
|
|
910
|
+
Log.v(this.TAG, `[muvie] PMT parsing: found audio track (codec=${codec_log}, pid=${elementary_PID}, lang=${lang || 'und'}, is_primary=${!already_has_audio})`);
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
if (stream_type === StreamType.kPESPrivateData) {
|
|
914
|
+
pmt.pes_private_data_pids[elementary_PID] = true;
|
|
915
|
+
if (ES_info_length > 0) {
|
|
916
|
+
// parse descriptor for PES private data
|
|
917
|
+
for (let offset = i + 5; offset < i + 5 + ES_info_length; ) {
|
|
918
|
+
let tag = data[offset + 0];
|
|
919
|
+
let length = data[offset + 1];
|
|
920
|
+
if (tag === 0x05) { // Registration Descriptor
|
|
921
|
+
let registration = String.fromCharCode(... Array.from(data.subarray(offset + 2, offset + 2 + length)));
|
|
922
|
+
|
|
923
|
+
if (registration === 'VANC') {
|
|
924
|
+
pmt.smpte2038_pids[elementary_PID] = true;
|
|
925
|
+
} /* else if (registration === 'AC-3' && !already_has_audio) {
|
|
926
|
+
pmt.common_pids.ac3 = elementary_PID; // DVB AC-3 (FIXME: NEED VERIFY)
|
|
927
|
+
} */ /* else if (registration === 'EC-3' && !alrady_has_audio) {
|
|
928
|
+
pmt.common_pids.eac3 = elementary_PID; // DVB EAC-3 (FIXME: NEED VERIFY)
|
|
929
|
+
} */
|
|
930
|
+
else if (registration === 'AV01') {
|
|
931
|
+
pmt.common_pids.av1 = elementary_PID;
|
|
932
|
+
} else if (registration === 'Opus') {
|
|
933
|
+
pmt.common_pids.opus = elementary_PID;
|
|
934
|
+
} else if (registration === 'KLVA') {
|
|
935
|
+
pmt.asynchronous_klv_pids[elementary_PID] = true;
|
|
936
|
+
}
|
|
937
|
+
} else if (tag === 0x7F) { // DVB extension descriptor
|
|
938
|
+
if (elementary_PID === pmt.common_pids.opus) {
|
|
939
|
+
let ext_desc_tag = data[offset + 2];
|
|
940
|
+
let channel_config_code: number | null = null;
|
|
941
|
+
if (ext_desc_tag === 0x80) { // User defined (provisional Opus)
|
|
942
|
+
channel_config_code = data[offset + 3];
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
if (channel_config_code == null) {
|
|
946
|
+
Log.e(this.TAG, `Not Supported Opus channel count.`);
|
|
947
|
+
continue;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
const meta = {
|
|
951
|
+
codec: 'opus',
|
|
952
|
+
channel_count: (channel_config_code & 0x0F) === 0 ? 2 : (channel_config_code & 0x0F),
|
|
953
|
+
channel_config_code,
|
|
954
|
+
sample_rate: 48000
|
|
955
|
+
} as const;
|
|
956
|
+
const sample = {
|
|
957
|
+
codec: 'opus',
|
|
958
|
+
meta
|
|
959
|
+
} as const;
|
|
960
|
+
|
|
961
|
+
if (this.audio_init_segment_dispatched_ == false) {
|
|
962
|
+
this.audio_metadata_ = meta;
|
|
963
|
+
this.dispatchAudioInitSegment(sample);
|
|
964
|
+
} else if (this.detectAudioMetadataChange(sample)) {
|
|
965
|
+
// flush stashed frames before notify new AudioSpecificConfig
|
|
966
|
+
this.dispatchAudioMediaSegment();
|
|
967
|
+
// notify new AAC AudioSpecificConfig
|
|
968
|
+
this.dispatchAudioInitSegment(sample);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
} else if (tag === 0x80) {
|
|
972
|
+
if (elementary_PID === pmt.common_pids.av1) {
|
|
973
|
+
this.video_metadata_.av1c = data.subarray(offset + 2, offset + 2 + length)
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
offset += 2 + length;
|
|
978
|
+
}
|
|
979
|
+
// provide descriptor for PES private data via callback
|
|
980
|
+
let descriptors = data.subarray(i + 5, i + 5 + ES_info_length);
|
|
981
|
+
this.dispatchPESPrivateDataDescriptor(elementary_PID, stream_type, descriptors);
|
|
982
|
+
}
|
|
983
|
+
} else if (stream_type === StreamType.kMetadata) {
|
|
984
|
+
if (ES_info_length > 0) {
|
|
985
|
+
// parse descriptor for PES private data
|
|
986
|
+
for (let offset = i + 5; offset < i + 5 + ES_info_length; ) {
|
|
987
|
+
let tag = data[offset + 0];
|
|
988
|
+
let length = data[offset + 1];
|
|
989
|
+
|
|
990
|
+
if (tag === 0x26) {
|
|
991
|
+
let metadata_application_format = (data[offset + 2] << 8) | (data[offset + 3] << 0);
|
|
992
|
+
let metadata_application_format_identifier = null;
|
|
993
|
+
if (metadata_application_format === 0xFFFF) {
|
|
994
|
+
metadata_application_format_identifier = String.fromCharCode(... Array.from(data.subarray(offset + 4, offset + 4 + 4)));
|
|
995
|
+
}
|
|
996
|
+
let metadata_format = data[offset + 4 + (metadata_application_format === 0xFFFF ? 4 : 0)];
|
|
997
|
+
let metadata_format_identifier = null;
|
|
998
|
+
if (metadata_format === 0xFF) {
|
|
999
|
+
let pad = 4 + (metadata_application_format === 0xFFFF ? 4 : 0) + 1;
|
|
1000
|
+
metadata_format_identifier = String.fromCharCode(... Array.from(data.subarray(offset + pad, offset + pad + 4)));
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
if (metadata_application_format_identifier === 'ID3 ' && metadata_format_identifier === 'ID3 ') {
|
|
1004
|
+
pmt.timed_id3_pids[elementary_PID] = true;
|
|
1005
|
+
} else if (metadata_format_identifier === 'KLVA') {
|
|
1006
|
+
pmt.synchronous_klv_pids[elementary_PID] = true;
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
offset += 2 + length;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
} else if (stream_type === StreamType.kSCTE35) {
|
|
1014
|
+
pmt.scte_35_pids[elementary_PID] = true;
|
|
1015
|
+
} else if (stream_type === StreamType.kPGS) {
|
|
1016
|
+
pmt.pgs_langs[elementary_PID] = 'und';
|
|
1017
|
+
if (ES_info_length > 0) {
|
|
1018
|
+
// parse descriptor
|
|
1019
|
+
for (let offset = i + 5; offset < i + 5 + ES_info_length; ) {
|
|
1020
|
+
let tag = data[offset + 0];
|
|
1021
|
+
let length = data[offset + 1];
|
|
1022
|
+
if (tag === 0x0a) { // ISO_639_LANGUAGE_DESCRIPTOR
|
|
1023
|
+
const lang = String.fromCharCode(... Array.from(data.slice(offset + 2, offset + 5)));
|
|
1024
|
+
pmt.pgs_langs[elementary_PID] = lang;
|
|
1025
|
+
}
|
|
1026
|
+
offset += 2 + length;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
pmt.pgs_pids[elementary_PID] = true;
|
|
1030
|
+
pmt.subtitle_pids.push({pid: elementary_PID, type: 'pgs', lang: pmt.pgs_langs[elementary_PID]});
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
i += 5 + ES_info_length;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
if (program_number === this.current_program_) {
|
|
1037
|
+
if (this.pmt_ == undefined) {
|
|
1038
|
+
Log.v(this.TAG, `Parsed first PMT: ${JSON.stringify(pmt)}`);
|
|
1039
|
+
}
|
|
1040
|
+
this.pmt_ = pmt;
|
|
1041
|
+
if (pmt.common_pids.h264 || pmt.common_pids.h265 || pmt.common_pids.av1) {
|
|
1042
|
+
this.has_video_ = true;
|
|
1043
|
+
}
|
|
1044
|
+
if (pmt.common_pids.adts_aac || pmt.common_pids.loas_aac || pmt.common_pids.ac3 || pmt.common_pids.opus || pmt.common_pids.mp3) {
|
|
1045
|
+
this.has_audio_ = true;
|
|
1046
|
+
}
|
|
1047
|
+
// Also index timed_id3 pids as subtitle tracks
|
|
1048
|
+
for (const pid of Object.keys(pmt.timed_id3_pids).map(Number)) {
|
|
1049
|
+
if (!pmt.subtitle_pids.find(s => s.pid === pid)) {
|
|
1050
|
+
pmt.subtitle_pids.push({pid, type: 'timed_id3'});
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
// Auto-fallback: if primary audio codec is AC-3/E-AC3 (unsupported by browser MSE),
|
|
1054
|
+
// switch to the first compatible alternative (AAC/MP3) without user intervention.
|
|
1055
|
+
if (is_new_pmt && (pmt.common_pids.ac3 || pmt.common_pids.eac3)) {
|
|
1056
|
+
const unsupportedCodec = pmt.common_pids.ac3 ? 'ac-3' : 'ec-3';
|
|
1057
|
+
// Check explicit config preference first, then stored active_audio_pid_, then first compatible
|
|
1058
|
+
const cfgPref = this.config_?.preferredAudioPid;
|
|
1059
|
+
let fallback = undefined;
|
|
1060
|
+
if (cfgPref && pmt.all_audio_pids.some(a => a.pid === cfgPref)) {
|
|
1061
|
+
fallback = pmt.all_audio_pids.find(a => a.pid === cfgPref);
|
|
1062
|
+
} else if (this.active_audio_pid_ && pmt.all_audio_pids.some(a => a.pid === this.active_audio_pid_)) {
|
|
1063
|
+
fallback = pmt.all_audio_pids.find(a => a.pid === this.active_audio_pid_);
|
|
1064
|
+
}
|
|
1065
|
+
if (!fallback) {
|
|
1066
|
+
fallback = pmt.all_audio_pids.find(a => a.codec === 'aac' || a.codec === 'mp3');
|
|
1067
|
+
}
|
|
1068
|
+
if (fallback) {
|
|
1069
|
+
Log.w(this.TAG, `[muvie] Primary audio is ${unsupportedCodec} (browser-incompatible) — auto-fallback to ${fallback.codec} pid=${fallback.pid} lang=${fallback.lang || 'und'}`);
|
|
1070
|
+
// Clear the unsupported codec pid so audio data on that pid is ignored
|
|
1071
|
+
pmt.common_pids.ac3 = 0;
|
|
1072
|
+
pmt.common_pids.eac3 = 0;
|
|
1073
|
+
this.selectAudioPid(fallback.pid);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
// Emit available track info to the player layer
|
|
1077
|
+
if (is_new_pmt && this.onTracksUpdated && (pmt.all_audio_pids.length > 1 || pmt.subtitle_pids.length > 0)) {
|
|
1078
|
+
this.onTracksUpdated({
|
|
1079
|
+
audioTracks: pmt.all_audio_pids,
|
|
1080
|
+
subtitleTracks: pmt.subtitle_pids,
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
private parseSCTE35(data: Uint8Array): void {
|
|
1087
|
+
const scte35 = readSCTE35(data);
|
|
1088
|
+
|
|
1089
|
+
if (scte35.pts != undefined) {
|
|
1090
|
+
let pts_ms = Math.floor(scte35.pts / this.timescale_);
|
|
1091
|
+
scte35.pts = pts_ms;
|
|
1092
|
+
} else {
|
|
1093
|
+
scte35.nearest_pts = this.getNearestTimestampMilliseconds();
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
if (this.onSCTE35Metadata) {
|
|
1097
|
+
this.onSCTE35Metadata(scte35);
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
private parseAV1Payload(data: Uint8Array, pts: number, dts: number, file_position: number, random_access_indicator: number) {
|
|
1102
|
+
let av1_in_ts_parser = new AV1OBUInMpegTsParser(data);
|
|
1103
|
+
let payload: Uint8Array | null = null;
|
|
1104
|
+
let units: {data: Uint8Array}[] = [];
|
|
1105
|
+
let length = 0;
|
|
1106
|
+
let keyframe = false;
|
|
1107
|
+
|
|
1108
|
+
let details = null;
|
|
1109
|
+
while ((payload = av1_in_ts_parser.readNextOBUPayload()) != null) {
|
|
1110
|
+
details = AV1OBUParser.parseOBUs(payload, this.video_metadata_.details);
|
|
1111
|
+
|
|
1112
|
+
if (details && details.keyframe === true) {
|
|
1113
|
+
if (!this.video_init_segment_dispatched_) {
|
|
1114
|
+
const av1c = new Uint8Array((new ArrayBuffer(this.video_metadata_.av1c.byteLength + details.sequence_header_data.byteLength)));
|
|
1115
|
+
av1c.set(this.video_metadata_.av1c, 0);
|
|
1116
|
+
av1c.set(details.sequence_header_data, this.video_metadata_.av1c.byteLength);
|
|
1117
|
+
details.av1c = av1c;
|
|
1118
|
+
|
|
1119
|
+
this.video_metadata_.details = details;
|
|
1120
|
+
this.dispatchVideoInitSegment();
|
|
1121
|
+
} else if (this.detectVideoMetadataChange(null, details) === true) {
|
|
1122
|
+
this.video_metadata_changed_ = true;
|
|
1123
|
+
// flush stashed frames before changing codec metadata
|
|
1124
|
+
this.dispatchVideoMediaSegment();
|
|
1125
|
+
|
|
1126
|
+
const av1c = new Uint8Array((new ArrayBuffer(this.video_metadata_.av1c.byteLength + details.sequence_header_data.byteLength)));
|
|
1127
|
+
av1c.set(this.video_metadata_.av1c, 0);
|
|
1128
|
+
av1c.set(details.sequence_header_data, this.video_metadata_.av1c.byteLength);
|
|
1129
|
+
details.av1c = av1c;
|
|
1130
|
+
// notify new codec metadata (maybe changed)
|
|
1131
|
+
this.dispatchVideoInitSegment();
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
this.video_metadata_.details = details;
|
|
1135
|
+
|
|
1136
|
+
//if (this.video_init_segment_dispatched_) {
|
|
1137
|
+
if (this.video_init_segment_dispatched_ && details?.keyframe) this.video_keyframe_seen_after_init_ = true;
|
|
1138
|
+
keyframe ||= details.keyframe;
|
|
1139
|
+
units.push({ data: payload });
|
|
1140
|
+
length += payload.byteLength;
|
|
1141
|
+
//}
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
let pts_ms = Math.floor(pts / this.timescale_);
|
|
1145
|
+
let dts_ms = Math.floor(dts / this.timescale_);
|
|
1146
|
+
|
|
1147
|
+
if (units.length) {
|
|
1148
|
+
let track = this.video_track_;
|
|
1149
|
+
let av1_sample = {
|
|
1150
|
+
units,
|
|
1151
|
+
length,
|
|
1152
|
+
isKeyframe: keyframe,
|
|
1153
|
+
dts: dts_ms,
|
|
1154
|
+
pts: pts_ms,
|
|
1155
|
+
cts: pts_ms - dts_ms,
|
|
1156
|
+
file_position
|
|
1157
|
+
};
|
|
1158
|
+
track.samples.push(av1_sample);
|
|
1159
|
+
track.length += length;
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
private parseH264Payload(data: Uint8Array, pts: number, dts: number, file_position: number, random_access_indicator: number) {
|
|
1164
|
+
let annexb_parser = new H264AnnexBParser(data);
|
|
1165
|
+
let nalu_payload: H264NaluPayload = null;
|
|
1166
|
+
let units: {type: H264NaluType, data: Uint8Array}[] = [];
|
|
1167
|
+
let length = 0;
|
|
1168
|
+
let keyframe = false;
|
|
1169
|
+
|
|
1170
|
+
while ((nalu_payload = annexb_parser.readNextNaluPayload()) != null) {
|
|
1171
|
+
let nalu_avc1 = new H264NaluAVC1(nalu_payload);
|
|
1172
|
+
|
|
1173
|
+
if (nalu_avc1.type === H264NaluType.kSliceSPS) {
|
|
1174
|
+
// Notice: parseSPS requires Nalu without startcode or length-header
|
|
1175
|
+
let details = SPSParser.parseSPS(nalu_payload.data);
|
|
1176
|
+
if (!this.video_init_segment_dispatched_) {
|
|
1177
|
+
this.video_metadata_.sps = nalu_avc1;
|
|
1178
|
+
this.video_metadata_.details = details;
|
|
1179
|
+
} else if (this.detectVideoMetadataChange(nalu_avc1, details) === true) {
|
|
1180
|
+
Log.v(this.TAG, `H264: Critical h264 metadata has been changed, attempt to re-generate InitSegment`);
|
|
1181
|
+
this.video_metadata_changed_ = true;
|
|
1182
|
+
this.video_metadata_ = {vps: undefined, sps: nalu_avc1, pps: undefined, av1c: undefined, details: details};
|
|
1183
|
+
}
|
|
1184
|
+
} else if (nalu_avc1.type === H264NaluType.kSlicePPS) {
|
|
1185
|
+
if (!this.video_init_segment_dispatched_ || this.video_metadata_changed_) {
|
|
1186
|
+
this.video_metadata_.pps = nalu_avc1;
|
|
1187
|
+
if (this.video_metadata_.sps && this.video_metadata_.pps) {
|
|
1188
|
+
if (this.video_metadata_changed_) {
|
|
1189
|
+
// flush stashed frames before changing codec metadata
|
|
1190
|
+
this.dispatchVideoMediaSegment();
|
|
1191
|
+
}
|
|
1192
|
+
// notify new codec metadata (maybe changed)
|
|
1193
|
+
this.dispatchVideoInitSegment();
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
} else if (nalu_avc1.type === H264NaluType.kSliceIDR) {
|
|
1197
|
+
keyframe = true;
|
|
1198
|
+
} else if (nalu_avc1.type === H264NaluType.kSliceNonIDR && random_access_indicator === 1) {
|
|
1199
|
+
// For open-gop stream, use random_access_indicator to identify keyframe
|
|
1200
|
+
keyframe = true;
|
|
1201
|
+
} else if (nalu_avc1.type === H264NaluType.kSliceSEI) {
|
|
1202
|
+
this.parseSEIPayload(nalu_payload.data, pts, 'h264');
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
// Push samples to remuxer only if initialization metadata has been dispatched
|
|
1206
|
+
if (this.video_init_segment_dispatched_) {
|
|
1207
|
+
if (keyframe && !this.video_keyframe_seen_after_init_) {
|
|
1208
|
+
Log.v(this.TAG, `[muvie] H264: first IDR keyframe seen after init dispatch — media segments will now flow`);
|
|
1209
|
+
this.video_keyframe_seen_after_init_ = true;
|
|
1210
|
+
} else if (keyframe) {
|
|
1211
|
+
this.video_keyframe_seen_after_init_ = true;
|
|
1212
|
+
}
|
|
1213
|
+
units.push(nalu_avc1);
|
|
1214
|
+
length += nalu_avc1.data.byteLength;
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
let pts_ms = Math.floor(pts / this.timescale_);
|
|
1219
|
+
let dts_ms = Math.floor(dts / this.timescale_);
|
|
1220
|
+
|
|
1221
|
+
if (units.length) {
|
|
1222
|
+
let track = this.video_track_;
|
|
1223
|
+
let avc_sample = {
|
|
1224
|
+
units,
|
|
1225
|
+
length,
|
|
1226
|
+
isKeyframe: keyframe,
|
|
1227
|
+
dts: dts_ms,
|
|
1228
|
+
pts: pts_ms,
|
|
1229
|
+
cts: pts_ms - dts_ms,
|
|
1230
|
+
file_position
|
|
1231
|
+
};
|
|
1232
|
+
track.samples.push(avc_sample);
|
|
1233
|
+
track.length += length;
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
private parseH265Payload(data: Uint8Array, pts: number, dts: number, file_position: number, random_access_indicator: number) {
|
|
1238
|
+
let annexb_parser = new H265AnnexBParser(data);
|
|
1239
|
+
let nalu_payload: H265NaluPayload = null;
|
|
1240
|
+
let units: {type: H265NaluType, data: Uint8Array}[] = [];
|
|
1241
|
+
let length = 0;
|
|
1242
|
+
let keyframe = false;
|
|
1243
|
+
|
|
1244
|
+
while ((nalu_payload = annexb_parser.readNextNaluPayload()) != null) {
|
|
1245
|
+
let nalu_hvc1 = new H265NaluHVC1(nalu_payload);
|
|
1246
|
+
|
|
1247
|
+
if (nalu_hvc1.type === H265NaluType.kSliceVPS) {
|
|
1248
|
+
if (!this.video_init_segment_dispatched_) {
|
|
1249
|
+
let details = H265Parser.parseVPS(nalu_payload.data);
|
|
1250
|
+
this.video_metadata_.vps = nalu_hvc1;
|
|
1251
|
+
this.video_metadata_.details = {
|
|
1252
|
+
... this.video_metadata_.details,
|
|
1253
|
+
... details
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
} else if (nalu_hvc1.type === H265NaluType.kSliceSPS) {
|
|
1257
|
+
let details = H265Parser.parseSPS(nalu_payload.data);
|
|
1258
|
+
if (!this.video_init_segment_dispatched_) {
|
|
1259
|
+
this.video_metadata_.sps = nalu_hvc1;
|
|
1260
|
+
this.video_metadata_.details = {
|
|
1261
|
+
... this.video_metadata_.details,
|
|
1262
|
+
... details
|
|
1263
|
+
};
|
|
1264
|
+
} else if (this.detectVideoMetadataChange(nalu_hvc1, details) === true) {
|
|
1265
|
+
Log.v(this.TAG, `H265: Critical h265 metadata has been changed, attempt to re-generate InitSegment`);
|
|
1266
|
+
this.video_metadata_changed_ = true;
|
|
1267
|
+
this.video_metadata_ = { vps: undefined, sps: nalu_hvc1, pps: undefined, av1c: undefined, details: details};
|
|
1268
|
+
}
|
|
1269
|
+
} else if (nalu_hvc1.type === H265NaluType.kSlicePPS) {
|
|
1270
|
+
if (!this.video_init_segment_dispatched_ || this.video_metadata_changed_) {
|
|
1271
|
+
let details = H265Parser.parsePPS(nalu_payload.data);
|
|
1272
|
+
this.video_metadata_.pps = nalu_hvc1;
|
|
1273
|
+
this.video_metadata_.details = {
|
|
1274
|
+
... this.video_metadata_.details,
|
|
1275
|
+
... details
|
|
1276
|
+
};
|
|
1277
|
+
|
|
1278
|
+
if (this.video_metadata_.vps && this.video_metadata_.sps && this.video_metadata_.pps) {
|
|
1279
|
+
if (this.video_metadata_changed_) {
|
|
1280
|
+
// flush stashed frames before changing codec metadata
|
|
1281
|
+
this.dispatchVideoMediaSegment();
|
|
1282
|
+
}
|
|
1283
|
+
// notify new codec metadata (maybe changed)
|
|
1284
|
+
this.dispatchVideoInitSegment();
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
} else if (nalu_hvc1.type === H265NaluType.kSliceIDR_W_RADL || nalu_hvc1.type === H265NaluType.kSliceIDR_N_LP || nalu_hvc1.type === H265NaluType.kSliceCRA_NUT) {
|
|
1288
|
+
keyframe = true;
|
|
1289
|
+
} else if (nalu_hvc1.type === H265NaluType.kSliceSEI || nalu_hvc1.type === H265NaluType.kSliceSEISuffix) {
|
|
1290
|
+
this.parseSEIPayload(nalu_payload.data, pts, 'h265');
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
// Push samples to remuxer only if initialization metadata has been dispatched
|
|
1294
|
+
if (this.video_init_segment_dispatched_) {
|
|
1295
|
+
if (keyframe && !this.video_keyframe_seen_after_init_) {
|
|
1296
|
+
Log.v(this.TAG, `[muvie] H265: first keyframe seen after init dispatch — media segments will now flow`);
|
|
1297
|
+
this.video_keyframe_seen_after_init_ = true;
|
|
1298
|
+
} else if (keyframe) {
|
|
1299
|
+
this.video_keyframe_seen_after_init_ = true;
|
|
1300
|
+
}
|
|
1301
|
+
units.push(nalu_hvc1);
|
|
1302
|
+
length += nalu_hvc1.data.byteLength;
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
let pts_ms = Math.floor(pts / this.timescale_);
|
|
1307
|
+
let dts_ms = Math.floor(dts / this.timescale_);
|
|
1308
|
+
|
|
1309
|
+
if (units.length) {
|
|
1310
|
+
let track = this.video_track_;
|
|
1311
|
+
let hvc_sample = {
|
|
1312
|
+
units,
|
|
1313
|
+
length,
|
|
1314
|
+
isKeyframe: keyframe,
|
|
1315
|
+
dts: dts_ms,
|
|
1316
|
+
pts: pts_ms,
|
|
1317
|
+
cts: pts_ms - dts_ms,
|
|
1318
|
+
file_position
|
|
1319
|
+
};
|
|
1320
|
+
track.samples.push(hvc_sample);
|
|
1321
|
+
track.length += length;
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
private detectVideoMetadataChange(new_sps: H264NaluAVC1 | H265NaluHVC1, new_details: any): boolean {
|
|
1326
|
+
if (new_details.codec_mimetype !== this.video_metadata_.details.codec_mimetype) {
|
|
1327
|
+
Log.v(this.TAG, `Video: Codec mimeType changed from ` +
|
|
1328
|
+
`${this.video_metadata_.details.codec_mimetype} to ${new_details.codec_mimetype}`);
|
|
1329
|
+
return true;
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
if (new_details.codec_size.width !== this.video_metadata_.details.codec_size.width
|
|
1333
|
+
|| new_details.codec_size.height !== this.video_metadata_.details.codec_size.height) {
|
|
1334
|
+
let old_size = this.video_metadata_.details.codec_size;
|
|
1335
|
+
let new_size = new_details.codec_size;
|
|
1336
|
+
Log.v(this.TAG, `Video: Coded Resolution changed from ` +
|
|
1337
|
+
`${old_size.width}x${old_size.height} to ${new_size.width}x${new_size.height}`);
|
|
1338
|
+
return true;
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
if (new_details.present_size.width !== this.video_metadata_.details.present_size.width) {
|
|
1342
|
+
Log.v(this.TAG, `Video: Present resolution width changed from ` +
|
|
1343
|
+
`${this.video_metadata_.details.present_size.width} to ${new_details.present_size.width}`);
|
|
1344
|
+
return true;
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
return false;
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
private isInitSegmentDispatched(): boolean {
|
|
1351
|
+
if (this.has_video_ && this.has_audio_) { // both video & audio
|
|
1352
|
+
return this.video_init_segment_dispatched_ && this.audio_init_segment_dispatched_;
|
|
1353
|
+
}
|
|
1354
|
+
if (this.has_video_ && !this.has_audio_) { // video only
|
|
1355
|
+
return this.video_init_segment_dispatched_;
|
|
1356
|
+
}
|
|
1357
|
+
if (!this.has_video_ && this.has_audio_) { // audio only
|
|
1358
|
+
return this.audio_init_segment_dispatched_;
|
|
1359
|
+
}
|
|
1360
|
+
return false;
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
private dispatchVideoInitSegment() {
|
|
1364
|
+
let details = this.video_metadata_.details;
|
|
1365
|
+
let meta: any = {};
|
|
1366
|
+
|
|
1367
|
+
meta.type = 'video';
|
|
1368
|
+
meta.id = this.video_track_.id;
|
|
1369
|
+
meta.timescale = 1000;
|
|
1370
|
+
meta.duration = this.duration_;
|
|
1371
|
+
|
|
1372
|
+
meta.codecWidth = details.codec_size.width;
|
|
1373
|
+
meta.codecHeight = details.codec_size.height;
|
|
1374
|
+
meta.presentWidth = details.present_size.width;
|
|
1375
|
+
meta.presentHeight = details.present_size.height;
|
|
1376
|
+
|
|
1377
|
+
meta.profile = details.profile_string;
|
|
1378
|
+
meta.level = details.level_string;
|
|
1379
|
+
meta.bitDepth = details.bit_depth;
|
|
1380
|
+
meta.chromaFormat = details.chroma_format;
|
|
1381
|
+
meta.sarRatio = details.sar_ratio;
|
|
1382
|
+
meta.frameRate = details.frame_rate;
|
|
1383
|
+
|
|
1384
|
+
let fps_den = meta.frameRate.fps_den;
|
|
1385
|
+
let fps_num = meta.frameRate.fps_num;
|
|
1386
|
+
meta.refSampleDuration = 1000 * (fps_den / fps_num);
|
|
1387
|
+
|
|
1388
|
+
meta.codec = details.codec_mimetype;
|
|
1389
|
+
|
|
1390
|
+
if (this.video_metadata_.av1c) {
|
|
1391
|
+
meta.av1c = this.video_metadata_.av1c;
|
|
1392
|
+
if (this.video_init_segment_dispatched_ == false) {
|
|
1393
|
+
Log.v(this.TAG, `Generated first AV1 for mimeType: ${meta.codec}`);
|
|
1394
|
+
}
|
|
1395
|
+
} else if (this.video_metadata_.vps) {
|
|
1396
|
+
let vps_without_header = this.video_metadata_.vps.data.subarray(4);
|
|
1397
|
+
let sps_without_header = this.video_metadata_.sps.data.subarray(4);
|
|
1398
|
+
let pps_without_header = this.video_metadata_.pps.data.subarray(4);
|
|
1399
|
+
let hvcc = new HEVCDecoderConfigurationRecord(vps_without_header, sps_without_header, pps_without_header, details);
|
|
1400
|
+
meta.hvcc = hvcc.getData();
|
|
1401
|
+
|
|
1402
|
+
if (this.video_init_segment_dispatched_ == false) {
|
|
1403
|
+
Log.v(this.TAG, `Generated first HEVCDecoderConfigurationRecord for mimeType: ${meta.codec}`);
|
|
1404
|
+
}
|
|
1405
|
+
} else {
|
|
1406
|
+
let sps_without_header = this.video_metadata_.sps.data.subarray(4);
|
|
1407
|
+
let pps_without_header = this.video_metadata_.pps.data.subarray(4);
|
|
1408
|
+
let avcc = new AVCDecoderConfigurationRecord(sps_without_header, pps_without_header, details);
|
|
1409
|
+
meta.avcc = avcc.getData();
|
|
1410
|
+
|
|
1411
|
+
if (this.video_init_segment_dispatched_ == false) {
|
|
1412
|
+
Log.v(this.TAG, `Generated first AVCDecoderConfigurationRecord for mimeType: ${meta.codec}`);
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
this.onTrackMetadata('video', meta);
|
|
1416
|
+
this.video_init_segment_dispatched_ = true;
|
|
1417
|
+
this.video_init_dispatch_time_ = Date.now();
|
|
1418
|
+
this.video_metadata_changed_ = false;
|
|
1419
|
+
|
|
1420
|
+
// notify new MediaInfo
|
|
1421
|
+
let mi = this.media_info_;
|
|
1422
|
+
mi.hasVideo = true;
|
|
1423
|
+
mi.width = meta.codecWidth;
|
|
1424
|
+
mi.height = meta.codecHeight;
|
|
1425
|
+
mi.fps = meta.frameRate.fps;
|
|
1426
|
+
mi.profile = meta.profile;
|
|
1427
|
+
mi.level = meta.level;
|
|
1428
|
+
mi.refFrames = details.ref_frames;
|
|
1429
|
+
mi.chromaFormat = details.chroma_format_string;
|
|
1430
|
+
mi.sarNum = meta.sarRatio.width;
|
|
1431
|
+
mi.sarDen = meta.sarRatio.height;
|
|
1432
|
+
mi.videoCodec = meta.codec;
|
|
1433
|
+
|
|
1434
|
+
if (mi.hasAudio && mi.audioCodec) {
|
|
1435
|
+
mi.mimeType = `video/mp2t; codecs="${mi.videoCodec},${mi.audioCodec}"`;
|
|
1436
|
+
} else {
|
|
1437
|
+
mi.mimeType = `video/mp2t; codecs="${mi.videoCodec}"`;
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
if (mi.isComplete()) {
|
|
1441
|
+
this.onMediaInfo(mi);
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
private dispatchVideoMediaSegment() {
|
|
1446
|
+
if (this.isInitSegmentDispatched()) {
|
|
1447
|
+
if (this.video_track_.length) {
|
|
1448
|
+
this.onDataAvailable(null, this.video_track_);
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
private dispatchAudioMediaSegment() {
|
|
1454
|
+
if (this.isInitSegmentDispatched()) {
|
|
1455
|
+
if (this.audio_track_.length) {
|
|
1456
|
+
this.onDataAvailable(this.audio_track_, null);
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
private stashAudioBeforeVideoInit(codec: StashedAudioPayload['codec'], data: Uint8Array, pts: number) {
|
|
1462
|
+
this.stashed_audio_before_video_init_.push({codec, data, pts});
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
private dispatchAudioVideoMediaSegment() {
|
|
1466
|
+
// Flush any audio that was stashed before video init dispatched.
|
|
1467
|
+
if (this.video_init_segment_dispatched_ && this.stashed_audio_before_video_init_.length > 0) {
|
|
1468
|
+
const count = this.stashed_audio_before_video_init_.length;
|
|
1469
|
+
Log.v(this.TAG, `[muvie] Flushing ${count} stashed audio payload(s) now that video init is dispatched`);
|
|
1470
|
+
const stash = this.stashed_audio_before_video_init_;
|
|
1471
|
+
this.stashed_audio_before_video_init_ = [];
|
|
1472
|
+
for (const item of stash) {
|
|
1473
|
+
switch (item.codec) {
|
|
1474
|
+
case 'aac':
|
|
1475
|
+
this.parseADTSAACPayload(item.data, item.pts);
|
|
1476
|
+
break;
|
|
1477
|
+
case 'aac-loas':
|
|
1478
|
+
this.parseLOASAACPayload(item.data, item.pts);
|
|
1479
|
+
break;
|
|
1480
|
+
case 'ac-3':
|
|
1481
|
+
this.parseAC3Payload(item.data, item.pts);
|
|
1482
|
+
break;
|
|
1483
|
+
case 'ec-3':
|
|
1484
|
+
this.parseEAC3Payload(item.data, item.pts);
|
|
1485
|
+
break;
|
|
1486
|
+
case 'opus':
|
|
1487
|
+
this.parseOpusPayload(item.data, item.pts);
|
|
1488
|
+
break;
|
|
1489
|
+
case 'mp3':
|
|
1490
|
+
this.parseMP3Payload(item.data, item.pts);
|
|
1491
|
+
break;
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
const initReady = this.isInitSegmentDispatched();
|
|
1496
|
+
if (!initReady) {
|
|
1497
|
+
const reason = `waiting for init (video=${this.video_init_segment_dispatched_} audio=${this.audio_init_segment_dispatched_})`;
|
|
1498
|
+
if (this._last_dispatch_block_reason_ !== reason) {
|
|
1499
|
+
this._last_dispatch_block_reason_ = reason;
|
|
1500
|
+
Log.v(this.TAG, `[muvie] dispatchAV blocked: ${reason}`);
|
|
1501
|
+
}
|
|
1502
|
+
return;
|
|
1503
|
+
}
|
|
1504
|
+
// If a keyframe hasn't been flagged, scan the current video track samples —
|
|
1505
|
+
// the IDR NALU may have arrived without setting the flag (e.g. cross-chunk PES).
|
|
1506
|
+
if (!this.video_keyframe_seen_after_init_ && this.video_track_.samples.length > 0) {
|
|
1507
|
+
const hasKeyframeSample = (this.video_track_.samples as any[]).some((s: any) => s.isKeyframe);
|
|
1508
|
+
if (hasKeyframeSample) {
|
|
1509
|
+
Log.v(this.TAG, `[muvie] H264: keyframe found in queued samples via retrospective scan — unblocking`);
|
|
1510
|
+
this.video_keyframe_seen_after_init_ = true;
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
// Fallback: if both inits are ready but we've waited >6s for a keyframe,
|
|
1514
|
+
// force-dispatch so the decoder can at least try (handles Linux Chrome strict MSE).
|
|
1515
|
+
if (!this.video_keyframe_seen_after_init_ && this.video_init_dispatch_time_ > 0) {
|
|
1516
|
+
const elapsed = Date.now() - this.video_init_dispatch_time_;
|
|
1517
|
+
if (elapsed > 6000 && (this.audio_track_.length || this.video_track_.length)) {
|
|
1518
|
+
Log.w(this.TAG, `[muvie] dispatchAV: no keyframe seen after ${elapsed}ms — force-dispatching (fallback for strict MSE decoders)`);
|
|
1519
|
+
this.video_keyframe_seen_after_init_ = true;
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
const keyframeReady = this.video_keyframe_seen_after_init_;
|
|
1523
|
+
if (!keyframeReady) {
|
|
1524
|
+
const reason = 'waiting for first keyframe after init';
|
|
1525
|
+
if (this._last_dispatch_block_reason_ !== reason) {
|
|
1526
|
+
this._last_dispatch_block_reason_ = reason;
|
|
1527
|
+
Log.v(this.TAG, `[muvie] dispatchAV blocked: ${reason}`);
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
if (keyframeReady) {
|
|
1531
|
+
if (this._last_dispatch_block_reason_) {
|
|
1532
|
+
Log.v(this.TAG, `[muvie] dispatchAV unblocked — media segments now flowing`);
|
|
1533
|
+
this._last_dispatch_block_reason_ = '';
|
|
1534
|
+
}
|
|
1535
|
+
if (this.audio_track_.length || this.video_track_.length) {
|
|
1536
|
+
this.onDataAvailable(this.audio_track_, this.video_track_);
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
private parseADTSAACPayload(data: Uint8Array, pts: number) {
|
|
1542
|
+
if (this.has_video_ && !this.video_init_segment_dispatched_) {
|
|
1543
|
+
// Video init not dispatched yet — stash instead of dropping so audio
|
|
1544
|
+
// init can be dispatched once video init fires (avoids deadlock).
|
|
1545
|
+
this.stashAudioBeforeVideoInit('aac', data, pts);
|
|
1546
|
+
return;
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
if (this.aac_last_incomplete_data_) {
|
|
1550
|
+
let buf = new Uint8Array(data.byteLength + this.aac_last_incomplete_data_.byteLength);
|
|
1551
|
+
buf.set(this.aac_last_incomplete_data_, 0);
|
|
1552
|
+
buf.set(data, this.aac_last_incomplete_data_.byteLength);
|
|
1553
|
+
data = buf;
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
let ref_sample_duration: number;
|
|
1557
|
+
let base_pts_ms: number;
|
|
1558
|
+
|
|
1559
|
+
if (pts != undefined) {
|
|
1560
|
+
base_pts_ms = pts / this.timescale_;
|
|
1561
|
+
}
|
|
1562
|
+
if (this.audio_metadata_.codec === 'aac') {
|
|
1563
|
+
if (pts == undefined && this.audio_last_sample_pts_ != undefined) {
|
|
1564
|
+
ref_sample_duration = 1024 / this.audio_metadata_.sampling_frequency * 1000;
|
|
1565
|
+
base_pts_ms = this.audio_last_sample_pts_ + ref_sample_duration;
|
|
1566
|
+
} else if (pts == undefined){
|
|
1567
|
+
Log.w(this.TAG, `AAC: Unknown pts`);
|
|
1568
|
+
return;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
if (this.aac_last_incomplete_data_ && this.audio_last_sample_pts_) {
|
|
1572
|
+
ref_sample_duration = 1024 / this.audio_metadata_.sampling_frequency * 1000;
|
|
1573
|
+
let new_pts_ms = this.audio_last_sample_pts_ + ref_sample_duration;
|
|
1574
|
+
|
|
1575
|
+
if (Math.abs(new_pts_ms - base_pts_ms) > 1) {
|
|
1576
|
+
Log.w(this.TAG, `AAC: Detected pts overlapped, ` +
|
|
1577
|
+
`expected: ${new_pts_ms}ms, PES pts: ${base_pts_ms}ms`);
|
|
1578
|
+
base_pts_ms = new_pts_ms;
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
let adts_parser = new AACADTSParser(data);
|
|
1584
|
+
let aac_frame: AACFrame = null;
|
|
1585
|
+
let sample_pts_ms = base_pts_ms;
|
|
1586
|
+
let last_sample_pts_ms: number;
|
|
1587
|
+
|
|
1588
|
+
while ((aac_frame = adts_parser.readNextAACFrame()) != null) {
|
|
1589
|
+
ref_sample_duration = 1024 / aac_frame.sampling_frequency * 1000;
|
|
1590
|
+
const audio_sample = {
|
|
1591
|
+
codec: 'aac',
|
|
1592
|
+
data: aac_frame
|
|
1593
|
+
} as const;
|
|
1594
|
+
|
|
1595
|
+
if (this.audio_init_segment_dispatched_ == false) {
|
|
1596
|
+
this.audio_metadata_ = {
|
|
1597
|
+
codec: 'aac',
|
|
1598
|
+
audio_object_type: aac_frame.audio_object_type,
|
|
1599
|
+
sampling_freq_index: aac_frame.sampling_freq_index,
|
|
1600
|
+
sampling_frequency: aac_frame.sampling_frequency,
|
|
1601
|
+
channel_config: aac_frame.channel_config
|
|
1602
|
+
};
|
|
1603
|
+
this.dispatchAudioInitSegment(audio_sample);
|
|
1604
|
+
} else if (this.detectAudioMetadataChange(audio_sample)) {
|
|
1605
|
+
// flush stashed frames before notify new AudioSpecificConfig
|
|
1606
|
+
this.dispatchAudioMediaSegment();
|
|
1607
|
+
// notify new AAC AudioSpecificConfig
|
|
1608
|
+
this.dispatchAudioInitSegment(audio_sample);
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
last_sample_pts_ms = sample_pts_ms;
|
|
1612
|
+
let sample_pts_ms_int = Math.floor(sample_pts_ms);
|
|
1613
|
+
|
|
1614
|
+
let aac_sample = {
|
|
1615
|
+
unit: aac_frame.data,
|
|
1616
|
+
length: aac_frame.data.byteLength,
|
|
1617
|
+
pts: sample_pts_ms_int,
|
|
1618
|
+
dts: sample_pts_ms_int
|
|
1619
|
+
};
|
|
1620
|
+
this.audio_track_.samples.push(aac_sample);
|
|
1621
|
+
this.audio_track_.length += aac_frame.data.byteLength;
|
|
1622
|
+
|
|
1623
|
+
sample_pts_ms += ref_sample_duration;
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
if (adts_parser.hasIncompleteData()) {
|
|
1627
|
+
this.aac_last_incomplete_data_ = adts_parser.getIncompleteData();
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
if (last_sample_pts_ms) {
|
|
1631
|
+
this.audio_last_sample_pts_ = last_sample_pts_ms;
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
private parseLOASAACPayload(data: Uint8Array, pts: number) {
|
|
1636
|
+
if (this.has_video_ && !this.video_init_segment_dispatched_) {
|
|
1637
|
+
// Preserve LOAS AAC until video init is ready so we don't lose
|
|
1638
|
+
// the first audio payloads on fallback streams.
|
|
1639
|
+
this.stashAudioBeforeVideoInit('aac-loas', data, pts);
|
|
1640
|
+
return;
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
if (this.aac_last_incomplete_data_) {
|
|
1644
|
+
let buf = new Uint8Array(data.byteLength + this.aac_last_incomplete_data_.byteLength);
|
|
1645
|
+
buf.set(this.aac_last_incomplete_data_, 0);
|
|
1646
|
+
buf.set(data, this.aac_last_incomplete_data_.byteLength);
|
|
1647
|
+
data = buf;
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
let ref_sample_duration: number;
|
|
1651
|
+
let base_pts_ms: number;
|
|
1652
|
+
|
|
1653
|
+
if (pts != undefined) {
|
|
1654
|
+
base_pts_ms = pts / this.timescale_;
|
|
1655
|
+
}
|
|
1656
|
+
if (this.audio_metadata_.codec === 'aac') {
|
|
1657
|
+
if (pts == undefined && this.audio_last_sample_pts_ != undefined) {
|
|
1658
|
+
ref_sample_duration = 1024 / this.audio_metadata_.sampling_frequency * 1000;
|
|
1659
|
+
base_pts_ms = this.audio_last_sample_pts_ + ref_sample_duration;
|
|
1660
|
+
} else if (pts == undefined){
|
|
1661
|
+
Log.w(this.TAG, `AAC: Unknown pts`);
|
|
1662
|
+
return;
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
if (this.aac_last_incomplete_data_ && this.audio_last_sample_pts_) {
|
|
1666
|
+
ref_sample_duration = 1024 / this.audio_metadata_.sampling_frequency * 1000;
|
|
1667
|
+
let new_pts_ms = this.audio_last_sample_pts_ + ref_sample_duration;
|
|
1668
|
+
|
|
1669
|
+
if (Math.abs(new_pts_ms - base_pts_ms) > 1) {
|
|
1670
|
+
Log.w(this.TAG, `AAC: Detected pts overlapped, ` +
|
|
1671
|
+
`expected: ${new_pts_ms}ms, PES pts: ${base_pts_ms}ms`);
|
|
1672
|
+
base_pts_ms = new_pts_ms;
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
let loas_parser = new AACLOASParser(data);
|
|
1678
|
+
let aac_frame: LOASAACFrame = null;
|
|
1679
|
+
let sample_pts_ms = base_pts_ms;
|
|
1680
|
+
let last_sample_pts_ms: number;
|
|
1681
|
+
|
|
1682
|
+
while ((aac_frame = loas_parser.readNextAACFrame(this.loas_previous_frame ?? undefined)) != null) {
|
|
1683
|
+
this.loas_previous_frame = aac_frame;
|
|
1684
|
+
ref_sample_duration = 1024 / aac_frame.sampling_frequency * 1000;
|
|
1685
|
+
const audio_sample = {
|
|
1686
|
+
codec: 'aac',
|
|
1687
|
+
data: aac_frame
|
|
1688
|
+
} as const;
|
|
1689
|
+
|
|
1690
|
+
if (this.audio_init_segment_dispatched_ == false) {
|
|
1691
|
+
this.audio_metadata_ = {
|
|
1692
|
+
codec: 'aac',
|
|
1693
|
+
audio_object_type: aac_frame.audio_object_type,
|
|
1694
|
+
sampling_freq_index: aac_frame.sampling_freq_index,
|
|
1695
|
+
sampling_frequency: aac_frame.sampling_frequency,
|
|
1696
|
+
channel_config: aac_frame.channel_config
|
|
1697
|
+
};
|
|
1698
|
+
this.dispatchAudioInitSegment(audio_sample);
|
|
1699
|
+
} else if (this.detectAudioMetadataChange(audio_sample)) {
|
|
1700
|
+
// flush stashed frames before notify new AudioSpecificConfig
|
|
1701
|
+
this.dispatchAudioMediaSegment();
|
|
1702
|
+
// notify new AAC AudioSpecificConfig
|
|
1703
|
+
this.dispatchAudioInitSegment(audio_sample);
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
last_sample_pts_ms = sample_pts_ms;
|
|
1707
|
+
let sample_pts_ms_int = Math.floor(sample_pts_ms);
|
|
1708
|
+
|
|
1709
|
+
let aac_sample = {
|
|
1710
|
+
unit: aac_frame.data,
|
|
1711
|
+
length: aac_frame.data.byteLength,
|
|
1712
|
+
pts: sample_pts_ms_int,
|
|
1713
|
+
dts: sample_pts_ms_int
|
|
1714
|
+
};
|
|
1715
|
+
this.audio_track_.samples.push(aac_sample);
|
|
1716
|
+
this.audio_track_.length += aac_frame.data.byteLength;
|
|
1717
|
+
|
|
1718
|
+
sample_pts_ms += ref_sample_duration;
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
if (loas_parser.hasIncompleteData()) {
|
|
1722
|
+
this.aac_last_incomplete_data_ = loas_parser.getIncompleteData();
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
if (last_sample_pts_ms) {
|
|
1726
|
+
this.audio_last_sample_pts_ = last_sample_pts_ms;
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
private parseAC3Payload(data: Uint8Array, pts: number) {
|
|
1731
|
+
if (this.has_video_ && !this.video_init_segment_dispatched_) {
|
|
1732
|
+
this.stashAudioBeforeVideoInit('ac-3', data, pts);
|
|
1733
|
+
return;
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
let ref_sample_duration: number;
|
|
1737
|
+
let base_pts_ms: number;
|
|
1738
|
+
|
|
1739
|
+
if (pts != undefined) {
|
|
1740
|
+
base_pts_ms = pts / this.timescale_;
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
if (this.audio_metadata_.codec === 'ac-3') {
|
|
1744
|
+
if (pts == undefined && this.audio_last_sample_pts_ != undefined) {
|
|
1745
|
+
ref_sample_duration = 1536 / this.audio_metadata_.sampling_frequency * 1000;
|
|
1746
|
+
base_pts_ms = this.audio_last_sample_pts_ + ref_sample_duration;
|
|
1747
|
+
} else if (pts == undefined){
|
|
1748
|
+
Log.w(this.TAG, `AC3: Unknown pts`);
|
|
1749
|
+
return;
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
let adts_parser = new AC3Parser(data);
|
|
1754
|
+
let ac3_frame: AC3Frame = null;
|
|
1755
|
+
let sample_pts_ms = base_pts_ms;
|
|
1756
|
+
let last_sample_pts_ms: number;
|
|
1757
|
+
|
|
1758
|
+
while ((ac3_frame = adts_parser.readNextAC3Frame()) != null) {
|
|
1759
|
+
ref_sample_duration = 1536 / ac3_frame.sampling_frequency * 1000;
|
|
1760
|
+
const audio_sample = {
|
|
1761
|
+
codec: 'ac-3',
|
|
1762
|
+
data: ac3_frame
|
|
1763
|
+
} as const;
|
|
1764
|
+
|
|
1765
|
+
if (this.audio_init_segment_dispatched_ == false) {
|
|
1766
|
+
this.audio_metadata_ = {
|
|
1767
|
+
codec: 'ac-3',
|
|
1768
|
+
sampling_frequency: ac3_frame.sampling_frequency,
|
|
1769
|
+
bit_stream_identification: ac3_frame.bit_stream_identification,
|
|
1770
|
+
bit_stream_mode: ac3_frame.bit_stream_mode,
|
|
1771
|
+
low_frequency_effects_channel_on: ac3_frame.low_frequency_effects_channel_on,
|
|
1772
|
+
channel_mode: ac3_frame.channel_mode,
|
|
1773
|
+
};
|
|
1774
|
+
this.dispatchAudioInitSegment(audio_sample);
|
|
1775
|
+
} else if (this.detectAudioMetadataChange(audio_sample)) {
|
|
1776
|
+
// flush stashed frames before notify new AudioSpecificConfig
|
|
1777
|
+
this.dispatchAudioMediaSegment();
|
|
1778
|
+
// notify new AAC AudioSpecificConfig
|
|
1779
|
+
this.dispatchAudioInitSegment(audio_sample);
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
last_sample_pts_ms = sample_pts_ms;
|
|
1783
|
+
let sample_pts_ms_int = Math.floor(sample_pts_ms);
|
|
1784
|
+
|
|
1785
|
+
let ac3_sample = {
|
|
1786
|
+
unit: ac3_frame.data,
|
|
1787
|
+
length: ac3_frame.data.byteLength,
|
|
1788
|
+
pts: sample_pts_ms_int,
|
|
1789
|
+
dts: sample_pts_ms_int
|
|
1790
|
+
};
|
|
1791
|
+
|
|
1792
|
+
this.audio_track_.samples.push(ac3_sample);
|
|
1793
|
+
this.audio_track_.length += ac3_frame.data.byteLength;
|
|
1794
|
+
|
|
1795
|
+
sample_pts_ms += ref_sample_duration;
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
if (last_sample_pts_ms) {
|
|
1799
|
+
this.audio_last_sample_pts_ = last_sample_pts_ms;
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
private parseEAC3Payload(data: Uint8Array, pts: number) {
|
|
1804
|
+
if (this.has_video_ && !this.video_init_segment_dispatched_) {
|
|
1805
|
+
this.stashAudioBeforeVideoInit('ec-3', data, pts);
|
|
1806
|
+
return;
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
let ref_sample_duration: number;
|
|
1810
|
+
let base_pts_ms: number;
|
|
1811
|
+
|
|
1812
|
+
if (pts != undefined) {
|
|
1813
|
+
base_pts_ms = pts / this.timescale_;
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
if (this.audio_metadata_.codec === 'ec-3') {
|
|
1817
|
+
if (pts == undefined && this.audio_last_sample_pts_ != undefined) {
|
|
1818
|
+
ref_sample_duration = (256 * this.audio_metadata_.num_blks) / this.audio_metadata_.sampling_frequency * 1000; // TODO: AEC3 BLK
|
|
1819
|
+
base_pts_ms = this.audio_last_sample_pts_ + ref_sample_duration;
|
|
1820
|
+
} else if (pts == undefined){
|
|
1821
|
+
Log.w(this.TAG, `EAC3: Unknown pts`);
|
|
1822
|
+
return;
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
let adts_parser = new EAC3Parser(data);
|
|
1827
|
+
let eac3_frame: EAC3Frame = null;
|
|
1828
|
+
let sample_pts_ms = base_pts_ms;
|
|
1829
|
+
let last_sample_pts_ms: number;
|
|
1830
|
+
|
|
1831
|
+
while ((eac3_frame = adts_parser.readNextEAC3Frame()) != null) {
|
|
1832
|
+
ref_sample_duration = 1536 / eac3_frame.sampling_frequency * 1000; // TODO: EAC3 BLK
|
|
1833
|
+
const audio_sample = {
|
|
1834
|
+
codec: 'ec-3',
|
|
1835
|
+
data: eac3_frame
|
|
1836
|
+
} as const;
|
|
1837
|
+
|
|
1838
|
+
if (this.audio_init_segment_dispatched_ == false) {
|
|
1839
|
+
this.audio_metadata_ = {
|
|
1840
|
+
codec: 'ec-3',
|
|
1841
|
+
sampling_frequency: eac3_frame.sampling_frequency,
|
|
1842
|
+
bit_stream_identification: eac3_frame.bit_stream_identification,
|
|
1843
|
+
low_frequency_effects_channel_on: eac3_frame.low_frequency_effects_channel_on,
|
|
1844
|
+
num_blks: eac3_frame.num_blks,
|
|
1845
|
+
channel_mode: eac3_frame.channel_mode,
|
|
1846
|
+
};
|
|
1847
|
+
this.dispatchAudioInitSegment(audio_sample);
|
|
1848
|
+
} else if (this.detectAudioMetadataChange(audio_sample)) {
|
|
1849
|
+
// flush stashed frames before notify new AudioSpecificConfig
|
|
1850
|
+
this.dispatchAudioMediaSegment();
|
|
1851
|
+
// notify new AAC AudioSpecificConfig
|
|
1852
|
+
this.dispatchAudioInitSegment(audio_sample);
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
last_sample_pts_ms = sample_pts_ms;
|
|
1856
|
+
let sample_pts_ms_int = Math.floor(sample_pts_ms);
|
|
1857
|
+
|
|
1858
|
+
let ac3_sample = {
|
|
1859
|
+
unit: eac3_frame.data,
|
|
1860
|
+
length: eac3_frame.data.byteLength,
|
|
1861
|
+
pts: sample_pts_ms_int,
|
|
1862
|
+
dts: sample_pts_ms_int
|
|
1863
|
+
};
|
|
1864
|
+
|
|
1865
|
+
this.audio_track_.samples.push(ac3_sample);
|
|
1866
|
+
this.audio_track_.length += eac3_frame.data.byteLength;
|
|
1867
|
+
|
|
1868
|
+
sample_pts_ms += ref_sample_duration;
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
if (last_sample_pts_ms) {
|
|
1872
|
+
this.audio_last_sample_pts_ = last_sample_pts_ms;
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
private parseOpusPayload(data: Uint8Array, pts: number) {
|
|
1877
|
+
if (this.has_video_ && !this.video_init_segment_dispatched_) {
|
|
1878
|
+
this.stashAudioBeforeVideoInit('opus', data, pts);
|
|
1879
|
+
return;
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
let ref_sample_duration: number;
|
|
1883
|
+
let base_pts_ms: number;
|
|
1884
|
+
|
|
1885
|
+
if (pts != undefined) {
|
|
1886
|
+
base_pts_ms = pts / this.timescale_;
|
|
1887
|
+
}
|
|
1888
|
+
if (this.audio_metadata_.codec === 'opus') {
|
|
1889
|
+
if (pts == undefined && this.audio_last_sample_pts_ != undefined) {
|
|
1890
|
+
ref_sample_duration = 20;
|
|
1891
|
+
base_pts_ms = this.audio_last_sample_pts_ + ref_sample_duration;
|
|
1892
|
+
} else if (pts == undefined){
|
|
1893
|
+
Log.w(this.TAG, `Opus: Unknown pts`);
|
|
1894
|
+
return;
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
let sample_pts_ms = base_pts_ms;
|
|
1899
|
+
let last_sample_pts_ms: number;
|
|
1900
|
+
|
|
1901
|
+
for (let offset = 0; offset < data.length; ) {
|
|
1902
|
+
ref_sample_duration = 20;
|
|
1903
|
+
|
|
1904
|
+
const opus_pending_trim_start = (data[offset + 1] & 0x10) !== 0;
|
|
1905
|
+
const trim_end = (data[offset + 1] & 0x08) !== 0;
|
|
1906
|
+
let index = offset + 2;
|
|
1907
|
+
let size = 0;
|
|
1908
|
+
|
|
1909
|
+
while (data[index] === 0xFF) {
|
|
1910
|
+
size += 255;
|
|
1911
|
+
index += 1;
|
|
1912
|
+
}
|
|
1913
|
+
size += data[index];
|
|
1914
|
+
index += 1;
|
|
1915
|
+
index += opus_pending_trim_start ? 2 : 0;
|
|
1916
|
+
index += trim_end ? 2 : 0;
|
|
1917
|
+
|
|
1918
|
+
last_sample_pts_ms = sample_pts_ms;
|
|
1919
|
+
let sample_pts_ms_int = Math.floor(sample_pts_ms);
|
|
1920
|
+
let sample = data.slice(index, index + size)
|
|
1921
|
+
|
|
1922
|
+
let opus_sample = {
|
|
1923
|
+
unit: sample,
|
|
1924
|
+
length: sample.byteLength,
|
|
1925
|
+
pts: sample_pts_ms_int,
|
|
1926
|
+
dts: sample_pts_ms_int
|
|
1927
|
+
};
|
|
1928
|
+
this.audio_track_.samples.push(opus_sample);
|
|
1929
|
+
this.audio_track_.length += sample.byteLength;
|
|
1930
|
+
|
|
1931
|
+
sample_pts_ms += ref_sample_duration;
|
|
1932
|
+
offset = index + size;
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
if (last_sample_pts_ms) {
|
|
1936
|
+
this.audio_last_sample_pts_ = last_sample_pts_ms;
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
private parseMP3Payload(data: Uint8Array, pts: number) {
|
|
1941
|
+
if (this.has_video_ && !this.video_init_segment_dispatched_) {
|
|
1942
|
+
this.stashAudioBeforeVideoInit('mp3', data, pts);
|
|
1943
|
+
return;
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
let _mpegAudioV10SampleRateTable = [44100, 48000, 32000, 0];
|
|
1947
|
+
let _mpegAudioV20SampleRateTable = [22050, 24000, 16000, 0];
|
|
1948
|
+
let _mpegAudioV25SampleRateTable = [11025, 12000, 8000, 0];
|
|
1949
|
+
let _mpegAudioL1BitRateTable = [0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, -1];
|
|
1950
|
+
let _mpegAudioL2BitRateTable = [0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, -1];
|
|
1951
|
+
let _mpegAudioL3BitRateTable = [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1];
|
|
1952
|
+
|
|
1953
|
+
let ver = (data[1] >>> 3) & 0x03;
|
|
1954
|
+
let layer = (data[1] & 0x06) >> 1;
|
|
1955
|
+
let bitrate_index = (data[2] & 0xF0) >>> 4;
|
|
1956
|
+
let sampling_freq_index = (data[2] & 0x0C) >>> 2;
|
|
1957
|
+
let channel_mode = (data[3] >>> 6) & 0x03;
|
|
1958
|
+
let channel_count = channel_mode !== 3 ? 2 : 1;
|
|
1959
|
+
|
|
1960
|
+
let sample_rate = 0;
|
|
1961
|
+
let bit_rate = 0;
|
|
1962
|
+
let object_type = 34; // Layer-3, listed in MPEG-4 Audio Object Types
|
|
1963
|
+
|
|
1964
|
+
let codec = 'mp3';
|
|
1965
|
+
switch (ver) {
|
|
1966
|
+
case 0: // MPEG 2.5
|
|
1967
|
+
sample_rate = _mpegAudioV25SampleRateTable[sampling_freq_index];
|
|
1968
|
+
break;
|
|
1969
|
+
case 2: // MPEG 2
|
|
1970
|
+
sample_rate = _mpegAudioV20SampleRateTable[sampling_freq_index];
|
|
1971
|
+
break;
|
|
1972
|
+
case 3: // MPEG 1
|
|
1973
|
+
sample_rate = _mpegAudioV10SampleRateTable[sampling_freq_index];
|
|
1974
|
+
break;
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
switch (layer) {
|
|
1978
|
+
case 1: // Layer 3
|
|
1979
|
+
object_type = 34;
|
|
1980
|
+
if (bitrate_index < _mpegAudioL3BitRateTable.length) {
|
|
1981
|
+
bit_rate = _mpegAudioL3BitRateTable[bitrate_index];
|
|
1982
|
+
}
|
|
1983
|
+
break;
|
|
1984
|
+
case 2: // Layer 2
|
|
1985
|
+
object_type = 33;
|
|
1986
|
+
if (bitrate_index < _mpegAudioL2BitRateTable.length) {
|
|
1987
|
+
bit_rate = _mpegAudioL2BitRateTable[bitrate_index];
|
|
1988
|
+
}
|
|
1989
|
+
break;
|
|
1990
|
+
case 3: // Layer 1
|
|
1991
|
+
object_type = 32;
|
|
1992
|
+
if (bitrate_index < _mpegAudioL1BitRateTable.length) {
|
|
1993
|
+
bit_rate = _mpegAudioL1BitRateTable[bitrate_index];
|
|
1994
|
+
}
|
|
1995
|
+
break;
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
const sample = new MP3Data();
|
|
1999
|
+
sample.object_type = object_type;
|
|
2000
|
+
sample.sample_rate = sample_rate;
|
|
2001
|
+
sample.channel_count = channel_count;
|
|
2002
|
+
sample.data = data;
|
|
2003
|
+
const audio_sample = {
|
|
2004
|
+
codec: 'mp3',
|
|
2005
|
+
data: sample
|
|
2006
|
+
} as const;
|
|
2007
|
+
|
|
2008
|
+
|
|
2009
|
+
if (this.audio_init_segment_dispatched_ == false) {
|
|
2010
|
+
this.audio_metadata_ = {
|
|
2011
|
+
codec: 'mp3',
|
|
2012
|
+
object_type,
|
|
2013
|
+
sample_rate,
|
|
2014
|
+
channel_count
|
|
2015
|
+
}
|
|
2016
|
+
this.dispatchAudioInitSegment(audio_sample);
|
|
2017
|
+
} else if (this.detectAudioMetadataChange(audio_sample)) {
|
|
2018
|
+
// flush stashed frames before notify new AudioSpecificConfig
|
|
2019
|
+
this.dispatchAudioMediaSegment();
|
|
2020
|
+
// notify new AAC AudioSpecificConfig
|
|
2021
|
+
this.dispatchAudioInitSegment(audio_sample);
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
let mp3_sample = {
|
|
2025
|
+
unit: data,
|
|
2026
|
+
length: data.byteLength,
|
|
2027
|
+
pts: pts / this.timescale_,
|
|
2028
|
+
dts: pts / this.timescale_
|
|
2029
|
+
};
|
|
2030
|
+
this.audio_track_.samples.push(mp3_sample);
|
|
2031
|
+
this.audio_track_.length += data.byteLength;
|
|
2032
|
+
this.audio_last_sample_pts_ = mp3_sample.pts;
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
private detectAudioMetadataChange(sample: AudioData): boolean {
|
|
2036
|
+
if (sample.codec !== this.audio_metadata_.codec) {
|
|
2037
|
+
Log.v(this.TAG, `Audio: Audio Codecs changed from ` +
|
|
2038
|
+
`${this.audio_metadata_.codec} to ${sample.codec}`);
|
|
2039
|
+
return true;
|
|
2040
|
+
}
|
|
2041
|
+
|
|
2042
|
+
if (sample.codec === 'aac' && this.audio_metadata_.codec === 'aac') {
|
|
2043
|
+
const frame = sample.data;
|
|
2044
|
+
if (frame.audio_object_type !== this.audio_metadata_.audio_object_type) {
|
|
2045
|
+
Log.v(this.TAG, `AAC: AudioObjectType changed from ` +
|
|
2046
|
+
`${this.audio_metadata_.audio_object_type} to ${frame.audio_object_type}`);
|
|
2047
|
+
return true;
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
if (frame.sampling_freq_index !== this.audio_metadata_.sampling_freq_index) {
|
|
2051
|
+
Log.v(this.TAG, `AAC: SamplingFrequencyIndex changed from ` +
|
|
2052
|
+
`${this.audio_metadata_.sampling_freq_index} to ${frame.sampling_freq_index}`);
|
|
2053
|
+
return true;
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
if (frame.channel_config !== this.audio_metadata_.channel_config) {
|
|
2057
|
+
Log.v(this.TAG, `AAC: Channel configuration changed from ` +
|
|
2058
|
+
`${this.audio_metadata_.channel_config} to ${frame.channel_config}`);
|
|
2059
|
+
return true;
|
|
2060
|
+
}
|
|
2061
|
+
} else if (sample.codec === 'ac-3' && this.audio_metadata_.codec === 'ac-3') {
|
|
2062
|
+
const frame = sample.data;
|
|
2063
|
+
if (frame.sampling_frequency !== this.audio_metadata_.sampling_frequency) {
|
|
2064
|
+
Log.v(this.TAG, `AC3: Sampling Frequency changed from ` +
|
|
2065
|
+
`${this.audio_metadata_.sampling_frequency} to ${frame.sampling_frequency}`);
|
|
2066
|
+
return true;
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
if (frame.bit_stream_identification !== this.audio_metadata_.bit_stream_identification) {
|
|
2070
|
+
Log.v(this.TAG, `AC3: Bit Stream Identification changed from ` +
|
|
2071
|
+
`${this.audio_metadata_.bit_stream_identification} to ${frame.bit_stream_identification}`);
|
|
2072
|
+
return true;
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
if (frame.bit_stream_mode !== this.audio_metadata_.bit_stream_mode) {
|
|
2076
|
+
Log.v(this.TAG, `AC3: BitStream Mode changed from ` +
|
|
2077
|
+
`${this.audio_metadata_.bit_stream_mode} to ${frame.bit_stream_mode}`);
|
|
2078
|
+
return true;
|
|
2079
|
+
}
|
|
2080
|
+
|
|
2081
|
+
if (frame.channel_mode !== this.audio_metadata_.channel_mode) {
|
|
2082
|
+
Log.v(this.TAG, `AC3: Channel Mode changed from ` +
|
|
2083
|
+
`${this.audio_metadata_.channel_mode} to ${frame.channel_mode}`);
|
|
2084
|
+
return true;
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
if (frame.low_frequency_effects_channel_on !== this.audio_metadata_.low_frequency_effects_channel_on) {
|
|
2088
|
+
Log.v(this.TAG, `AC3: Low Frequency Effects Channel On changed from ` +
|
|
2089
|
+
`${this.audio_metadata_.low_frequency_effects_channel_on} to ${frame.low_frequency_effects_channel_on}`);
|
|
2090
|
+
return true;
|
|
2091
|
+
}
|
|
2092
|
+
} else if (sample.codec === 'opus' && this.audio_metadata_.codec === 'opus') {
|
|
2093
|
+
const data = sample.meta;
|
|
2094
|
+
|
|
2095
|
+
if (data.sample_rate !== this.audio_metadata_.sample_rate) {
|
|
2096
|
+
Log.v(this.TAG, `Opus: SamplingFrequencyIndex changed from ` +
|
|
2097
|
+
`${this.audio_metadata_.sample_rate} to ${data.sample_rate}`);
|
|
2098
|
+
return true;
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
if (data.channel_count !== this.audio_metadata_.channel_count) {
|
|
2102
|
+
Log.v(this.TAG, `Opus: Channel count changed from ` +
|
|
2103
|
+
`${this.audio_metadata_.channel_count} to ${data.channel_count}`);
|
|
2104
|
+
return true;
|
|
2105
|
+
}
|
|
2106
|
+
} else if (sample.codec === 'mp3' && this.audio_metadata_.codec === 'mp3') {
|
|
2107
|
+
const data = sample.data;
|
|
2108
|
+
if (data.object_type !== this.audio_metadata_.object_type) {
|
|
2109
|
+
Log.v(this.TAG, `MP3: AudioObjectType changed from ` +
|
|
2110
|
+
`${this.audio_metadata_.object_type} to ${data.object_type}`);
|
|
2111
|
+
return true;
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
if (data.sample_rate !== this.audio_metadata_.sample_rate) {
|
|
2115
|
+
Log.v(this.TAG, `MP3: SamplingFrequencyIndex changed from ` +
|
|
2116
|
+
`${this.audio_metadata_.sample_rate} to ${data.sample_rate}`);
|
|
2117
|
+
return true;
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2120
|
+
if (data.channel_count !== this.audio_metadata_.channel_count) {
|
|
2121
|
+
Log.v(this.TAG, `MP3: Channel count changed from ` +
|
|
2122
|
+
`${this.audio_metadata_.channel_count} to ${data.channel_count}`);
|
|
2123
|
+
return true;
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2127
|
+
return false;
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
private dispatchAudioInitSegment(sample: AudioData) {
|
|
2131
|
+
let meta: any = {};
|
|
2132
|
+
meta.type = 'audio';
|
|
2133
|
+
meta.id = this.audio_track_.id;
|
|
2134
|
+
meta.timescale = 1000;
|
|
2135
|
+
meta.duration = this.duration_;
|
|
2136
|
+
|
|
2137
|
+
if (this.audio_metadata_.codec === 'aac') {
|
|
2138
|
+
let aac_frame = sample.codec === 'aac' ? sample.data : null;
|
|
2139
|
+
let audio_specific_config = new AudioSpecificConfig(aac_frame);
|
|
2140
|
+
|
|
2141
|
+
meta.audioSampleRate = audio_specific_config.sampling_rate;
|
|
2142
|
+
meta.channelCount = audio_specific_config.channel_count;
|
|
2143
|
+
meta.codec = audio_specific_config.codec_mimetype;
|
|
2144
|
+
meta.originalCodec = audio_specific_config.original_codec_mimetype;
|
|
2145
|
+
meta.config = audio_specific_config.config;
|
|
2146
|
+
meta.refSampleDuration = 1024 / meta.audioSampleRate * meta.timescale;
|
|
2147
|
+
} else if (this.audio_metadata_.codec === 'ac-3') {
|
|
2148
|
+
let ac3_frame = sample.codec === 'ac-3' ? sample.data : null;
|
|
2149
|
+
let ac3_config = new AC3Config(ac3_frame);
|
|
2150
|
+
meta.audioSampleRate = ac3_config.sampling_rate
|
|
2151
|
+
meta.channelCount = ac3_config.channel_count;
|
|
2152
|
+
meta.codec = ac3_config.codec_mimetype;
|
|
2153
|
+
meta.originalCodec = ac3_config.original_codec_mimetype;
|
|
2154
|
+
meta.config = ac3_config.config;
|
|
2155
|
+
meta.refSampleDuration = 1536 / meta.audioSampleRate * meta.timescale;
|
|
2156
|
+
} else if (this.audio_metadata_.codec === 'ec-3') {
|
|
2157
|
+
let ec3_frame = sample.codec === 'ec-3' ? sample.data : null;
|
|
2158
|
+
let ec3_config = new EAC3Config(ec3_frame);
|
|
2159
|
+
meta.audioSampleRate = ec3_config.sampling_rate
|
|
2160
|
+
meta.channelCount = ec3_config.channel_count;
|
|
2161
|
+
meta.codec = ec3_config.codec_mimetype;
|
|
2162
|
+
meta.originalCodec = ec3_config.original_codec_mimetype;
|
|
2163
|
+
meta.config = ec3_config.config;
|
|
2164
|
+
meta.refSampleDuration = (256 * ec3_config.num_blks) / meta.audioSampleRate * meta.timescale; // TODO: blk size
|
|
2165
|
+
} else if (this.audio_metadata_.codec === 'opus') {
|
|
2166
|
+
meta.audioSampleRate = this.audio_metadata_.sample_rate;
|
|
2167
|
+
meta.channelCount = this.audio_metadata_.channel_count;
|
|
2168
|
+
meta.channelConfigCode = this.audio_metadata_.channel_config_code;
|
|
2169
|
+
meta.codec = 'opus';
|
|
2170
|
+
meta.originalCodec = 'opus';
|
|
2171
|
+
meta.config = undefined;
|
|
2172
|
+
meta.refSampleDuration = 20;
|
|
2173
|
+
} else if (this.audio_metadata_.codec === 'mp3') {
|
|
2174
|
+
meta.audioSampleRate = this.audio_metadata_.sample_rate;
|
|
2175
|
+
meta.channelCount = this.audio_metadata_.channel_count;
|
|
2176
|
+
meta.codec = 'mp3';
|
|
2177
|
+
meta.originalCodec = 'mp3';
|
|
2178
|
+
meta.config = undefined;
|
|
2179
|
+
meta.refSampleDuration = 1152 / meta.audioSampleRate * meta.timescale;
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
if (this.audio_init_segment_dispatched_ == false) {
|
|
2183
|
+
Log.v(this.TAG, `Generated first AudioSpecificConfig for mimeType: ${meta.codec}`);
|
|
2184
|
+
}
|
|
2185
|
+
|
|
2186
|
+
this.onTrackMetadata('audio', meta);
|
|
2187
|
+
this.audio_init_segment_dispatched_ = true;
|
|
2188
|
+
this.audio_metadata_changed_ = false;
|
|
2189
|
+
|
|
2190
|
+
// notify new MediaInfo
|
|
2191
|
+
let mi = this.media_info_;
|
|
2192
|
+
mi.hasAudio = true;
|
|
2193
|
+
mi.audioCodec = meta.originalCodec;
|
|
2194
|
+
mi.audioSampleRate = meta.audioSampleRate;
|
|
2195
|
+
mi.audioChannelCount = meta.channelCount;
|
|
2196
|
+
|
|
2197
|
+
if (mi.hasVideo && mi.videoCodec) {
|
|
2198
|
+
mi.mimeType = `video/mp2t; codecs="${mi.videoCodec},${mi.audioCodec}"`;
|
|
2199
|
+
} else {
|
|
2200
|
+
mi.mimeType = `video/mp2t; codecs="${mi.audioCodec}"`;
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
if (mi.isComplete()) {
|
|
2204
|
+
this.onMediaInfo(mi);
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
private dispatchPESPrivateDataDescriptor(pid: number, stream_type: number, descriptor: Uint8Array) {
|
|
2209
|
+
let desc = new PESPrivateDataDescriptor();
|
|
2210
|
+
desc.pid = pid;
|
|
2211
|
+
desc.stream_type = stream_type;
|
|
2212
|
+
desc.descriptor = descriptor;
|
|
2213
|
+
|
|
2214
|
+
if (this.onPESPrivateDataDescriptor) {
|
|
2215
|
+
this.onPESPrivateDataDescriptor(desc);
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
private parsePESPrivateDataPayload(data: Uint8Array, pts: number, dts: number, pid: number, stream_id: number) {
|
|
2220
|
+
let private_data = new PESPrivateData();
|
|
2221
|
+
|
|
2222
|
+
private_data.pid = pid;
|
|
2223
|
+
private_data.stream_id = stream_id;
|
|
2224
|
+
private_data.len = data.byteLength;
|
|
2225
|
+
private_data.data = data;
|
|
2226
|
+
|
|
2227
|
+
if (pts != undefined) {
|
|
2228
|
+
let pts_ms = Math.floor(pts / this.timescale_);
|
|
2229
|
+
private_data.pts = pts_ms;
|
|
2230
|
+
} else {
|
|
2231
|
+
private_data.nearest_pts = this.getNearestTimestampMilliseconds();
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
if (dts != undefined) {
|
|
2235
|
+
let dts_ms = Math.floor(dts / this.timescale_);
|
|
2236
|
+
private_data.dts = dts_ms;
|
|
2237
|
+
}
|
|
2238
|
+
|
|
2239
|
+
if (this.onPESPrivateData) {
|
|
2240
|
+
this.onPESPrivateData(private_data);
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
private parseTimedID3MetadataPayload(data: Uint8Array, pts: number, dts: number, pid: number, stream_id: number) {
|
|
2245
|
+
let timed_id3_metadata = new PESPrivateData();
|
|
2246
|
+
|
|
2247
|
+
timed_id3_metadata.pid = pid;
|
|
2248
|
+
timed_id3_metadata.stream_id = stream_id;
|
|
2249
|
+
timed_id3_metadata.len = data.byteLength;
|
|
2250
|
+
timed_id3_metadata.data = data;
|
|
2251
|
+
|
|
2252
|
+
if (pts != undefined) {
|
|
2253
|
+
let pts_ms = Math.floor(pts / this.timescale_);
|
|
2254
|
+
timed_id3_metadata.pts = pts_ms;
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2257
|
+
if (dts != undefined) {
|
|
2258
|
+
let dts_ms = Math.floor(dts / this.timescale_);
|
|
2259
|
+
timed_id3_metadata.dts = dts_ms;
|
|
2260
|
+
}
|
|
2261
|
+
|
|
2262
|
+
if (this.onTimedID3Metadata) {
|
|
2263
|
+
this.onTimedID3Metadata(timed_id3_metadata);
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
private parsePGSPayload(data: Uint8Array, pts: number, dts: number, pid: number, stream_id: number, lang: string) {
|
|
2268
|
+
let pgs_data = new PGSData();
|
|
2269
|
+
|
|
2270
|
+
pgs_data.pid = pid;
|
|
2271
|
+
pgs_data.lang = lang;
|
|
2272
|
+
pgs_data.stream_id = stream_id;
|
|
2273
|
+
pgs_data.len = data.byteLength;
|
|
2274
|
+
pgs_data.data = data;
|
|
2275
|
+
|
|
2276
|
+
if (pts != undefined) {
|
|
2277
|
+
let pts_ms = Math.floor(pts / this.timescale_);
|
|
2278
|
+
pgs_data.pts = pts_ms;
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2281
|
+
if (dts != undefined) {
|
|
2282
|
+
let dts_ms = Math.floor(dts / this.timescale_);
|
|
2283
|
+
pgs_data.dts = dts_ms;
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
if (this.onPGSSubtitleData) {
|
|
2287
|
+
this.onPGSSubtitleData(pgs_data);
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
private parseSynchronousKLVMetadataPayload(data: Uint8Array, pts: number, dts: number, pid: number, stream_id: number) {
|
|
2292
|
+
let synchronous_klv_metadata = new KLVData();
|
|
2293
|
+
|
|
2294
|
+
synchronous_klv_metadata.pid = pid;
|
|
2295
|
+
synchronous_klv_metadata.stream_id = stream_id;
|
|
2296
|
+
synchronous_klv_metadata.len = data.byteLength;
|
|
2297
|
+
synchronous_klv_metadata.data = data;
|
|
2298
|
+
|
|
2299
|
+
if (pts != undefined) {
|
|
2300
|
+
let pts_ms = Math.floor(pts / this.timescale_);
|
|
2301
|
+
synchronous_klv_metadata.pts = pts_ms;
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
if (dts != undefined) {
|
|
2305
|
+
let dts_ms = Math.floor(dts / this.timescale_);
|
|
2306
|
+
synchronous_klv_metadata.dts = dts_ms;
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
synchronous_klv_metadata.access_units = klv_parse(data);
|
|
2310
|
+
|
|
2311
|
+
if (this.onSynchronousKLVMetadata) {
|
|
2312
|
+
this.onSynchronousKLVMetadata(synchronous_klv_metadata);
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
private parseAsynchronousKLVMetadataPayload(data: Uint8Array, pid: number, stream_id: number) {
|
|
2317
|
+
let asynchronous_klv_metadata = new PESPrivateData();
|
|
2318
|
+
|
|
2319
|
+
asynchronous_klv_metadata.pid = pid;
|
|
2320
|
+
asynchronous_klv_metadata.stream_id = stream_id;
|
|
2321
|
+
asynchronous_klv_metadata.len = data.byteLength;
|
|
2322
|
+
asynchronous_klv_metadata.data = data;
|
|
2323
|
+
|
|
2324
|
+
if (this.onAsynchronousKLVMetadata) {
|
|
2325
|
+
this.onAsynchronousKLVMetadata(asynchronous_klv_metadata);
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
private parseSMPTE2038MetadataPayload(data: Uint8Array, pts: number, dts: number, pid: number, stream_id: number) {
|
|
2330
|
+
let smpte2038_data = new SMPTE2038Data();
|
|
2331
|
+
|
|
2332
|
+
smpte2038_data.pid = pid;
|
|
2333
|
+
smpte2038_data.stream_id = stream_id;
|
|
2334
|
+
smpte2038_data.len = data.byteLength;
|
|
2335
|
+
smpte2038_data.data = data;
|
|
2336
|
+
|
|
2337
|
+
if (pts != undefined) {
|
|
2338
|
+
let pts_ms = Math.floor(pts / this.timescale_);
|
|
2339
|
+
smpte2038_data.pts = pts_ms;
|
|
2340
|
+
}
|
|
2341
|
+
smpte2038_data.nearest_pts = this.getNearestTimestampMilliseconds();
|
|
2342
|
+
|
|
2343
|
+
if (dts != undefined) {
|
|
2344
|
+
let dts_ms = Math.floor(dts / this.timescale_);
|
|
2345
|
+
smpte2038_data.dts = dts_ms;
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2348
|
+
smpte2038_data.ancillaries = smpte2038parse(data);
|
|
2349
|
+
if (this.onSMPTE2038Metadata) {
|
|
2350
|
+
this.onSMPTE2038Metadata(smpte2038_data);
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
private parseSEIPayload(data: Uint8Array, pts: number, codec: 'h264' | 'h265') {
|
|
2355
|
+
let timestamp = pts != undefined ? Math.floor(pts / this.timescale_) : undefined;
|
|
2356
|
+
let sei_data = parseSEI(data, timestamp, codec);
|
|
2357
|
+
|
|
2358
|
+
if (sei_data && this.onSEI) {
|
|
2359
|
+
this.onSEI(sei_data);
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2363
|
+
private getNearestTimestampMilliseconds(): number | undefined {
|
|
2364
|
+
// Prefer using last audio sample pts if audio track exists
|
|
2365
|
+
if (this.audio_last_sample_pts_ != undefined) {
|
|
2366
|
+
return Math.floor(this.audio_last_sample_pts_);
|
|
2367
|
+
} else if (this.last_pcr_ != undefined) {
|
|
2368
|
+
// Fallback to PCR time if audio track doesn't exist
|
|
2369
|
+
const pcr_time_ms = Math.floor(this.last_pcr_ / 300 / this.timescale_);
|
|
2370
|
+
return pcr_time_ms;
|
|
2371
|
+
}
|
|
2372
|
+
return undefined;
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
private getPcrBase(data: Uint8Array): number {
|
|
2376
|
+
let pcr_base = data[6] * 33554432 // 1 << 25
|
|
2377
|
+
+ data[7] * 131072 // 1 << 17
|
|
2378
|
+
+ data[8] * 512 // 1 << 9
|
|
2379
|
+
+ data[9] * 2 // 1 << 1
|
|
2380
|
+
+ (data[10] & 0x80) / 128 // 1 >> 7
|
|
2381
|
+
+ this.timestamp_offset_;
|
|
2382
|
+
if (pcr_base + 0x100000000 < this.last_pcr_base_) {
|
|
2383
|
+
pcr_base += 0x200000000; // pcr_base wraparound
|
|
2384
|
+
this.timestamp_offset_ += 0x200000000;
|
|
2385
|
+
}
|
|
2386
|
+
this.last_pcr_base_ = pcr_base;
|
|
2387
|
+
return pcr_base;
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
private getTimestamp(data: Uint8Array, pos: number): number {
|
|
2391
|
+
let timestamp = (data[pos] & 0x0E) * 536870912 // 1 << 29
|
|
2392
|
+
+ (data[pos + 1] & 0xFF) * 4194304 // 1 << 22
|
|
2393
|
+
+ (data[pos + 2] & 0xFE) * 16384 // 1 << 14
|
|
2394
|
+
+ (data[pos + 3] & 0xFF) * 128 // 1 << 7
|
|
2395
|
+
+ (data[pos + 4] & 0xFE) / 2
|
|
2396
|
+
+ this.timestamp_offset_;
|
|
2397
|
+
if (timestamp + 0x100000000 < this.last_pcr_base_) {
|
|
2398
|
+
timestamp += 0x200000000; // pts/dts wraparound
|
|
2399
|
+
}
|
|
2400
|
+
return timestamp;
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2405
|
+
export default TSDemuxer;
|