@zenvor/hls.js 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +28 -0
- package/README.md +472 -0
- package/dist/hls-demo.js +26995 -0
- package/dist/hls-demo.js.map +1 -0
- package/dist/hls.d.mts +4204 -0
- package/dist/hls.d.ts +4204 -0
- package/dist/hls.js +40050 -0
- package/dist/hls.js.d.ts +4204 -0
- package/dist/hls.js.map +1 -0
- package/dist/hls.light.js +27145 -0
- package/dist/hls.light.js.map +1 -0
- package/dist/hls.light.min.js +2 -0
- package/dist/hls.light.min.js.map +1 -0
- package/dist/hls.light.mjs +26392 -0
- package/dist/hls.light.mjs.map +1 -0
- package/dist/hls.min.js +2 -0
- package/dist/hls.min.js.map +1 -0
- package/dist/hls.mjs +38956 -0
- package/dist/hls.mjs.map +1 -0
- package/dist/hls.worker.js +2 -0
- package/dist/hls.worker.js.map +1 -0
- package/package.json +143 -0
- package/src/config.ts +794 -0
- package/src/controller/abr-controller.ts +1019 -0
- package/src/controller/algo-data-controller.ts +794 -0
- package/src/controller/audio-stream-controller.ts +1099 -0
- package/src/controller/audio-track-controller.ts +454 -0
- package/src/controller/base-playlist-controller.ts +438 -0
- package/src/controller/base-stream-controller.ts +2526 -0
- package/src/controller/buffer-controller.ts +2015 -0
- package/src/controller/buffer-operation-queue.ts +159 -0
- package/src/controller/cap-level-controller.ts +367 -0
- package/src/controller/cmcd-controller.ts +422 -0
- package/src/controller/content-steering-controller.ts +622 -0
- package/src/controller/eme-controller.ts +1617 -0
- package/src/controller/error-controller.ts +627 -0
- package/src/controller/fps-controller.ts +146 -0
- package/src/controller/fragment-finders.ts +256 -0
- package/src/controller/fragment-tracker.ts +567 -0
- package/src/controller/gap-controller.ts +719 -0
- package/src/controller/id3-track-controller.ts +488 -0
- package/src/controller/interstitial-player.ts +302 -0
- package/src/controller/interstitials-controller.ts +2895 -0
- package/src/controller/interstitials-schedule.ts +698 -0
- package/src/controller/latency-controller.ts +294 -0
- package/src/controller/level-controller.ts +776 -0
- package/src/controller/stream-controller.ts +1597 -0
- package/src/controller/subtitle-stream-controller.ts +508 -0
- package/src/controller/subtitle-track-controller.ts +617 -0
- package/src/controller/timeline-controller.ts +677 -0
- package/src/crypt/aes-crypto.ts +36 -0
- package/src/crypt/aes-decryptor.ts +339 -0
- package/src/crypt/decrypter-aes-mode.ts +4 -0
- package/src/crypt/decrypter.ts +225 -0
- package/src/crypt/fast-aes-key.ts +39 -0
- package/src/define-plugin.d.ts +17 -0
- package/src/demux/audio/aacdemuxer.ts +126 -0
- package/src/demux/audio/ac3-demuxer.ts +170 -0
- package/src/demux/audio/adts.ts +249 -0
- package/src/demux/audio/base-audio-demuxer.ts +205 -0
- package/src/demux/audio/dolby.ts +21 -0
- package/src/demux/audio/mp3demuxer.ts +85 -0
- package/src/demux/audio/mpegaudio.ts +177 -0
- package/src/demux/chunk-cache.ts +42 -0
- package/src/demux/dummy-demuxed-track.ts +13 -0
- package/src/demux/inject-worker.ts +75 -0
- package/src/demux/mp4demuxer.ts +234 -0
- package/src/demux/sample-aes.ts +198 -0
- package/src/demux/transmuxer-interface.ts +449 -0
- package/src/demux/transmuxer-worker.ts +221 -0
- package/src/demux/transmuxer.ts +560 -0
- package/src/demux/tsdemuxer.ts +1256 -0
- package/src/demux/video/avc-video-parser.ts +401 -0
- package/src/demux/video/base-video-parser.ts +198 -0
- package/src/demux/video/exp-golomb.ts +153 -0
- package/src/demux/video/hevc-video-parser.ts +736 -0
- package/src/empty-es.js +5 -0
- package/src/empty.js +3 -0
- package/src/errors.ts +107 -0
- package/src/events.ts +548 -0
- package/src/exports-default.ts +3 -0
- package/src/exports-named.ts +81 -0
- package/src/hls.ts +1613 -0
- package/src/is-supported.ts +54 -0
- package/src/loader/date-range.ts +207 -0
- package/src/loader/fragment-loader.ts +403 -0
- package/src/loader/fragment.ts +487 -0
- package/src/loader/interstitial-asset-list.ts +162 -0
- package/src/loader/interstitial-event.ts +337 -0
- package/src/loader/key-loader.ts +439 -0
- package/src/loader/level-details.ts +203 -0
- package/src/loader/level-key.ts +259 -0
- package/src/loader/load-stats.ts +17 -0
- package/src/loader/m3u8-parser.ts +1072 -0
- package/src/loader/playlist-loader.ts +839 -0
- package/src/polyfills/number.ts +15 -0
- package/src/remux/aac-helper.ts +81 -0
- package/src/remux/mp4-generator.ts +1380 -0
- package/src/remux/mp4-remuxer.ts +1261 -0
- package/src/remux/passthrough-remuxer.ts +434 -0
- package/src/task-loop.ts +130 -0
- package/src/types/algo.ts +44 -0
- package/src/types/buffer.ts +105 -0
- package/src/types/component-api.ts +20 -0
- package/src/types/demuxer.ts +208 -0
- package/src/types/events.ts +574 -0
- package/src/types/fragment-tracker.ts +23 -0
- package/src/types/level.ts +268 -0
- package/src/types/loader.ts +198 -0
- package/src/types/media-playlist.ts +92 -0
- package/src/types/network-details.ts +3 -0
- package/src/types/remuxer.ts +104 -0
- package/src/types/track.ts +12 -0
- package/src/types/transmuxer.ts +46 -0
- package/src/types/tuples.ts +6 -0
- package/src/types/vtt.ts +11 -0
- package/src/utils/arrays.ts +22 -0
- package/src/utils/attr-list.ts +192 -0
- package/src/utils/binary-search.ts +46 -0
- package/src/utils/buffer-helper.ts +173 -0
- package/src/utils/cea-608-parser.ts +1413 -0
- package/src/utils/chunker.ts +41 -0
- package/src/utils/codecs.ts +314 -0
- package/src/utils/cues.ts +96 -0
- package/src/utils/discontinuities.ts +174 -0
- package/src/utils/encryption-methods-util.ts +21 -0
- package/src/utils/error-helper.ts +95 -0
- package/src/utils/event-listener-helper.ts +16 -0
- package/src/utils/ewma-bandwidth-estimator.ts +97 -0
- package/src/utils/ewma.ts +43 -0
- package/src/utils/fetch-loader.ts +331 -0
- package/src/utils/global.ts +2 -0
- package/src/utils/hash.ts +10 -0
- package/src/utils/hdr.ts +67 -0
- package/src/utils/hex.ts +32 -0
- package/src/utils/imsc1-ttml-parser.ts +261 -0
- package/src/utils/keysystem-util.ts +45 -0
- package/src/utils/level-helper.ts +629 -0
- package/src/utils/logger.ts +120 -0
- package/src/utils/media-option-attributes.ts +49 -0
- package/src/utils/mediacapabilities-helper.ts +301 -0
- package/src/utils/mediakeys-helper.ts +210 -0
- package/src/utils/mediasource-helper.ts +37 -0
- package/src/utils/mp4-tools.ts +1473 -0
- package/src/utils/number.ts +3 -0
- package/src/utils/numeric-encoding-utils.ts +26 -0
- package/src/utils/output-filter.ts +46 -0
- package/src/utils/rendition-helper.ts +505 -0
- package/src/utils/safe-json-stringify.ts +22 -0
- package/src/utils/texttrack-utils.ts +164 -0
- package/src/utils/time-ranges.ts +17 -0
- package/src/utils/timescale-conversion.ts +46 -0
- package/src/utils/utf8-utils.ts +18 -0
- package/src/utils/variable-substitution.ts +105 -0
- package/src/utils/vttcue.ts +384 -0
- package/src/utils/vttparser.ts +497 -0
- package/src/utils/webvtt-parser.ts +166 -0
- package/src/utils/xhr-loader.ts +337 -0
- package/src/version.ts +1 -0
|
@@ -0,0 +1,1256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* highly optimized TS demuxer:
|
|
3
|
+
* parse PAT, PMT
|
|
4
|
+
* extract PES packet from audio and video PIDs
|
|
5
|
+
* extract AVC/H264 (or HEVC/H265) NAL units and AAC/ADTS samples from PES packet
|
|
6
|
+
* trigger the remuxer upon parsing completion
|
|
7
|
+
* it also tries to workaround as best as it can audio codec switch (HE-AAC to AAC and vice versa), without having to restart the MediaSource.
|
|
8
|
+
* it also controls the remuxing process :
|
|
9
|
+
* upon discontinuity or level switch detection, it will also notifies the remuxer so that it can reset its state.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import * as AC3 from './audio/ac3-demuxer';
|
|
13
|
+
import * as ADTS from './audio/adts';
|
|
14
|
+
import * as MpegAudio from './audio/mpegaudio';
|
|
15
|
+
import SampleAesDecrypter from './sample-aes';
|
|
16
|
+
import AvcVideoParser from './video/avc-video-parser';
|
|
17
|
+
import HevcVideoParser from './video/hevc-video-parser';
|
|
18
|
+
import { ErrorDetails, ErrorTypes } from '../errors';
|
|
19
|
+
import { Events } from '../events';
|
|
20
|
+
import {
|
|
21
|
+
type DemuxedAudioTrack,
|
|
22
|
+
type DemuxedMetadataTrack,
|
|
23
|
+
type DemuxedTrack,
|
|
24
|
+
type DemuxedUserdataTrack,
|
|
25
|
+
type DemuxedVideoTrack,
|
|
26
|
+
type Demuxer,
|
|
27
|
+
type DemuxerResult,
|
|
28
|
+
type ElementaryStreamData,
|
|
29
|
+
type KeyData,
|
|
30
|
+
MetadataSchema,
|
|
31
|
+
type VideoSample,
|
|
32
|
+
} from '../types/demuxer';
|
|
33
|
+
import { appendUint8Array, RemuxerTrackIdConfig } from '../utils/mp4-tools';
|
|
34
|
+
import type { HlsConfig } from '../config';
|
|
35
|
+
import type { HlsEventEmitter } from '../events';
|
|
36
|
+
import type BaseVideoParser from './video/base-video-parser';
|
|
37
|
+
import type { AudioFrame, DemuxedAAC } from '../types/demuxer';
|
|
38
|
+
import type { TypeSupported } from '../utils/codecs';
|
|
39
|
+
import type { ILogger } from '../utils/logger';
|
|
40
|
+
|
|
41
|
+
export type ParsedTimestamp = {
|
|
42
|
+
pts?: number;
|
|
43
|
+
dts?: number;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export type PES = ParsedTimestamp & {
|
|
47
|
+
data: Uint8Array;
|
|
48
|
+
len: number;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type ParsedVideoSample = ParsedTimestamp &
|
|
52
|
+
Omit<VideoSample, 'pts' | 'dts'>;
|
|
53
|
+
|
|
54
|
+
const PACKET_LENGTH = 188;
|
|
55
|
+
|
|
56
|
+
class TSDemuxer implements Demuxer {
|
|
57
|
+
private readonly logger: ILogger;
|
|
58
|
+
private readonly observer: HlsEventEmitter;
|
|
59
|
+
private readonly config: HlsConfig;
|
|
60
|
+
private readonly typeSupported: TypeSupported;
|
|
61
|
+
|
|
62
|
+
private sampleAes: SampleAesDecrypter | null = null;
|
|
63
|
+
private pmtParsed: boolean = false;
|
|
64
|
+
private audioCodec?: string;
|
|
65
|
+
private videoCodec?: string;
|
|
66
|
+
private _pmtId: number = -1;
|
|
67
|
+
|
|
68
|
+
private _videoTrack?: DemuxedVideoTrack;
|
|
69
|
+
private _audioTrack?: DemuxedAudioTrack;
|
|
70
|
+
private _id3Track?: DemuxedMetadataTrack;
|
|
71
|
+
private _txtTrack?: DemuxedUserdataTrack;
|
|
72
|
+
private _klvPid: number = -1;
|
|
73
|
+
private aacOverFlow: AudioFrame | null = null;
|
|
74
|
+
private remainderData: Uint8Array | null = null;
|
|
75
|
+
private videoParser: BaseVideoParser | null;
|
|
76
|
+
private videoIntegrityChecker: PacketsIntegrityChecker | null = null;
|
|
77
|
+
|
|
78
|
+
constructor(
|
|
79
|
+
observer: HlsEventEmitter,
|
|
80
|
+
config: HlsConfig,
|
|
81
|
+
typeSupported: TypeSupported,
|
|
82
|
+
logger: ILogger,
|
|
83
|
+
) {
|
|
84
|
+
this.observer = observer;
|
|
85
|
+
this.config = config;
|
|
86
|
+
this.typeSupported = typeSupported;
|
|
87
|
+
this.logger = logger;
|
|
88
|
+
this.videoParser = null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
static probe(data: Uint8Array, logger: ILogger) {
|
|
92
|
+
const syncOffset = TSDemuxer.syncOffset(data);
|
|
93
|
+
if (syncOffset > 0) {
|
|
94
|
+
logger.warn(
|
|
95
|
+
`MPEG2-TS detected but first sync word found @ offset ${syncOffset}`,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
return syncOffset !== -1;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
static syncOffset(data: Uint8Array): number {
|
|
102
|
+
const length = data.length;
|
|
103
|
+
let scanwindow = Math.min(PACKET_LENGTH * 5, length - PACKET_LENGTH) + 1;
|
|
104
|
+
let i = 0;
|
|
105
|
+
while (i < scanwindow) {
|
|
106
|
+
// a TS init segment should contain at least 2 TS packets: PAT and PMT, each starting with 0x47
|
|
107
|
+
let foundPat = false;
|
|
108
|
+
let packetStart = -1;
|
|
109
|
+
let tsPackets = 0;
|
|
110
|
+
for (let j = i; j < length; j += PACKET_LENGTH) {
|
|
111
|
+
if (
|
|
112
|
+
data[j] === 0x47 &&
|
|
113
|
+
(length - j === PACKET_LENGTH || data[j + PACKET_LENGTH] === 0x47)
|
|
114
|
+
) {
|
|
115
|
+
tsPackets++;
|
|
116
|
+
if (packetStart === -1) {
|
|
117
|
+
packetStart = j;
|
|
118
|
+
// First sync word found at offset, increase scan length (#5251)
|
|
119
|
+
if (packetStart !== 0) {
|
|
120
|
+
scanwindow =
|
|
121
|
+
Math.min(
|
|
122
|
+
packetStart + PACKET_LENGTH * 99,
|
|
123
|
+
data.length - PACKET_LENGTH,
|
|
124
|
+
) + 1;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (!foundPat) {
|
|
128
|
+
foundPat = parsePID(data, j) === 0;
|
|
129
|
+
}
|
|
130
|
+
// Sync word found at 0 with 3 packets, or found at offset least 2 packets up to scanwindow (#5501)
|
|
131
|
+
if (
|
|
132
|
+
foundPat &&
|
|
133
|
+
tsPackets > 1 &&
|
|
134
|
+
((packetStart === 0 && tsPackets > 2) ||
|
|
135
|
+
j + PACKET_LENGTH > scanwindow)
|
|
136
|
+
) {
|
|
137
|
+
return packetStart;
|
|
138
|
+
}
|
|
139
|
+
} else if (tsPackets) {
|
|
140
|
+
// Exit if sync word found, but does not contain contiguous packets
|
|
141
|
+
return -1;
|
|
142
|
+
} else {
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
i++;
|
|
147
|
+
}
|
|
148
|
+
return -1;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Creates a track model internal to demuxer used to drive remuxing input
|
|
153
|
+
*/
|
|
154
|
+
static createTrack(
|
|
155
|
+
type: 'audio' | 'video' | 'id3' | 'text',
|
|
156
|
+
duration?: number,
|
|
157
|
+
): DemuxedTrack {
|
|
158
|
+
return {
|
|
159
|
+
container:
|
|
160
|
+
type === 'video' || type === 'audio' ? 'video/mp2t' : undefined,
|
|
161
|
+
type,
|
|
162
|
+
id: RemuxerTrackIdConfig[type],
|
|
163
|
+
pid: -1,
|
|
164
|
+
inputTimeScale: 90000,
|
|
165
|
+
sequenceNumber: 0,
|
|
166
|
+
samples: [],
|
|
167
|
+
dropped: 0,
|
|
168
|
+
duration: type === 'audio' ? duration : undefined,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Initializes a new init segment on the demuxer/remuxer interface. Needed for discontinuities/track-switches (or at stream start)
|
|
174
|
+
* Resets all internal track instances of the demuxer.
|
|
175
|
+
*/
|
|
176
|
+
public resetInitSegment(
|
|
177
|
+
initSegment: Uint8Array | undefined,
|
|
178
|
+
audioCodec: string,
|
|
179
|
+
videoCodec: string,
|
|
180
|
+
trackDuration: number,
|
|
181
|
+
) {
|
|
182
|
+
this.pmtParsed = false;
|
|
183
|
+
this._pmtId = -1;
|
|
184
|
+
|
|
185
|
+
this._videoTrack = TSDemuxer.createTrack('video') as DemuxedVideoTrack;
|
|
186
|
+
this._videoTrack.duration = trackDuration;
|
|
187
|
+
this.videoIntegrityChecker =
|
|
188
|
+
this.config.handleMpegTsVideoIntegrityErrors === 'skip'
|
|
189
|
+
? new PacketsIntegrityChecker(this.logger)
|
|
190
|
+
: null;
|
|
191
|
+
this._audioTrack = TSDemuxer.createTrack(
|
|
192
|
+
'audio',
|
|
193
|
+
trackDuration,
|
|
194
|
+
) as DemuxedAudioTrack;
|
|
195
|
+
this._id3Track = TSDemuxer.createTrack('id3') as DemuxedMetadataTrack;
|
|
196
|
+
this._txtTrack = TSDemuxer.createTrack('text') as DemuxedUserdataTrack;
|
|
197
|
+
this._audioTrack.segmentCodec = 'aac';
|
|
198
|
+
|
|
199
|
+
// flush any partial content
|
|
200
|
+
this.videoParser = null;
|
|
201
|
+
this.aacOverFlow = null;
|
|
202
|
+
this.remainderData = null;
|
|
203
|
+
this.audioCodec = audioCodec;
|
|
204
|
+
this.videoCodec = videoCodec;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
public resetTimeStamp() {}
|
|
208
|
+
|
|
209
|
+
public resetContiguity(): void {
|
|
210
|
+
const { _audioTrack, _videoTrack, _id3Track } = this;
|
|
211
|
+
if (_audioTrack) {
|
|
212
|
+
_audioTrack.pesData = null;
|
|
213
|
+
}
|
|
214
|
+
if (_videoTrack) {
|
|
215
|
+
_videoTrack.pesData = null;
|
|
216
|
+
}
|
|
217
|
+
if (_id3Track) {
|
|
218
|
+
_id3Track.pesData = null;
|
|
219
|
+
}
|
|
220
|
+
this.aacOverFlow = null;
|
|
221
|
+
this.remainderData = null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
public demux(
|
|
225
|
+
data: Uint8Array,
|
|
226
|
+
timeOffset: number,
|
|
227
|
+
isSampleAes = false,
|
|
228
|
+
flush = false,
|
|
229
|
+
): DemuxerResult {
|
|
230
|
+
if (!isSampleAes) {
|
|
231
|
+
this.sampleAes = null;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
let pes: PES | null;
|
|
235
|
+
|
|
236
|
+
const videoTrack = this._videoTrack as DemuxedVideoTrack;
|
|
237
|
+
const videoIntegrityChecker = this.videoIntegrityChecker;
|
|
238
|
+
const audioTrack = this._audioTrack as DemuxedAudioTrack;
|
|
239
|
+
const id3Track = this._id3Track as DemuxedMetadataTrack;
|
|
240
|
+
const textTrack = this._txtTrack as DemuxedUserdataTrack;
|
|
241
|
+
|
|
242
|
+
let videoPid = videoTrack.pid;
|
|
243
|
+
let videoData = videoTrack.pesData;
|
|
244
|
+
let audioPid = audioTrack.pid;
|
|
245
|
+
let id3Pid = id3Track.pid;
|
|
246
|
+
let klvPid = this._klvPid;
|
|
247
|
+
let audioData = audioTrack.pesData;
|
|
248
|
+
let id3Data = id3Track.pesData;
|
|
249
|
+
let klvData: ElementaryStreamData | null = null;
|
|
250
|
+
let unknownPID: number | null = null;
|
|
251
|
+
let pmtParsed = this.pmtParsed;
|
|
252
|
+
let pmtId = this._pmtId;
|
|
253
|
+
|
|
254
|
+
let len = data.length;
|
|
255
|
+
if (this.remainderData) {
|
|
256
|
+
data = appendUint8Array(this.remainderData, data);
|
|
257
|
+
len = data.length;
|
|
258
|
+
this.remainderData = null;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (len < PACKET_LENGTH && !flush) {
|
|
262
|
+
this.remainderData = data;
|
|
263
|
+
return {
|
|
264
|
+
audioTrack,
|
|
265
|
+
videoTrack,
|
|
266
|
+
id3Track,
|
|
267
|
+
textTrack,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const syncOffset = Math.max(0, TSDemuxer.syncOffset(data));
|
|
272
|
+
len -= (len - syncOffset) % PACKET_LENGTH;
|
|
273
|
+
if (len < data.byteLength && !flush) {
|
|
274
|
+
this.remainderData = new Uint8Array(
|
|
275
|
+
data.buffer,
|
|
276
|
+
len,
|
|
277
|
+
data.buffer.byteLength - len,
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// loop through TS packets
|
|
282
|
+
let tsPacketErrors = 0;
|
|
283
|
+
for (let start = syncOffset; start < len; start += PACKET_LENGTH) {
|
|
284
|
+
if (data[start] === 0x47) {
|
|
285
|
+
const stt = !!(data[start + 1] & 0x40);
|
|
286
|
+
const pid = parsePID(data, start);
|
|
287
|
+
const atf = (data[start + 3] & 0x30) >> 4;
|
|
288
|
+
|
|
289
|
+
// if an adaption field is present, its length is specified by the fifth byte of the TS packet header.
|
|
290
|
+
let offset: number;
|
|
291
|
+
if (atf > 1) {
|
|
292
|
+
offset = start + 5 + data[start + 4];
|
|
293
|
+
// continue if there is only adaptation field
|
|
294
|
+
if (offset === start + PACKET_LENGTH) {
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
} else {
|
|
298
|
+
offset = start + 4;
|
|
299
|
+
}
|
|
300
|
+
switch (pid) {
|
|
301
|
+
case videoPid:
|
|
302
|
+
if (stt) {
|
|
303
|
+
if (
|
|
304
|
+
videoData &&
|
|
305
|
+
!videoIntegrityChecker?.isCorrupted &&
|
|
306
|
+
(pes = parsePES(videoData, this.logger))
|
|
307
|
+
) {
|
|
308
|
+
this.readyVideoParser(videoTrack.segmentCodec);
|
|
309
|
+
if (this.videoParser !== null) {
|
|
310
|
+
this.videoParser.parsePES(videoTrack, textTrack, pes, false);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
videoData = { data: [], size: 0 };
|
|
315
|
+
videoIntegrityChecker?.reset(videoPid);
|
|
316
|
+
}
|
|
317
|
+
videoIntegrityChecker?.handlePacket(data.subarray(start));
|
|
318
|
+
if (videoData) {
|
|
319
|
+
videoData.data.push(data.subarray(offset, start + PACKET_LENGTH));
|
|
320
|
+
videoData.size += start + PACKET_LENGTH - offset;
|
|
321
|
+
}
|
|
322
|
+
break;
|
|
323
|
+
case audioPid:
|
|
324
|
+
if (stt) {
|
|
325
|
+
if (audioData && (pes = parsePES(audioData, this.logger))) {
|
|
326
|
+
switch (audioTrack.segmentCodec) {
|
|
327
|
+
case 'aac':
|
|
328
|
+
this.parseAACPES(audioTrack, pes);
|
|
329
|
+
break;
|
|
330
|
+
case 'mp3':
|
|
331
|
+
this.parseMPEGPES(audioTrack, pes);
|
|
332
|
+
break;
|
|
333
|
+
case 'ac3':
|
|
334
|
+
if (__USE_M2TS_ADVANCED_CODECS__) {
|
|
335
|
+
this.parseAC3PES(audioTrack, pes);
|
|
336
|
+
}
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
audioData = { data: [], size: 0 };
|
|
341
|
+
}
|
|
342
|
+
if (audioData) {
|
|
343
|
+
audioData.data.push(data.subarray(offset, start + PACKET_LENGTH));
|
|
344
|
+
audioData.size += start + PACKET_LENGTH - offset;
|
|
345
|
+
}
|
|
346
|
+
break;
|
|
347
|
+
case id3Pid:
|
|
348
|
+
if (stt) {
|
|
349
|
+
if (id3Data && (pes = parsePES(id3Data, this.logger))) {
|
|
350
|
+
this.parseID3PES(id3Track, pes);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
id3Data = { data: [], size: 0 };
|
|
354
|
+
}
|
|
355
|
+
if (id3Data) {
|
|
356
|
+
id3Data.data.push(data.subarray(offset, start + PACKET_LENGTH));
|
|
357
|
+
id3Data.size += start + PACKET_LENGTH - offset;
|
|
358
|
+
}
|
|
359
|
+
break;
|
|
360
|
+
case klvPid:
|
|
361
|
+
if (stt) {
|
|
362
|
+
if (klvData && (pes = parsePES(klvData, this.logger))) {
|
|
363
|
+
this.parseKlvPES(id3Track, pes);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
klvData = { data: [], size: 0 };
|
|
367
|
+
}
|
|
368
|
+
if (klvData) {
|
|
369
|
+
klvData.data.push(data.subarray(offset, start + PACKET_LENGTH));
|
|
370
|
+
klvData.size += start + PACKET_LENGTH - offset;
|
|
371
|
+
}
|
|
372
|
+
break;
|
|
373
|
+
case 0:
|
|
374
|
+
if (stt) {
|
|
375
|
+
offset += data[offset] + 1;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
pmtId = this._pmtId = parsePAT(data, offset);
|
|
379
|
+
// this.logger.log('PMT PID:' + this._pmtId);
|
|
380
|
+
break;
|
|
381
|
+
case pmtId: {
|
|
382
|
+
if (stt) {
|
|
383
|
+
offset += data[offset] + 1;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const parsedPIDs = parsePMT(
|
|
387
|
+
data,
|
|
388
|
+
offset,
|
|
389
|
+
this.typeSupported,
|
|
390
|
+
isSampleAes,
|
|
391
|
+
this.observer,
|
|
392
|
+
this.logger,
|
|
393
|
+
this.config,
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
// only update track id if track PID found while parsing PMT
|
|
397
|
+
// this is to avoid resetting the PID to -1 in case
|
|
398
|
+
// track PID transiently disappears from the stream
|
|
399
|
+
// this could happen in case of transient missing audio samples for example
|
|
400
|
+
// NOTE this is only the PID of the track as found in TS,
|
|
401
|
+
// but we are not using this for MP4 track IDs.
|
|
402
|
+
videoPid = parsedPIDs.videoPid;
|
|
403
|
+
if (videoPid > 0) {
|
|
404
|
+
videoTrack.pid = videoPid;
|
|
405
|
+
videoTrack.segmentCodec = parsedPIDs.segmentVideoCodec;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
audioPid = parsedPIDs.audioPid;
|
|
409
|
+
if (audioPid > 0) {
|
|
410
|
+
audioTrack.pid = audioPid;
|
|
411
|
+
audioTrack.segmentCodec = parsedPIDs.segmentAudioCodec;
|
|
412
|
+
}
|
|
413
|
+
id3Pid = parsedPIDs.id3Pid;
|
|
414
|
+
if (id3Pid > 0) {
|
|
415
|
+
id3Track.pid = id3Pid;
|
|
416
|
+
}
|
|
417
|
+
klvPid = parsedPIDs.klvPid;
|
|
418
|
+
if (klvPid > 0) {
|
|
419
|
+
this._klvPid = klvPid;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (unknownPID !== null && !pmtParsed) {
|
|
423
|
+
this.logger.warn(
|
|
424
|
+
`MPEG-TS PMT found at ${start} after unknown PID '${unknownPID}'. Backtracking to sync byte @${syncOffset} to parse all TS packets.`,
|
|
425
|
+
);
|
|
426
|
+
unknownPID = null;
|
|
427
|
+
// we set it to -188, the += 188 in the for loop will reset start to 0
|
|
428
|
+
start = syncOffset - 188;
|
|
429
|
+
}
|
|
430
|
+
pmtParsed = this.pmtParsed = true;
|
|
431
|
+
break;
|
|
432
|
+
}
|
|
433
|
+
case 0x11:
|
|
434
|
+
case 0x1fff:
|
|
435
|
+
break;
|
|
436
|
+
default:
|
|
437
|
+
unknownPID = pid;
|
|
438
|
+
break;
|
|
439
|
+
}
|
|
440
|
+
} else {
|
|
441
|
+
tsPacketErrors++;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (tsPacketErrors > 0) {
|
|
446
|
+
emitParsingError(
|
|
447
|
+
this.observer,
|
|
448
|
+
new Error(
|
|
449
|
+
`Found ${tsPacketErrors} TS packet/s that do not start with 0x47`,
|
|
450
|
+
),
|
|
451
|
+
undefined,
|
|
452
|
+
this.logger,
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
videoTrack.pesData = videoData;
|
|
457
|
+
audioTrack.pesData = audioData;
|
|
458
|
+
id3Track.pesData = id3Data;
|
|
459
|
+
|
|
460
|
+
const demuxResult: DemuxerResult = {
|
|
461
|
+
audioTrack,
|
|
462
|
+
videoTrack,
|
|
463
|
+
id3Track,
|
|
464
|
+
textTrack,
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
if (flush) {
|
|
468
|
+
this.extractRemainingSamples(demuxResult);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return demuxResult;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
public flush(): DemuxerResult | Promise<DemuxerResult> {
|
|
475
|
+
const { remainderData } = this;
|
|
476
|
+
this.remainderData = null;
|
|
477
|
+
let result: DemuxerResult;
|
|
478
|
+
if (remainderData) {
|
|
479
|
+
result = this.demux(remainderData, -1, false, true);
|
|
480
|
+
} else {
|
|
481
|
+
result = {
|
|
482
|
+
videoTrack: this._videoTrack as DemuxedVideoTrack,
|
|
483
|
+
audioTrack: this._audioTrack as DemuxedAudioTrack,
|
|
484
|
+
id3Track: this._id3Track as DemuxedMetadataTrack,
|
|
485
|
+
textTrack: this._txtTrack as DemuxedUserdataTrack,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
this.extractRemainingSamples(result);
|
|
489
|
+
if (this.sampleAes) {
|
|
490
|
+
return this.decrypt(result, this.sampleAes);
|
|
491
|
+
}
|
|
492
|
+
return result;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
private extractRemainingSamples(demuxResult: DemuxerResult) {
|
|
496
|
+
const { audioTrack, videoTrack, id3Track, textTrack } = demuxResult;
|
|
497
|
+
const videoData = videoTrack.pesData;
|
|
498
|
+
const audioData = audioTrack.pesData;
|
|
499
|
+
const id3Data = id3Track.pesData;
|
|
500
|
+
const videoIntegrityChecker = this.videoIntegrityChecker;
|
|
501
|
+
// try to parse last PES packets
|
|
502
|
+
let pes: PES | null;
|
|
503
|
+
if (
|
|
504
|
+
videoData &&
|
|
505
|
+
!videoIntegrityChecker?.isCorrupted &&
|
|
506
|
+
(pes = parsePES(videoData, this.logger))
|
|
507
|
+
) {
|
|
508
|
+
this.readyVideoParser(videoTrack.segmentCodec);
|
|
509
|
+
if (this.videoParser !== null) {
|
|
510
|
+
this.videoParser.parsePES(
|
|
511
|
+
videoTrack as DemuxedVideoTrack,
|
|
512
|
+
textTrack as DemuxedUserdataTrack,
|
|
513
|
+
pes,
|
|
514
|
+
true,
|
|
515
|
+
);
|
|
516
|
+
videoTrack.pesData = null;
|
|
517
|
+
}
|
|
518
|
+
} else {
|
|
519
|
+
// either avcData null or PES truncated, keep it for next frag parsing
|
|
520
|
+
videoTrack.pesData = videoData;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if (audioData && (pes = parsePES(audioData, this.logger))) {
|
|
524
|
+
switch (audioTrack.segmentCodec) {
|
|
525
|
+
case 'aac':
|
|
526
|
+
this.parseAACPES(audioTrack, pes);
|
|
527
|
+
break;
|
|
528
|
+
case 'mp3':
|
|
529
|
+
this.parseMPEGPES(audioTrack, pes);
|
|
530
|
+
break;
|
|
531
|
+
case 'ac3':
|
|
532
|
+
if (__USE_M2TS_ADVANCED_CODECS__) {
|
|
533
|
+
this.parseAC3PES(audioTrack, pes);
|
|
534
|
+
}
|
|
535
|
+
break;
|
|
536
|
+
}
|
|
537
|
+
audioTrack.pesData = null;
|
|
538
|
+
} else {
|
|
539
|
+
if (audioData?.size) {
|
|
540
|
+
this.logger.log(
|
|
541
|
+
'last AAC PES packet truncated,might overlap between fragments',
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// either audioData null or PES truncated, keep it for next frag parsing
|
|
546
|
+
audioTrack.pesData = audioData;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (id3Data && (pes = parsePES(id3Data, this.logger))) {
|
|
550
|
+
this.parseID3PES(id3Track, pes);
|
|
551
|
+
id3Track.pesData = null;
|
|
552
|
+
} else {
|
|
553
|
+
// either id3Data null or PES truncated, keep it for next frag parsing
|
|
554
|
+
id3Track.pesData = id3Data;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
public demuxSampleAes(
|
|
559
|
+
data: Uint8Array,
|
|
560
|
+
keyData: KeyData,
|
|
561
|
+
timeOffset: number,
|
|
562
|
+
): Promise<DemuxerResult> {
|
|
563
|
+
const demuxResult = this.demux(
|
|
564
|
+
data,
|
|
565
|
+
timeOffset,
|
|
566
|
+
true,
|
|
567
|
+
!this.config.progressive,
|
|
568
|
+
);
|
|
569
|
+
const sampleAes = (this.sampleAes = new SampleAesDecrypter(
|
|
570
|
+
this.observer,
|
|
571
|
+
this.config,
|
|
572
|
+
keyData,
|
|
573
|
+
));
|
|
574
|
+
return this.decrypt(demuxResult, sampleAes);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
private readyVideoParser(codec: string | undefined) {
|
|
578
|
+
if (this.videoParser === null) {
|
|
579
|
+
if (codec === 'avc') {
|
|
580
|
+
this.videoParser = new AvcVideoParser();
|
|
581
|
+
} else if (__USE_M2TS_ADVANCED_CODECS__ && codec === 'hevc') {
|
|
582
|
+
this.videoParser = new HevcVideoParser();
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
private decrypt(
|
|
588
|
+
demuxResult: DemuxerResult,
|
|
589
|
+
sampleAes: SampleAesDecrypter,
|
|
590
|
+
): Promise<DemuxerResult> {
|
|
591
|
+
return new Promise((resolve) => {
|
|
592
|
+
const { audioTrack, videoTrack } = demuxResult;
|
|
593
|
+
if (audioTrack.samples && audioTrack.segmentCodec === 'aac') {
|
|
594
|
+
sampleAes.decryptAacSamples(
|
|
595
|
+
(audioTrack as DemuxedAAC).samples,
|
|
596
|
+
0,
|
|
597
|
+
() => {
|
|
598
|
+
if (videoTrack.samples) {
|
|
599
|
+
sampleAes.decryptAvcSamples(videoTrack.samples, 0, 0, () => {
|
|
600
|
+
resolve(demuxResult);
|
|
601
|
+
});
|
|
602
|
+
} else {
|
|
603
|
+
resolve(demuxResult);
|
|
604
|
+
}
|
|
605
|
+
},
|
|
606
|
+
);
|
|
607
|
+
} else if (videoTrack.samples) {
|
|
608
|
+
sampleAes.decryptAvcSamples(videoTrack.samples, 0, 0, () => {
|
|
609
|
+
resolve(demuxResult);
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
public destroy() {
|
|
616
|
+
if (this.observer) {
|
|
617
|
+
this.observer.removeAllListeners();
|
|
618
|
+
}
|
|
619
|
+
// @ts-ignore
|
|
620
|
+
this.config = this.logger = this.observer = null;
|
|
621
|
+
this.aacOverFlow =
|
|
622
|
+
this.videoParser =
|
|
623
|
+
this.remainderData =
|
|
624
|
+
this.sampleAes =
|
|
625
|
+
null;
|
|
626
|
+
this._videoTrack =
|
|
627
|
+
this._audioTrack =
|
|
628
|
+
this._id3Track =
|
|
629
|
+
this._txtTrack =
|
|
630
|
+
undefined;
|
|
631
|
+
|
|
632
|
+
this.videoIntegrityChecker = null;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
private parseAACPES(track: DemuxedAudioTrack, pes: PES) {
|
|
636
|
+
let startOffset = 0;
|
|
637
|
+
const aacOverFlow = this.aacOverFlow;
|
|
638
|
+
let data = pes.data;
|
|
639
|
+
if (aacOverFlow) {
|
|
640
|
+
this.aacOverFlow = null;
|
|
641
|
+
const frameMissingBytes = aacOverFlow.missing;
|
|
642
|
+
const sampleLength = aacOverFlow.sample.unit.byteLength;
|
|
643
|
+
// logger.log(`AAC: append overflowing ${sampleLength} bytes to beginning of new PES`);
|
|
644
|
+
if (frameMissingBytes === -1) {
|
|
645
|
+
data = appendUint8Array(aacOverFlow.sample.unit, data);
|
|
646
|
+
} else {
|
|
647
|
+
const frameOverflowBytes = sampleLength - frameMissingBytes;
|
|
648
|
+
aacOverFlow.sample.unit.set(
|
|
649
|
+
data.subarray(0, frameMissingBytes),
|
|
650
|
+
frameOverflowBytes,
|
|
651
|
+
);
|
|
652
|
+
track.samples.push(aacOverFlow.sample);
|
|
653
|
+
startOffset = aacOverFlow.missing;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
// look for ADTS header (0xFFFx)
|
|
657
|
+
let offset: number;
|
|
658
|
+
let len: number;
|
|
659
|
+
for (offset = startOffset, len = data.length; offset < len - 1; offset++) {
|
|
660
|
+
if (ADTS.isHeader(data, offset)) {
|
|
661
|
+
break;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
// if ADTS header does not start straight from the beginning of the PES payload, raise an error
|
|
665
|
+
if (offset !== startOffset) {
|
|
666
|
+
let reason: string;
|
|
667
|
+
const recoverable = offset < len - 1;
|
|
668
|
+
if (recoverable) {
|
|
669
|
+
reason = `AAC PES did not start with ADTS header,offset:${offset}`;
|
|
670
|
+
} else {
|
|
671
|
+
reason = 'No ADTS header found in AAC PES';
|
|
672
|
+
}
|
|
673
|
+
emitParsingError(
|
|
674
|
+
this.observer,
|
|
675
|
+
new Error(reason),
|
|
676
|
+
recoverable,
|
|
677
|
+
this.logger,
|
|
678
|
+
);
|
|
679
|
+
if (!recoverable) {
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
ADTS.initTrackConfig(track, this.observer, data, offset, this.audioCodec);
|
|
685
|
+
|
|
686
|
+
let pts: number;
|
|
687
|
+
if (pes.pts !== undefined) {
|
|
688
|
+
pts = pes.pts;
|
|
689
|
+
} else if (aacOverFlow) {
|
|
690
|
+
// if last AAC frame is overflowing, we should ensure timestamps are contiguous:
|
|
691
|
+
// first sample PTS should be equal to last sample PTS + frameDuration
|
|
692
|
+
const frameDuration = ADTS.getFrameDuration(track.samplerate as number);
|
|
693
|
+
pts = aacOverFlow.sample.pts + frameDuration;
|
|
694
|
+
} else {
|
|
695
|
+
this.logger.warn('[tsdemuxer]: AAC PES unknown PTS');
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// scan for aac samples
|
|
700
|
+
let frameIndex = 0;
|
|
701
|
+
let frame;
|
|
702
|
+
while (offset < len) {
|
|
703
|
+
frame = ADTS.appendFrame(track, data, offset, pts, frameIndex);
|
|
704
|
+
offset += frame.length;
|
|
705
|
+
if (!frame.missing) {
|
|
706
|
+
frameIndex++;
|
|
707
|
+
for (; offset < len - 1; offset++) {
|
|
708
|
+
if (ADTS.isHeader(data, offset)) {
|
|
709
|
+
break;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
} else {
|
|
713
|
+
this.aacOverFlow = frame;
|
|
714
|
+
break;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
private parseMPEGPES(track: DemuxedAudioTrack, pes: PES) {
|
|
720
|
+
const data = pes.data;
|
|
721
|
+
const length = data.length;
|
|
722
|
+
let frameIndex = 0;
|
|
723
|
+
let offset = 0;
|
|
724
|
+
const pts = pes.pts;
|
|
725
|
+
if (pts === undefined) {
|
|
726
|
+
this.logger.warn('[tsdemuxer]: MPEG PES unknown PTS');
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
while (offset < length) {
|
|
731
|
+
if (MpegAudio.isHeader(data, offset)) {
|
|
732
|
+
const frame = MpegAudio.appendFrame(
|
|
733
|
+
track,
|
|
734
|
+
data,
|
|
735
|
+
offset,
|
|
736
|
+
pts,
|
|
737
|
+
frameIndex,
|
|
738
|
+
);
|
|
739
|
+
if (frame) {
|
|
740
|
+
offset += frame.length;
|
|
741
|
+
frameIndex++;
|
|
742
|
+
} else {
|
|
743
|
+
// logger.log('Unable to parse Mpeg audio frame');
|
|
744
|
+
break;
|
|
745
|
+
}
|
|
746
|
+
} else {
|
|
747
|
+
// nothing found, keep looking
|
|
748
|
+
offset++;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
private parseAC3PES(track: DemuxedAudioTrack, pes: PES) {
|
|
754
|
+
if (__USE_M2TS_ADVANCED_CODECS__) {
|
|
755
|
+
const data = pes.data;
|
|
756
|
+
const pts = pes.pts;
|
|
757
|
+
if (pts === undefined) {
|
|
758
|
+
this.logger.warn('[tsdemuxer]: AC3 PES unknown PTS');
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
const length = data.length;
|
|
762
|
+
let frameIndex = 0;
|
|
763
|
+
let offset = 0;
|
|
764
|
+
let parsed;
|
|
765
|
+
|
|
766
|
+
while (
|
|
767
|
+
offset < length &&
|
|
768
|
+
(parsed = AC3.appendFrame(track, data, offset, pts, frameIndex++)) > 0
|
|
769
|
+
) {
|
|
770
|
+
offset += parsed;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
private parseID3PES(id3Track: DemuxedMetadataTrack, pes: PES) {
|
|
776
|
+
if (pes.pts === undefined) {
|
|
777
|
+
this.logger.warn('[tsdemuxer]: ID3 PES unknown PTS');
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
const id3Sample = Object.assign({}, pes as Required<PES>, {
|
|
781
|
+
type: this._videoTrack ? MetadataSchema.emsg : MetadataSchema.audioId3,
|
|
782
|
+
duration: Number.POSITIVE_INFINITY,
|
|
783
|
+
});
|
|
784
|
+
id3Track.samples.push(id3Sample);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
private parseKlvPES(id3Track: DemuxedMetadataTrack, pes: PES) {
|
|
788
|
+
const pts = pes.pts ?? pes.dts;
|
|
789
|
+
if (pts === undefined) {
|
|
790
|
+
this.logger.warn('[tsdemuxer]: KLV PES unknown PTS and DTS');
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
// Parse KLV data from PES payload
|
|
794
|
+
// KLV format: Key (16 bytes) + Length (BER encoded) + Value (variable)
|
|
795
|
+
const klvData = pes.data;
|
|
796
|
+
if (klvData.length < 16) {
|
|
797
|
+
this.logger.warn('[tsdemuxer]: KLV PES payload too short');
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// Extract KLV packets from the PES payload
|
|
802
|
+
// Multiple KLV packets can be in a single PES packet
|
|
803
|
+
let offset = 0;
|
|
804
|
+
while (offset < klvData.length) {
|
|
805
|
+
if (offset + 16 > klvData.length) {
|
|
806
|
+
break; // Not enough data for a KLV key
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
const klvStart = offset;
|
|
810
|
+
|
|
811
|
+
// Extract Key (16 bytes for MISB ST 0601)
|
|
812
|
+
offset += 16;
|
|
813
|
+
|
|
814
|
+
if (offset >= klvData.length) {
|
|
815
|
+
break; // No length field
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// Parse BER-encoded Length
|
|
819
|
+
const firstByte = klvData[offset];
|
|
820
|
+
offset += 1;
|
|
821
|
+
|
|
822
|
+
let length = 0;
|
|
823
|
+
|
|
824
|
+
if (firstByte & 0x80) {
|
|
825
|
+
// Long form BER encoding
|
|
826
|
+
const lengthBytes = firstByte & 0x7f;
|
|
827
|
+
if (
|
|
828
|
+
lengthBytes === 0 ||
|
|
829
|
+
lengthBytes > 4 ||
|
|
830
|
+
offset + lengthBytes > klvData.length
|
|
831
|
+
) {
|
|
832
|
+
this.logger.warn('[tsdemuxer]: Invalid KLV length encoding');
|
|
833
|
+
break;
|
|
834
|
+
}
|
|
835
|
+
for (let i = 0; i < lengthBytes; i++) {
|
|
836
|
+
length = (length << 8) | klvData[offset + i];
|
|
837
|
+
}
|
|
838
|
+
offset += lengthBytes;
|
|
839
|
+
} else {
|
|
840
|
+
// Short form BER encoding
|
|
841
|
+
length = firstByte;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
if (offset + length > klvData.length) {
|
|
845
|
+
this.logger.warn('[tsdemuxer]: KLV value extends beyond PES payload');
|
|
846
|
+
break;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// Extract the complete KLV packet (Key + Length + Value)
|
|
850
|
+
const klvEnd = offset + length;
|
|
851
|
+
const klvPacket = klvData.subarray(klvStart, klvEnd);
|
|
852
|
+
|
|
853
|
+
const klvSample = {
|
|
854
|
+
data: klvPacket,
|
|
855
|
+
len: klvPacket.byteLength,
|
|
856
|
+
pts: pts,
|
|
857
|
+
dts: pes.dts ?? pts,
|
|
858
|
+
type: MetadataSchema.misbklv,
|
|
859
|
+
duration: Number.POSITIVE_INFINITY,
|
|
860
|
+
};
|
|
861
|
+
id3Track.samples.push(klvSample);
|
|
862
|
+
|
|
863
|
+
offset = klvEnd;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
function parsePID(data: Uint8Array, offset: number): number {
|
|
869
|
+
// pid is a 13-bit field starting at the last bit of TS[1]
|
|
870
|
+
return ((data[offset + 1] & 0x1f) << 8) + data[offset + 2];
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
function parsePAT(data: Uint8Array, offset: number): number {
|
|
874
|
+
// skip the PSI header and parse the first PMT entry
|
|
875
|
+
return ((data[offset + 10] & 0x1f) << 8) | data[offset + 11];
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
function parsePMT(
|
|
879
|
+
data: Uint8Array,
|
|
880
|
+
offset: number,
|
|
881
|
+
typeSupported: TypeSupported,
|
|
882
|
+
isSampleAes: boolean,
|
|
883
|
+
observer: HlsEventEmitter,
|
|
884
|
+
logger: ILogger,
|
|
885
|
+
config: HlsConfig,
|
|
886
|
+
) {
|
|
887
|
+
const result = {
|
|
888
|
+
audioPid: -1,
|
|
889
|
+
videoPid: -1,
|
|
890
|
+
id3Pid: -1,
|
|
891
|
+
klvPid: -1,
|
|
892
|
+
segmentVideoCodec: 'avc' as 'avc' | 'hevc',
|
|
893
|
+
segmentAudioCodec: 'aac' as 'aac' | 'ac3' | 'mp3',
|
|
894
|
+
};
|
|
895
|
+
const sectionLength = ((data[offset + 1] & 0x0f) << 8) | data[offset + 2];
|
|
896
|
+
const tableEnd = offset + 3 + sectionLength - 4;
|
|
897
|
+
// to determine where the table is, we have to figure out how
|
|
898
|
+
// long the program info descriptors are
|
|
899
|
+
const programInfoLength =
|
|
900
|
+
((data[offset + 10] & 0x0f) << 8) | data[offset + 11];
|
|
901
|
+
// advance the offset to the first entry in the mapping table
|
|
902
|
+
offset += 12 + programInfoLength;
|
|
903
|
+
while (offset < tableEnd) {
|
|
904
|
+
const pid = parsePID(data, offset);
|
|
905
|
+
const esInfoLength = ((data[offset + 3] & 0x0f) << 8) | data[offset + 4];
|
|
906
|
+
switch (data[offset]) {
|
|
907
|
+
case 0xcf: // SAMPLE-AES AAC
|
|
908
|
+
if (!isSampleAes) {
|
|
909
|
+
logEncryptedSamplesFoundInUnencryptedStream('ADTS AAC', logger);
|
|
910
|
+
break;
|
|
911
|
+
}
|
|
912
|
+
/* falls through */
|
|
913
|
+
case 0x0f: // ISO/IEC 13818-7 ADTS AAC (MPEG-2 lower bit-rate audio)
|
|
914
|
+
// logger.log('AAC PID:' + pid);
|
|
915
|
+
if (result.audioPid === -1) {
|
|
916
|
+
result.audioPid = pid;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
break;
|
|
920
|
+
|
|
921
|
+
// Packetized metadata (ID3)
|
|
922
|
+
case 0x15:
|
|
923
|
+
// logger.log('ID3 PID:' + pid);
|
|
924
|
+
if (result.id3Pid === -1) {
|
|
925
|
+
result.id3Pid = pid;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
break;
|
|
929
|
+
|
|
930
|
+
case 0xdb: // SAMPLE-AES AVC
|
|
931
|
+
if (!isSampleAes) {
|
|
932
|
+
logEncryptedSamplesFoundInUnencryptedStream('H.264', logger);
|
|
933
|
+
break;
|
|
934
|
+
}
|
|
935
|
+
/* falls through */
|
|
936
|
+
case 0x1b: // ITU-T Rec. H.264 and ISO/IEC 14496-10 (lower bit-rate video)
|
|
937
|
+
// logger.log('AVC PID:' + pid);
|
|
938
|
+
if (result.videoPid === -1) {
|
|
939
|
+
result.videoPid = pid;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
break;
|
|
943
|
+
|
|
944
|
+
// ISO/IEC 11172-3 (MPEG-1 audio)
|
|
945
|
+
// or ISO/IEC 13818-3 (MPEG-2 halved sample rate audio)
|
|
946
|
+
case 0x03:
|
|
947
|
+
case 0x04:
|
|
948
|
+
// logger.log('MPEG PID:' + pid);
|
|
949
|
+
if (!typeSupported.mpeg && !typeSupported.mp3) {
|
|
950
|
+
logger.log('MPEG audio found, not supported in this browser');
|
|
951
|
+
} else if (result.audioPid === -1) {
|
|
952
|
+
result.audioPid = pid;
|
|
953
|
+
result.segmentAudioCodec = 'mp3';
|
|
954
|
+
}
|
|
955
|
+
break;
|
|
956
|
+
|
|
957
|
+
case 0xc1: // SAMPLE-AES AC3
|
|
958
|
+
if (!isSampleAes) {
|
|
959
|
+
logEncryptedSamplesFoundInUnencryptedStream('AC-3', logger);
|
|
960
|
+
break;
|
|
961
|
+
}
|
|
962
|
+
/* falls through */
|
|
963
|
+
case 0x81:
|
|
964
|
+
if (__USE_M2TS_ADVANCED_CODECS__) {
|
|
965
|
+
if (!typeSupported.ac3) {
|
|
966
|
+
logger.log('AC-3 audio found, not supported in this browser');
|
|
967
|
+
} else if (result.audioPid === -1) {
|
|
968
|
+
result.audioPid = pid;
|
|
969
|
+
result.segmentAudioCodec = 'ac3';
|
|
970
|
+
}
|
|
971
|
+
} else {
|
|
972
|
+
logger.warn('AC-3 in M2TS support not included in build');
|
|
973
|
+
}
|
|
974
|
+
break;
|
|
975
|
+
|
|
976
|
+
case 0x06:
|
|
977
|
+
// stream_type 6 can mean a lot of different things in case of DVB.
|
|
978
|
+
// We need to look at the descriptors. Right now, we're only interested
|
|
979
|
+
// in AC-3 audio, so we do the descriptor parsing only when we don't have
|
|
980
|
+
// an audio PID yet.
|
|
981
|
+
if (esInfoLength > 0) {
|
|
982
|
+
let parsePos = offset + 5;
|
|
983
|
+
let remaining = esInfoLength;
|
|
984
|
+
|
|
985
|
+
while (remaining > 2) {
|
|
986
|
+
const descriptorId = data[parsePos];
|
|
987
|
+
|
|
988
|
+
switch (descriptorId) {
|
|
989
|
+
case 0x6a: // DVB Descriptor for AC-3
|
|
990
|
+
if (result.audioPid === -1) {
|
|
991
|
+
if (__USE_M2TS_ADVANCED_CODECS__) {
|
|
992
|
+
if (typeSupported.ac3 !== true) {
|
|
993
|
+
logger.log(
|
|
994
|
+
'AC-3 audio found, not supported in this browser for now',
|
|
995
|
+
);
|
|
996
|
+
} else {
|
|
997
|
+
result.audioPid = pid;
|
|
998
|
+
result.segmentAudioCodec = 'ac3';
|
|
999
|
+
}
|
|
1000
|
+
} else {
|
|
1001
|
+
logger.warn('AC-3 in M2TS support not included in build');
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
break;
|
|
1005
|
+
case 0x05: // MISB KLV descriptor
|
|
1006
|
+
if (result.klvPid === -1 && config.enableEmsgKLVMetadata) {
|
|
1007
|
+
result.klvPid = pid;
|
|
1008
|
+
logger.log(`KLV metadata PID found: ${pid}`);
|
|
1009
|
+
}
|
|
1010
|
+
break;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
const descriptorLen = data[parsePos + 1] + 2;
|
|
1014
|
+
parsePos += descriptorLen;
|
|
1015
|
+
remaining -= descriptorLen;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
break;
|
|
1019
|
+
|
|
1020
|
+
case 0xc2: // SAMPLE-AES EC3
|
|
1021
|
+
/* falls through */
|
|
1022
|
+
case 0x87:
|
|
1023
|
+
emitParsingError(
|
|
1024
|
+
observer,
|
|
1025
|
+
new Error('Unsupported EC-3 in M2TS found'),
|
|
1026
|
+
undefined,
|
|
1027
|
+
logger,
|
|
1028
|
+
);
|
|
1029
|
+
return result;
|
|
1030
|
+
|
|
1031
|
+
case 0x24: // ITU-T Rec. H.265 and ISO/IEC 23008-2 (HEVC)
|
|
1032
|
+
if (__USE_M2TS_ADVANCED_CODECS__) {
|
|
1033
|
+
if (result.videoPid === -1) {
|
|
1034
|
+
result.videoPid = pid;
|
|
1035
|
+
result.segmentVideoCodec = 'hevc';
|
|
1036
|
+
logger.log('HEVC in M2TS found');
|
|
1037
|
+
}
|
|
1038
|
+
} else {
|
|
1039
|
+
emitParsingError(
|
|
1040
|
+
observer,
|
|
1041
|
+
new Error('Unsupported HEVC in M2TS found'),
|
|
1042
|
+
undefined,
|
|
1043
|
+
logger,
|
|
1044
|
+
);
|
|
1045
|
+
return result;
|
|
1046
|
+
}
|
|
1047
|
+
break;
|
|
1048
|
+
|
|
1049
|
+
default:
|
|
1050
|
+
// logger.log('unknown stream type:' + data[offset]);
|
|
1051
|
+
break;
|
|
1052
|
+
}
|
|
1053
|
+
// move to the next table entry
|
|
1054
|
+
// skip past the elementary stream descriptors, if present
|
|
1055
|
+
offset += esInfoLength + 5;
|
|
1056
|
+
}
|
|
1057
|
+
return result;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
function emitParsingError(
|
|
1061
|
+
observer: HlsEventEmitter,
|
|
1062
|
+
error: Error,
|
|
1063
|
+
levelRetry: boolean | undefined,
|
|
1064
|
+
logger: ILogger,
|
|
1065
|
+
) {
|
|
1066
|
+
logger.warn(`parsing error: ${error.message}`);
|
|
1067
|
+
observer.emit(Events.ERROR, Events.ERROR, {
|
|
1068
|
+
type: ErrorTypes.MEDIA_ERROR,
|
|
1069
|
+
details: ErrorDetails.FRAG_PARSING_ERROR,
|
|
1070
|
+
fatal: false,
|
|
1071
|
+
levelRetry,
|
|
1072
|
+
error,
|
|
1073
|
+
reason: error.message,
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
function logEncryptedSamplesFoundInUnencryptedStream(
|
|
1078
|
+
type: string,
|
|
1079
|
+
logger: ILogger,
|
|
1080
|
+
) {
|
|
1081
|
+
logger.log(`${type} with AES-128-CBC encryption found in unencrypted stream`);
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
function parsePES(stream: ElementaryStreamData, logger: ILogger): PES | null {
|
|
1085
|
+
let i = 0;
|
|
1086
|
+
let frag: Uint8Array;
|
|
1087
|
+
let pesLen: number;
|
|
1088
|
+
let pesHdrLen: number;
|
|
1089
|
+
let pesPts: number | undefined;
|
|
1090
|
+
let pesDts: number | undefined;
|
|
1091
|
+
const data = stream.data;
|
|
1092
|
+
// safety check
|
|
1093
|
+
if (!stream || stream.size === 0) {
|
|
1094
|
+
return null;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
// we might need up to 19 bytes to read PES header
|
|
1098
|
+
// if first chunk of data is less than 19 bytes, let's merge it with following ones until we get 19 bytes
|
|
1099
|
+
// usually only one merge is needed (and this is rare ...)
|
|
1100
|
+
while (data[0].length < 19 && data.length > 1) {
|
|
1101
|
+
data[0] = appendUint8Array(data[0], data[1]);
|
|
1102
|
+
data.splice(1, 1);
|
|
1103
|
+
}
|
|
1104
|
+
// retrieve PTS/DTS from first fragment
|
|
1105
|
+
frag = data[0];
|
|
1106
|
+
const pesPrefix = (frag[0] << 16) + (frag[1] << 8) + frag[2];
|
|
1107
|
+
if (pesPrefix === 1) {
|
|
1108
|
+
pesLen = (frag[4] << 8) + frag[5];
|
|
1109
|
+
// if PES parsed length is not zero and greater than total received length, stop parsing. PES might be truncated
|
|
1110
|
+
// minus 6 : PES header size
|
|
1111
|
+
if (pesLen && pesLen > stream.size - 6) {
|
|
1112
|
+
return null;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
const pesFlags = frag[7];
|
|
1116
|
+
if (pesFlags & 0xc0) {
|
|
1117
|
+
/* PES header described here : http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
|
|
1118
|
+
as PTS / DTS is 33 bit we cannot use bitwise operator in JS,
|
|
1119
|
+
as Bitwise operators treat their operands as a sequence of 32 bits */
|
|
1120
|
+
pesPts =
|
|
1121
|
+
(frag[9] & 0x0e) * 536870912 + // 1 << 29
|
|
1122
|
+
(frag[10] & 0xff) * 4194304 + // 1 << 22
|
|
1123
|
+
(frag[11] & 0xfe) * 16384 + // 1 << 14
|
|
1124
|
+
(frag[12] & 0xff) * 128 + // 1 << 7
|
|
1125
|
+
(frag[13] & 0xfe) / 2;
|
|
1126
|
+
|
|
1127
|
+
if (pesFlags & 0x40) {
|
|
1128
|
+
pesDts =
|
|
1129
|
+
(frag[14] & 0x0e) * 536870912 + // 1 << 29
|
|
1130
|
+
(frag[15] & 0xff) * 4194304 + // 1 << 22
|
|
1131
|
+
(frag[16] & 0xfe) * 16384 + // 1 << 14
|
|
1132
|
+
(frag[17] & 0xff) * 128 + // 1 << 7
|
|
1133
|
+
(frag[18] & 0xfe) / 2;
|
|
1134
|
+
|
|
1135
|
+
if (pesPts - pesDts > 60 * 90000) {
|
|
1136
|
+
logger.warn(
|
|
1137
|
+
`${Math.round(
|
|
1138
|
+
(pesPts - pesDts) / 90000,
|
|
1139
|
+
)}s delta between PTS and DTS, align them`,
|
|
1140
|
+
);
|
|
1141
|
+
pesPts = pesDts;
|
|
1142
|
+
}
|
|
1143
|
+
} else {
|
|
1144
|
+
pesDts = pesPts;
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
pesHdrLen = frag[8];
|
|
1148
|
+
// 9 bytes : 6 bytes for PES header + 3 bytes for PES extension
|
|
1149
|
+
let payloadStartOffset = pesHdrLen + 9;
|
|
1150
|
+
if (stream.size <= payloadStartOffset) {
|
|
1151
|
+
return null;
|
|
1152
|
+
}
|
|
1153
|
+
stream.size -= payloadStartOffset;
|
|
1154
|
+
// reassemble PES packet
|
|
1155
|
+
const pesData = new Uint8Array(stream.size);
|
|
1156
|
+
for (let j = 0, dataLen = data.length; j < dataLen; j++) {
|
|
1157
|
+
frag = data[j];
|
|
1158
|
+
let len = frag.byteLength;
|
|
1159
|
+
if (payloadStartOffset) {
|
|
1160
|
+
if (payloadStartOffset > len) {
|
|
1161
|
+
// trim full frag if PES header bigger than frag
|
|
1162
|
+
payloadStartOffset -= len;
|
|
1163
|
+
continue;
|
|
1164
|
+
} else {
|
|
1165
|
+
// trim partial frag if PES header smaller than frag
|
|
1166
|
+
frag = frag.subarray(payloadStartOffset);
|
|
1167
|
+
len -= payloadStartOffset;
|
|
1168
|
+
payloadStartOffset = 0;
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
pesData.set(frag, i);
|
|
1172
|
+
i += len;
|
|
1173
|
+
}
|
|
1174
|
+
if (pesLen) {
|
|
1175
|
+
// payload size : remove PES header + PES extension
|
|
1176
|
+
pesLen -= pesHdrLen + 3;
|
|
1177
|
+
}
|
|
1178
|
+
return { data: pesData, pts: pesPts, dts: pesDts, len: pesLen };
|
|
1179
|
+
}
|
|
1180
|
+
return null;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
// See FFMpeg for reference: https://github.com/FFmpeg/FFmpeg/blob/e4c8e80a2efee275f2a10fcf0424c9fc1d86e309/libavformat/mpegts.c#L2811-L2834
|
|
1184
|
+
class PacketsIntegrityChecker {
|
|
1185
|
+
private readonly logger: ILogger;
|
|
1186
|
+
|
|
1187
|
+
private pid: number = 0;
|
|
1188
|
+
private lastContinuityCounter = -1;
|
|
1189
|
+
private integrityState: 'ok' | 'tei-bit' | 'cc-failed' = 'ok';
|
|
1190
|
+
|
|
1191
|
+
constructor(logger: ILogger) {
|
|
1192
|
+
this.logger = logger;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
public get isCorrupted(): boolean {
|
|
1196
|
+
return this.integrityState !== 'ok';
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
public reset(pid: number) {
|
|
1200
|
+
this.pid = pid;
|
|
1201
|
+
this.lastContinuityCounter = -1;
|
|
1202
|
+
this.integrityState = 'ok';
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
public handlePacket(data: Uint8Array) {
|
|
1206
|
+
if (data.byteLength < 4) {
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
const pid = parsePID(data, 0);
|
|
1211
|
+
if (pid !== this.pid) {
|
|
1212
|
+
this.logger.debug(`Packet PID mismatch, expected ${this.pid} got ${pid}`);
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
const adaptationFieldControl = (data[3] & 0x30) >> 4;
|
|
1217
|
+
if (adaptationFieldControl === 0) {
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
const continuityCounter = data[3] & 0xf;
|
|
1221
|
+
|
|
1222
|
+
const lastContinuityCounter = this.lastContinuityCounter;
|
|
1223
|
+
this.lastContinuityCounter = continuityCounter;
|
|
1224
|
+
|
|
1225
|
+
const hasPayload = (adaptationFieldControl & 0b01) != 0;
|
|
1226
|
+
const hasAdaptation = (adaptationFieldControl & 0b10) != 0;
|
|
1227
|
+
const isDiscontinuity =
|
|
1228
|
+
hasAdaptation && data[4] != 0 && (data[5] & 0x80) != 0;
|
|
1229
|
+
|
|
1230
|
+
if (isDiscontinuity) {
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
if (lastContinuityCounter < 0) {
|
|
1234
|
+
return;
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
const expectedContinuityCounter = hasPayload
|
|
1238
|
+
? (lastContinuityCounter + 1) & 0x0f
|
|
1239
|
+
: lastContinuityCounter;
|
|
1240
|
+
if (continuityCounter !== expectedContinuityCounter) {
|
|
1241
|
+
this.logger.warn(
|
|
1242
|
+
`MPEG-TS Continuity Counter check failed for PID='${pid}', CC=${continuityCounter}, Expected-CC=${expectedContinuityCounter} Last-CC=${lastContinuityCounter}`,
|
|
1243
|
+
);
|
|
1244
|
+
this.integrityState = 'cc-failed';
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
if ((data[1] & 0x80) !== 0) {
|
|
1249
|
+
this.logger.warn(`MPEG-TS Packet had TEI flag set for PID='${pid}'`);
|
|
1250
|
+
this.integrityState = 'tei-bit';
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
export default TSDemuxer;
|