@alessmicrosystems/mpegts.js 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +158 -0
  3. package/README_ja.md +153 -0
  4. package/README_zh.md +157 -0
  5. package/d.ts/mpegts.d.ts +524 -0
  6. package/d.ts/src/core/mse-events.d.ts +9 -0
  7. package/d.ts/src/core/transmuxing-events.d.ts +24 -0
  8. package/d.ts/src/demux/aac.d.ts +44 -0
  9. package/d.ts/src/demux/ac3.d.ts +70 -0
  10. package/d.ts/src/demux/av1-parser.d.ts +77 -0
  11. package/d.ts/src/demux/av1.d.ts +11 -0
  12. package/d.ts/src/demux/base-demuxer.d.ts +55 -0
  13. package/d.ts/src/demux/h264.d.ts +40 -0
  14. package/d.ts/src/demux/h265.d.ts +65 -0
  15. package/d.ts/src/demux/klv.d.ts +17 -0
  16. package/d.ts/src/demux/mp3.d.ts +6 -0
  17. package/d.ts/src/demux/mpeg4-audio.d.ts +28 -0
  18. package/d.ts/src/demux/pat-pmt-pes.d.ts +106 -0
  19. package/d.ts/src/demux/patpmt.d.ts +40 -0
  20. package/d.ts/src/demux/pes-private-data.d.ts +14 -0
  21. package/d.ts/src/demux/pgs-data.d.ts +9 -0
  22. package/d.ts/src/demux/scte35.d.ts +250 -0
  23. package/d.ts/src/demux/sei.d.ts +8 -0
  24. package/d.ts/src/demux/smpte2038.d.ts +22 -0
  25. package/d.ts/src/demux/ts-demuxer.d.ts +124 -0
  26. package/d.ts/src/player/live-latency-chaser.d.ts +10 -0
  27. package/d.ts/src/player/live-latency-synchronizer.d.ts +10 -0
  28. package/d.ts/src/player/loading-controller.d.ts +19 -0
  29. package/d.ts/src/player/mse-player.d.ts +30 -0
  30. package/d.ts/src/player/player-engine-dedicated-thread-worker.d.ts +2 -0
  31. package/d.ts/src/player/player-engine-dedicated-thread.d.ts +48 -0
  32. package/d.ts/src/player/player-engine-main-thread.d.ts +50 -0
  33. package/d.ts/src/player/player-engine-worker-cmd-def.d.ts +25 -0
  34. package/d.ts/src/player/player-engine-worker-msg-def.d.ts +54 -0
  35. package/d.ts/src/player/player-engine-worker.d.ts +2 -0
  36. package/d.ts/src/player/player-engine.d.ts +16 -0
  37. package/d.ts/src/player/player-events.d.ts +21 -0
  38. package/d.ts/src/player/seeking-handler.d.ts +22 -0
  39. package/d.ts/src/player/startup-stall-jumper.d.ts +14 -0
  40. package/d.ts/src/utils/typedarray-equality.d.ts +2 -0
  41. package/dist/mpegts.js +3 -0
  42. package/dist/mpegts.js.LICENSE.txt +7 -0
  43. package/dist/mpegts.js.map +1 -0
  44. package/package.json +53 -0
  45. package/src/config.js +67 -0
  46. package/src/core/features.js +88 -0
  47. package/src/core/media-info.js +127 -0
  48. package/src/core/media-segment-info.js +230 -0
  49. package/src/core/mse-controller.js +599 -0
  50. package/src/core/mse-events.ts +28 -0
  51. package/src/core/transmuxer.js +346 -0
  52. package/src/core/transmuxing-controller.js +628 -0
  53. package/src/core/transmuxing-events.ts +43 -0
  54. package/src/core/transmuxing-worker.js +286 -0
  55. package/src/demux/aac.ts +397 -0
  56. package/src/demux/ac3.ts +335 -0
  57. package/src/demux/amf-parser.js +243 -0
  58. package/src/demux/av1-parser.ts +629 -0
  59. package/src/demux/av1.ts +103 -0
  60. package/src/demux/base-demuxer.ts +69 -0
  61. package/src/demux/demux-errors.js +26 -0
  62. package/src/demux/exp-golomb.js +116 -0
  63. package/src/demux/flv-demuxer.js +1854 -0
  64. package/src/demux/h264.ts +187 -0
  65. package/src/demux/h265-parser.js +501 -0
  66. package/src/demux/h265.ts +214 -0
  67. package/src/demux/klv.ts +40 -0
  68. package/src/demux/mp3.ts +7 -0
  69. package/src/demux/mpeg4-audio.ts +45 -0
  70. package/src/demux/pat-pmt-pes.ts +132 -0
  71. package/src/demux/pes-private-data.ts +16 -0
  72. package/src/demux/pgs-data.ts +11 -0
  73. package/src/demux/scte35.ts +723 -0
  74. package/src/demux/sei.ts +99 -0
  75. package/src/demux/smpte2038.ts +89 -0
  76. package/src/demux/sps-parser.js +298 -0
  77. package/src/demux/ts-demuxer.ts +2405 -0
  78. package/src/index.js +4 -0
  79. package/src/io/fetch-stream-loader.js +266 -0
  80. package/src/io/io-controller.js +647 -0
  81. package/src/io/loader.js +134 -0
  82. package/src/io/param-seek-handler.js +85 -0
  83. package/src/io/range-seek-handler.js +52 -0
  84. package/src/io/speed-sampler.js +93 -0
  85. package/src/io/websocket-loader.js +151 -0
  86. package/src/io/xhr-moz-chunked-loader.js +211 -0
  87. package/src/io/xhr-msstream-loader.js +307 -0
  88. package/src/io/xhr-range-loader.js +366 -0
  89. package/src/mpegts.js +95 -0
  90. package/src/player/live-latency-chaser.ts +66 -0
  91. package/src/player/live-latency-synchronizer.ts +79 -0
  92. package/src/player/loading-controller.ts +142 -0
  93. package/src/player/mse-player.ts +150 -0
  94. package/src/player/native-player.js +262 -0
  95. package/src/player/player-engine-dedicated-thread.ts +479 -0
  96. package/src/player/player-engine-main-thread.ts +463 -0
  97. package/src/player/player-engine-worker-cmd-def.ts +62 -0
  98. package/src/player/player-engine-worker-msg-def.ts +102 -0
  99. package/src/player/player-engine-worker.ts +370 -0
  100. package/src/player/player-engine.ts +35 -0
  101. package/src/player/player-errors.js +39 -0
  102. package/src/player/player-events.ts +40 -0
  103. package/src/player/seeking-handler.ts +205 -0
  104. package/src/player/startup-stall-jumper.ts +86 -0
  105. package/src/remux/aac-silent.js +56 -0
  106. package/src/remux/mp4-generator.js +866 -0
  107. package/src/remux/mp4-remuxer.js +778 -0
  108. package/src/utils/browser.js +128 -0
  109. package/src/utils/exception.js +73 -0
  110. package/src/utils/logger.js +140 -0
  111. package/src/utils/logging-control.js +165 -0
  112. package/src/utils/polyfill.js +68 -0
  113. package/src/utils/typedarray-equality.ts +69 -0
  114. package/src/utils/utf8-conv.js +84 -0
  115. package/src/utils/webworkify-webpack.js +202 -0
  116. package/tsconfig.json +16 -0
  117. package/tslint.json +1 -0
  118. package/types/index.d.ts +3 -0
  119. package/types/test-flv.ts +8 -0
  120. package/types/tsconfig.json +24 -0
  121. package/webpack.config.js +55 -0
@@ -0,0 +1,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;