@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,401 @@
|
|
|
1
|
+
import BaseVideoParser from './base-video-parser';
|
|
2
|
+
import ExpGolomb from './exp-golomb';
|
|
3
|
+
import { parseSEIMessageFromNALu } from '../../utils/mp4-tools';
|
|
4
|
+
import type {
|
|
5
|
+
DemuxedUserdataTrack,
|
|
6
|
+
DemuxedVideoTrack,
|
|
7
|
+
} from '../../types/demuxer';
|
|
8
|
+
import type { PES } from '../tsdemuxer';
|
|
9
|
+
|
|
10
|
+
class AvcVideoParser extends BaseVideoParser {
|
|
11
|
+
public parsePES(
|
|
12
|
+
track: DemuxedVideoTrack,
|
|
13
|
+
textTrack: DemuxedUserdataTrack,
|
|
14
|
+
pes: PES,
|
|
15
|
+
endOfSegment: boolean,
|
|
16
|
+
) {
|
|
17
|
+
const units = this.parseNALu(track, pes.data, endOfSegment);
|
|
18
|
+
let VideoSample = this.VideoSample;
|
|
19
|
+
let push: boolean;
|
|
20
|
+
let spsfound = false;
|
|
21
|
+
// free pes.data to save up some memory
|
|
22
|
+
(pes as any).data = null;
|
|
23
|
+
|
|
24
|
+
// if new NAL units found and last sample still there, let's push ...
|
|
25
|
+
// this helps parsing streams with missing AUD (only do this if AUD never found)
|
|
26
|
+
if (VideoSample && units.length && !track.audFound) {
|
|
27
|
+
this.pushAccessUnit(VideoSample, track);
|
|
28
|
+
VideoSample = this.VideoSample = this.createVideoSample(
|
|
29
|
+
false,
|
|
30
|
+
pes.pts,
|
|
31
|
+
pes.dts,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
units.forEach((unit) => {
|
|
36
|
+
switch (unit.type) {
|
|
37
|
+
// NDR
|
|
38
|
+
case 1: {
|
|
39
|
+
let iskey = false;
|
|
40
|
+
push = true;
|
|
41
|
+
const data = unit.data;
|
|
42
|
+
// only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
|
|
43
|
+
if (spsfound && data.length > 4) {
|
|
44
|
+
// retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
|
|
45
|
+
const sliceType = this.readSliceType(data);
|
|
46
|
+
// 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
|
|
47
|
+
// SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
|
|
48
|
+
// An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
|
|
49
|
+
// I slice: A slice that is not an SI slice that is decoded using intra prediction only.
|
|
50
|
+
// if (sliceType === 2 || sliceType === 7) {
|
|
51
|
+
if (
|
|
52
|
+
sliceType === 2 ||
|
|
53
|
+
sliceType === 4 ||
|
|
54
|
+
sliceType === 7 ||
|
|
55
|
+
sliceType === 9
|
|
56
|
+
) {
|
|
57
|
+
iskey = true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (iskey) {
|
|
62
|
+
// if we have non-keyframe data already, that cannot belong to the same frame as a keyframe, so force a push
|
|
63
|
+
if (VideoSample?.frame && !VideoSample.key) {
|
|
64
|
+
this.pushAccessUnit(VideoSample, track);
|
|
65
|
+
VideoSample = this.VideoSample = null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!VideoSample) {
|
|
70
|
+
VideoSample = this.VideoSample = this.createVideoSample(
|
|
71
|
+
true,
|
|
72
|
+
pes.pts,
|
|
73
|
+
pes.dts,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
VideoSample.frame = true;
|
|
77
|
+
VideoSample.key = iskey;
|
|
78
|
+
|
|
79
|
+
break;
|
|
80
|
+
// IDR
|
|
81
|
+
}
|
|
82
|
+
case 5:
|
|
83
|
+
push = true;
|
|
84
|
+
// handle PES not starting with AUD
|
|
85
|
+
// if we have frame data already, that cannot belong to the same frame, so force a push
|
|
86
|
+
if (VideoSample?.frame && !VideoSample.key) {
|
|
87
|
+
this.pushAccessUnit(VideoSample, track);
|
|
88
|
+
VideoSample = this.VideoSample = null;
|
|
89
|
+
}
|
|
90
|
+
if (!VideoSample) {
|
|
91
|
+
VideoSample = this.VideoSample = this.createVideoSample(
|
|
92
|
+
true,
|
|
93
|
+
pes.pts,
|
|
94
|
+
pes.dts,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
VideoSample.key = true;
|
|
99
|
+
VideoSample.frame = true;
|
|
100
|
+
break;
|
|
101
|
+
// SEI
|
|
102
|
+
case 6: {
|
|
103
|
+
push = true;
|
|
104
|
+
parseSEIMessageFromNALu(
|
|
105
|
+
unit.data,
|
|
106
|
+
1,
|
|
107
|
+
pes.pts as number,
|
|
108
|
+
textTrack.samples,
|
|
109
|
+
);
|
|
110
|
+
break;
|
|
111
|
+
// SPS
|
|
112
|
+
}
|
|
113
|
+
case 7: {
|
|
114
|
+
push = true;
|
|
115
|
+
spsfound = true;
|
|
116
|
+
const sps = unit.data;
|
|
117
|
+
const config = this.readSPS(sps);
|
|
118
|
+
if (
|
|
119
|
+
!track.sps ||
|
|
120
|
+
track.width !== config.width ||
|
|
121
|
+
track.height !== config.height ||
|
|
122
|
+
track.pixelRatio?.[0] !== config.pixelRatio[0] ||
|
|
123
|
+
track.pixelRatio?.[1] !== config.pixelRatio[1]
|
|
124
|
+
) {
|
|
125
|
+
track.width = config.width;
|
|
126
|
+
track.height = config.height;
|
|
127
|
+
track.pixelRatio = config.pixelRatio;
|
|
128
|
+
track.sps = [sps];
|
|
129
|
+
const codecarray = sps.subarray(1, 4);
|
|
130
|
+
let codecstring = 'avc1.';
|
|
131
|
+
for (let i = 0; i < 3; i++) {
|
|
132
|
+
let h = codecarray[i].toString(16);
|
|
133
|
+
if (h.length < 2) {
|
|
134
|
+
h = '0' + h;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
codecstring += h;
|
|
138
|
+
}
|
|
139
|
+
track.codec = codecstring;
|
|
140
|
+
}
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
// PPS
|
|
144
|
+
case 8:
|
|
145
|
+
push = true;
|
|
146
|
+
|
|
147
|
+
track.pps = [unit.data];
|
|
148
|
+
|
|
149
|
+
break;
|
|
150
|
+
// AUD
|
|
151
|
+
case 9:
|
|
152
|
+
push = true;
|
|
153
|
+
track.audFound = true;
|
|
154
|
+
if (VideoSample?.frame) {
|
|
155
|
+
this.pushAccessUnit(VideoSample, track);
|
|
156
|
+
VideoSample = null;
|
|
157
|
+
}
|
|
158
|
+
if (!VideoSample) {
|
|
159
|
+
VideoSample = this.VideoSample = this.createVideoSample(
|
|
160
|
+
false,
|
|
161
|
+
pes.pts,
|
|
162
|
+
pes.dts,
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
break;
|
|
166
|
+
// Filler Data
|
|
167
|
+
case 12:
|
|
168
|
+
push = true;
|
|
169
|
+
break;
|
|
170
|
+
default:
|
|
171
|
+
push = false;
|
|
172
|
+
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
if (VideoSample && push) {
|
|
176
|
+
const units = VideoSample.units;
|
|
177
|
+
units.push(unit);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
// if last PES packet, push samples
|
|
181
|
+
if (endOfSegment && VideoSample) {
|
|
182
|
+
this.pushAccessUnit(VideoSample, track);
|
|
183
|
+
this.VideoSample = null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
protected getNALuType(data: Uint8Array, offset: number): number {
|
|
188
|
+
return data[offset] & 0x1f;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
readSliceType(data: Uint8Array) {
|
|
192
|
+
const eg = new ExpGolomb(data);
|
|
193
|
+
// skip NALu type
|
|
194
|
+
eg.readUByte();
|
|
195
|
+
// discard first_mb_in_slice
|
|
196
|
+
eg.readUEG();
|
|
197
|
+
// return slice_type
|
|
198
|
+
return eg.readUEG();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* The scaling list is optionally transmitted as part of a sequence parameter
|
|
203
|
+
* set and is not relevant to transmuxing.
|
|
204
|
+
* @param count the number of entries in this scaling list
|
|
205
|
+
* @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
|
|
206
|
+
*/
|
|
207
|
+
skipScalingList(count: number, reader: ExpGolomb): void {
|
|
208
|
+
let lastScale = 8;
|
|
209
|
+
let nextScale = 8;
|
|
210
|
+
let deltaScale;
|
|
211
|
+
for (let j = 0; j < count; j++) {
|
|
212
|
+
if (nextScale !== 0) {
|
|
213
|
+
deltaScale = reader.readEG();
|
|
214
|
+
nextScale = (lastScale + deltaScale + 256) % 256;
|
|
215
|
+
}
|
|
216
|
+
lastScale = nextScale === 0 ? lastScale : nextScale;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Read a sequence parameter set and return some interesting video
|
|
222
|
+
* properties. A sequence parameter set is the H264 metadata that
|
|
223
|
+
* describes the properties of upcoming video frames.
|
|
224
|
+
* @returns an object with configuration parsed from the
|
|
225
|
+
* sequence parameter set, including the dimensions of the
|
|
226
|
+
* associated video frames.
|
|
227
|
+
*/
|
|
228
|
+
readSPS(sps: Uint8Array): {
|
|
229
|
+
width: number;
|
|
230
|
+
height: number;
|
|
231
|
+
pixelRatio: [number, number];
|
|
232
|
+
} {
|
|
233
|
+
const eg = new ExpGolomb(sps);
|
|
234
|
+
let frameCropLeftOffset = 0;
|
|
235
|
+
let frameCropRightOffset = 0;
|
|
236
|
+
let frameCropTopOffset = 0;
|
|
237
|
+
let frameCropBottomOffset = 0;
|
|
238
|
+
let numRefFramesInPicOrderCntCycle;
|
|
239
|
+
let scalingListCount;
|
|
240
|
+
let i;
|
|
241
|
+
const readUByte = eg.readUByte.bind(eg);
|
|
242
|
+
const readBits = eg.readBits.bind(eg);
|
|
243
|
+
const readUEG = eg.readUEG.bind(eg);
|
|
244
|
+
const readBoolean = eg.readBoolean.bind(eg);
|
|
245
|
+
const skipBits = eg.skipBits.bind(eg);
|
|
246
|
+
const skipEG = eg.skipEG.bind(eg);
|
|
247
|
+
const skipUEG = eg.skipUEG.bind(eg);
|
|
248
|
+
const skipScalingList = this.skipScalingList.bind(this);
|
|
249
|
+
|
|
250
|
+
readUByte();
|
|
251
|
+
const profileIdc = readUByte(); // profile_idc
|
|
252
|
+
readBits(5); // profileCompat constraint_set[0-4]_flag, u(5)
|
|
253
|
+
skipBits(3); // reserved_zero_3bits u(3),
|
|
254
|
+
readUByte(); // level_idc u(8)
|
|
255
|
+
skipUEG(); // seq_parameter_set_id
|
|
256
|
+
// some profiles have more optional data we don't need
|
|
257
|
+
if (
|
|
258
|
+
profileIdc === 100 ||
|
|
259
|
+
profileIdc === 110 ||
|
|
260
|
+
profileIdc === 122 ||
|
|
261
|
+
profileIdc === 244 ||
|
|
262
|
+
profileIdc === 44 ||
|
|
263
|
+
profileIdc === 83 ||
|
|
264
|
+
profileIdc === 86 ||
|
|
265
|
+
profileIdc === 118 ||
|
|
266
|
+
profileIdc === 128
|
|
267
|
+
) {
|
|
268
|
+
const chromaFormatIdc = readUEG();
|
|
269
|
+
if (chromaFormatIdc === 3) {
|
|
270
|
+
skipBits(1);
|
|
271
|
+
} // separate_colour_plane_flag
|
|
272
|
+
|
|
273
|
+
skipUEG(); // bit_depth_luma_minus8
|
|
274
|
+
skipUEG(); // bit_depth_chroma_minus8
|
|
275
|
+
skipBits(1); // qpprime_y_zero_transform_bypass_flag
|
|
276
|
+
if (readBoolean()) {
|
|
277
|
+
// seq_scaling_matrix_present_flag
|
|
278
|
+
scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
|
|
279
|
+
for (i = 0; i < scalingListCount; i++) {
|
|
280
|
+
if (readBoolean()) {
|
|
281
|
+
// seq_scaling_list_present_flag[ i ]
|
|
282
|
+
if (i < 6) {
|
|
283
|
+
skipScalingList(16, eg);
|
|
284
|
+
} else {
|
|
285
|
+
skipScalingList(64, eg);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
skipUEG(); // log2_max_frame_num_minus4
|
|
292
|
+
const picOrderCntType = readUEG();
|
|
293
|
+
if (picOrderCntType === 0) {
|
|
294
|
+
readUEG(); // log2_max_pic_order_cnt_lsb_minus4
|
|
295
|
+
} else if (picOrderCntType === 1) {
|
|
296
|
+
skipBits(1); // delta_pic_order_always_zero_flag
|
|
297
|
+
skipEG(); // offset_for_non_ref_pic
|
|
298
|
+
skipEG(); // offset_for_top_to_bottom_field
|
|
299
|
+
numRefFramesInPicOrderCntCycle = readUEG();
|
|
300
|
+
for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
|
|
301
|
+
skipEG();
|
|
302
|
+
} // offset_for_ref_frame[ i ]
|
|
303
|
+
}
|
|
304
|
+
skipUEG(); // max_num_ref_frames
|
|
305
|
+
skipBits(1); // gaps_in_frame_num_value_allowed_flag
|
|
306
|
+
const picWidthInMbsMinus1 = readUEG();
|
|
307
|
+
const picHeightInMapUnitsMinus1 = readUEG();
|
|
308
|
+
const frameMbsOnlyFlag = readBits(1);
|
|
309
|
+
if (frameMbsOnlyFlag === 0) {
|
|
310
|
+
skipBits(1);
|
|
311
|
+
} // mb_adaptive_frame_field_flag
|
|
312
|
+
|
|
313
|
+
skipBits(1); // direct_8x8_inference_flag
|
|
314
|
+
if (readBoolean()) {
|
|
315
|
+
// frame_cropping_flag
|
|
316
|
+
frameCropLeftOffset = readUEG();
|
|
317
|
+
frameCropRightOffset = readUEG();
|
|
318
|
+
frameCropTopOffset = readUEG();
|
|
319
|
+
frameCropBottomOffset = readUEG();
|
|
320
|
+
}
|
|
321
|
+
let pixelRatio: [number, number] = [1, 1];
|
|
322
|
+
if (readBoolean()) {
|
|
323
|
+
// vui_parameters_present_flag
|
|
324
|
+
if (readBoolean()) {
|
|
325
|
+
// aspect_ratio_info_present_flag
|
|
326
|
+
const aspectRatioIdc = readUByte();
|
|
327
|
+
switch (aspectRatioIdc) {
|
|
328
|
+
case 1:
|
|
329
|
+
pixelRatio = [1, 1];
|
|
330
|
+
break;
|
|
331
|
+
case 2:
|
|
332
|
+
pixelRatio = [12, 11];
|
|
333
|
+
break;
|
|
334
|
+
case 3:
|
|
335
|
+
pixelRatio = [10, 11];
|
|
336
|
+
break;
|
|
337
|
+
case 4:
|
|
338
|
+
pixelRatio = [16, 11];
|
|
339
|
+
break;
|
|
340
|
+
case 5:
|
|
341
|
+
pixelRatio = [40, 33];
|
|
342
|
+
break;
|
|
343
|
+
case 6:
|
|
344
|
+
pixelRatio = [24, 11];
|
|
345
|
+
break;
|
|
346
|
+
case 7:
|
|
347
|
+
pixelRatio = [20, 11];
|
|
348
|
+
break;
|
|
349
|
+
case 8:
|
|
350
|
+
pixelRatio = [32, 11];
|
|
351
|
+
break;
|
|
352
|
+
case 9:
|
|
353
|
+
pixelRatio = [80, 33];
|
|
354
|
+
break;
|
|
355
|
+
case 10:
|
|
356
|
+
pixelRatio = [18, 11];
|
|
357
|
+
break;
|
|
358
|
+
case 11:
|
|
359
|
+
pixelRatio = [15, 11];
|
|
360
|
+
break;
|
|
361
|
+
case 12:
|
|
362
|
+
pixelRatio = [64, 33];
|
|
363
|
+
break;
|
|
364
|
+
case 13:
|
|
365
|
+
pixelRatio = [160, 99];
|
|
366
|
+
break;
|
|
367
|
+
case 14:
|
|
368
|
+
pixelRatio = [4, 3];
|
|
369
|
+
break;
|
|
370
|
+
case 15:
|
|
371
|
+
pixelRatio = [3, 2];
|
|
372
|
+
break;
|
|
373
|
+
case 16:
|
|
374
|
+
pixelRatio = [2, 1];
|
|
375
|
+
break;
|
|
376
|
+
case 255: {
|
|
377
|
+
pixelRatio = [
|
|
378
|
+
(readUByte() << 8) | readUByte(),
|
|
379
|
+
(readUByte() << 8) | readUByte(),
|
|
380
|
+
];
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return {
|
|
387
|
+
width: Math.ceil(
|
|
388
|
+
(picWidthInMbsMinus1 + 1) * 16 -
|
|
389
|
+
frameCropLeftOffset * 2 -
|
|
390
|
+
frameCropRightOffset * 2,
|
|
391
|
+
),
|
|
392
|
+
height:
|
|
393
|
+
(2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 -
|
|
394
|
+
(frameMbsOnlyFlag ? 2 : 4) *
|
|
395
|
+
(frameCropTopOffset + frameCropBottomOffset),
|
|
396
|
+
pixelRatio: pixelRatio,
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export default AvcVideoParser;
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { appendUint8Array } from '../../utils/mp4-tools';
|
|
2
|
+
import type {
|
|
3
|
+
DemuxedUserdataTrack,
|
|
4
|
+
DemuxedVideoTrack,
|
|
5
|
+
VideoSample,
|
|
6
|
+
VideoSampleUnit,
|
|
7
|
+
} from '../../types/demuxer';
|
|
8
|
+
import type { ParsedVideoSample } from '../tsdemuxer';
|
|
9
|
+
import type { PES } from '../tsdemuxer';
|
|
10
|
+
|
|
11
|
+
abstract class BaseVideoParser {
|
|
12
|
+
protected VideoSample: ParsedVideoSample | null = null;
|
|
13
|
+
|
|
14
|
+
protected createVideoSample(
|
|
15
|
+
key: boolean,
|
|
16
|
+
pts: number | undefined,
|
|
17
|
+
dts: number | undefined,
|
|
18
|
+
): ParsedVideoSample {
|
|
19
|
+
return {
|
|
20
|
+
key,
|
|
21
|
+
frame: false,
|
|
22
|
+
pts,
|
|
23
|
+
dts,
|
|
24
|
+
units: [],
|
|
25
|
+
length: 0,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
protected getLastNalUnit(
|
|
30
|
+
samples: VideoSample[],
|
|
31
|
+
): VideoSampleUnit | undefined {
|
|
32
|
+
let VideoSample = this.VideoSample;
|
|
33
|
+
let lastUnit: VideoSampleUnit | undefined;
|
|
34
|
+
// try to fallback to previous sample if current one is empty
|
|
35
|
+
if (!VideoSample || VideoSample.units.length === 0) {
|
|
36
|
+
VideoSample = samples[samples.length - 1];
|
|
37
|
+
}
|
|
38
|
+
if (VideoSample?.units) {
|
|
39
|
+
const units = VideoSample.units;
|
|
40
|
+
lastUnit = units[units.length - 1];
|
|
41
|
+
}
|
|
42
|
+
return lastUnit;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
protected pushAccessUnit(
|
|
46
|
+
VideoSample: ParsedVideoSample,
|
|
47
|
+
videoTrack: DemuxedVideoTrack,
|
|
48
|
+
) {
|
|
49
|
+
if (VideoSample.units.length && VideoSample.frame) {
|
|
50
|
+
// if sample does not have PTS/DTS, patch with last sample PTS/DTS
|
|
51
|
+
if (VideoSample.pts === undefined) {
|
|
52
|
+
const samples = videoTrack.samples;
|
|
53
|
+
const nbSamples = samples.length;
|
|
54
|
+
if (nbSamples) {
|
|
55
|
+
const lastSample = samples[nbSamples - 1];
|
|
56
|
+
VideoSample.pts = lastSample.pts;
|
|
57
|
+
VideoSample.dts = lastSample.dts;
|
|
58
|
+
} else {
|
|
59
|
+
// dropping samples, no timestamp found
|
|
60
|
+
videoTrack.dropped++;
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
videoTrack.samples.push(VideoSample as VideoSample);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
abstract parsePES(
|
|
69
|
+
track: DemuxedVideoTrack,
|
|
70
|
+
textTrack: DemuxedUserdataTrack,
|
|
71
|
+
pes: PES,
|
|
72
|
+
last: boolean,
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
protected abstract getNALuType(data: Uint8Array, offset: number): number;
|
|
76
|
+
|
|
77
|
+
protected parseNALu(
|
|
78
|
+
track: DemuxedVideoTrack,
|
|
79
|
+
array: Uint8Array,
|
|
80
|
+
endOfSegment: boolean,
|
|
81
|
+
): Array<{
|
|
82
|
+
data: Uint8Array;
|
|
83
|
+
type: number;
|
|
84
|
+
state?: number;
|
|
85
|
+
}> {
|
|
86
|
+
const len = array.byteLength;
|
|
87
|
+
let state = track.naluState || 0;
|
|
88
|
+
const lastState = state;
|
|
89
|
+
const units: VideoSampleUnit[] = [];
|
|
90
|
+
let i = 0;
|
|
91
|
+
let value: number;
|
|
92
|
+
let overflow: number;
|
|
93
|
+
let unitType: number;
|
|
94
|
+
let lastUnitStart = -1;
|
|
95
|
+
let lastUnitType: number = 0;
|
|
96
|
+
// logger.log('PES:' + Hex.hexDump(array));
|
|
97
|
+
|
|
98
|
+
if (state === -1) {
|
|
99
|
+
// special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
|
|
100
|
+
lastUnitStart = 0;
|
|
101
|
+
// NALu type is value read from offset 0
|
|
102
|
+
lastUnitType = this.getNALuType(array, 0);
|
|
103
|
+
state = 0;
|
|
104
|
+
i = 1;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
while (i < len) {
|
|
108
|
+
value = array[i++];
|
|
109
|
+
// optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
|
|
110
|
+
if (!state) {
|
|
111
|
+
state = value ? 0 : 1;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (state === 1) {
|
|
115
|
+
state = value ? 0 : 2;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
// here we have state either equal to 2 or 3
|
|
119
|
+
if (!value) {
|
|
120
|
+
state = 3;
|
|
121
|
+
} else if (value === 1) {
|
|
122
|
+
overflow = i - state - 1;
|
|
123
|
+
if (lastUnitStart >= 0) {
|
|
124
|
+
const unit: VideoSampleUnit = {
|
|
125
|
+
data: array.subarray(lastUnitStart, overflow),
|
|
126
|
+
type: lastUnitType,
|
|
127
|
+
};
|
|
128
|
+
// logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
|
|
129
|
+
units.push(unit);
|
|
130
|
+
} else {
|
|
131
|
+
// lastUnitStart is undefined => this is the first start code found in this PES packet
|
|
132
|
+
// first check if start code delimiter is overlapping between 2 PES packets,
|
|
133
|
+
// ie it started in last packet (lastState not zero)
|
|
134
|
+
// and ended at the beginning of this PES packet (i <= 4 - lastState)
|
|
135
|
+
const lastUnit = this.getLastNalUnit(track.samples);
|
|
136
|
+
if (lastUnit) {
|
|
137
|
+
if (lastState && i <= 4 - lastState) {
|
|
138
|
+
// start delimiter overlapping between PES packets
|
|
139
|
+
// strip start delimiter bytes from the end of last NAL unit
|
|
140
|
+
// check if lastUnit had a state different from zero
|
|
141
|
+
if (lastUnit.state) {
|
|
142
|
+
// strip last bytes
|
|
143
|
+
lastUnit.data = lastUnit.data.subarray(
|
|
144
|
+
0,
|
|
145
|
+
lastUnit.data.byteLength - lastState,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
|
|
150
|
+
|
|
151
|
+
if (overflow > 0) {
|
|
152
|
+
// logger.log('first NALU found with overflow:' + overflow);
|
|
153
|
+
lastUnit.data = appendUint8Array(
|
|
154
|
+
lastUnit.data,
|
|
155
|
+
array.subarray(0, overflow),
|
|
156
|
+
);
|
|
157
|
+
lastUnit.state = 0;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// check if we can read unit type
|
|
162
|
+
if (i < len) {
|
|
163
|
+
unitType = this.getNALuType(array, i);
|
|
164
|
+
// logger.log('find NALU @ offset:' + i + ',type:' + unitType);
|
|
165
|
+
lastUnitStart = i;
|
|
166
|
+
lastUnitType = unitType;
|
|
167
|
+
state = 0;
|
|
168
|
+
} else {
|
|
169
|
+
// not enough byte to read unit type. let's read it on next PES parsing
|
|
170
|
+
state = -1;
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
state = 0;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (lastUnitStart >= 0 && state >= 0) {
|
|
177
|
+
const unit: VideoSampleUnit = {
|
|
178
|
+
data: array.subarray(lastUnitStart, len),
|
|
179
|
+
type: lastUnitType,
|
|
180
|
+
state: state,
|
|
181
|
+
};
|
|
182
|
+
units.push(unit);
|
|
183
|
+
// logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
|
|
184
|
+
}
|
|
185
|
+
// no NALu found
|
|
186
|
+
if (units.length === 0) {
|
|
187
|
+
// append pes.data to previous NAL unit
|
|
188
|
+
const lastUnit = this.getLastNalUnit(track.samples);
|
|
189
|
+
if (lastUnit) {
|
|
190
|
+
lastUnit.data = appendUint8Array(lastUnit.data, array);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
track.naluState = state;
|
|
194
|
+
return units;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export default BaseVideoParser;
|