hls.js 1.5.13 → 1.5.14-0.canary.10417
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/README.md +4 -3
- package/dist/hls-demo.js +41 -38
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +4211 -2666
- package/dist/hls.js.d.ts +179 -110
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +2841 -1921
- package/dist/hls.light.js.map +1 -1
- package/dist/hls.light.min.js +1 -1
- package/dist/hls.light.min.js.map +1 -1
- package/dist/hls.light.mjs +2569 -1639
- package/dist/hls.light.mjs.map +1 -1
- package/dist/hls.min.js +1 -1
- package/dist/hls.min.js.map +1 -1
- package/dist/hls.mjs +3572 -2017
- package/dist/hls.mjs.map +1 -1
- package/dist/hls.worker.js +1 -1
- package/dist/hls.worker.js.map +1 -1
- package/package.json +38 -38
- package/src/config.ts +5 -2
- package/src/controller/abr-controller.ts +39 -25
- package/src/controller/audio-stream-controller.ts +156 -136
- package/src/controller/audio-track-controller.ts +1 -1
- package/src/controller/base-playlist-controller.ts +27 -10
- package/src/controller/base-stream-controller.ts +234 -89
- package/src/controller/buffer-controller.ts +250 -97
- package/src/controller/buffer-operation-queue.ts +16 -19
- package/src/controller/cap-level-controller.ts +3 -2
- package/src/controller/cmcd-controller.ts +51 -14
- package/src/controller/content-steering-controller.ts +29 -15
- package/src/controller/eme-controller.ts +10 -23
- package/src/controller/error-controller.ts +28 -22
- package/src/controller/fps-controller.ts +8 -3
- package/src/controller/fragment-finders.ts +44 -16
- package/src/controller/fragment-tracker.ts +58 -25
- package/src/controller/gap-controller.ts +43 -16
- package/src/controller/id3-track-controller.ts +45 -35
- package/src/controller/latency-controller.ts +18 -13
- package/src/controller/level-controller.ts +37 -19
- package/src/controller/stream-controller.ts +100 -83
- package/src/controller/subtitle-stream-controller.ts +35 -47
- package/src/controller/subtitle-track-controller.ts +5 -3
- package/src/controller/timeline-controller.ts +20 -22
- package/src/crypt/aes-crypto.ts +21 -2
- package/src/crypt/decrypter-aes-mode.ts +4 -0
- package/src/crypt/decrypter.ts +32 -16
- package/src/crypt/fast-aes-key.ts +28 -5
- package/src/demux/audio/aacdemuxer.ts +2 -2
- package/src/demux/audio/ac3-demuxer.ts +4 -3
- package/src/demux/audio/adts.ts +9 -4
- package/src/demux/audio/base-audio-demuxer.ts +16 -14
- package/src/demux/audio/mp3demuxer.ts +4 -3
- package/src/demux/audio/mpegaudio.ts +1 -1
- package/src/demux/mp4demuxer.ts +7 -7
- package/src/demux/sample-aes.ts +2 -0
- package/src/demux/transmuxer-interface.ts +8 -16
- package/src/demux/transmuxer-worker.ts +4 -4
- package/src/demux/transmuxer.ts +16 -3
- package/src/demux/tsdemuxer.ts +75 -38
- package/src/demux/video/avc-video-parser.ts +210 -121
- package/src/demux/video/base-video-parser.ts +135 -2
- package/src/demux/video/exp-golomb.ts +0 -208
- package/src/demux/video/hevc-video-parser.ts +749 -0
- package/src/events.ts +8 -1
- package/src/exports-named.ts +1 -1
- package/src/hls.ts +84 -47
- package/src/loader/date-range.ts +71 -5
- package/src/loader/fragment-loader.ts +23 -21
- package/src/loader/fragment.ts +8 -4
- package/src/loader/key-loader.ts +3 -1
- package/src/loader/level-details.ts +6 -6
- package/src/loader/level-key.ts +10 -9
- package/src/loader/m3u8-parser.ts +138 -144
- package/src/loader/playlist-loader.ts +5 -7
- package/src/remux/mp4-generator.ts +196 -1
- package/src/remux/mp4-remuxer.ts +32 -62
- package/src/remux/passthrough-remuxer.ts +1 -1
- package/src/task-loop.ts +5 -2
- package/src/types/component-api.ts +3 -1
- package/src/types/demuxer.ts +3 -0
- package/src/types/events.ts +19 -6
- package/src/types/fragment-tracker.ts +2 -2
- package/src/types/media-playlist.ts +9 -1
- package/src/types/remuxer.ts +1 -1
- package/src/utils/attr-list.ts +96 -9
- package/src/utils/buffer-helper.ts +12 -31
- package/src/utils/cea-608-parser.ts +1 -3
- package/src/utils/codecs.ts +34 -5
- package/src/utils/encryption-methods-util.ts +21 -0
- package/src/utils/fetch-loader.ts +1 -1
- package/src/utils/hash.ts +10 -0
- package/src/utils/hdr.ts +4 -7
- package/src/utils/imsc1-ttml-parser.ts +1 -1
- package/src/utils/keysystem-util.ts +1 -6
- package/src/utils/level-helper.ts +71 -44
- package/src/utils/logger.ts +58 -23
- package/src/utils/mp4-tools.ts +5 -3
- package/src/utils/rendition-helper.ts +100 -74
- package/src/utils/utf8-utils.ts +18 -0
- package/src/utils/variable-substitution.ts +0 -19
- package/src/utils/webvtt-parser.ts +2 -12
- package/src/demux/id3.ts +0 -411
- package/src/types/general.ts +0 -6
@@ -28,6 +28,8 @@ class MP4 {
|
|
28
28
|
MP4.types = {
|
29
29
|
avc1: [], // codingname
|
30
30
|
avcC: [],
|
31
|
+
hvc1: [],
|
32
|
+
hvcC: [],
|
31
33
|
btrt: [],
|
32
34
|
dinf: [],
|
33
35
|
dref: [],
|
@@ -839,8 +841,10 @@ class MP4 {
|
|
839
841
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.ac3(track));
|
840
842
|
}
|
841
843
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track));
|
842
|
-
} else {
|
844
|
+
} else if (track.segmentCodec === 'avc') {
|
843
845
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
|
846
|
+
} else {
|
847
|
+
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.hvc1(track));
|
844
848
|
}
|
845
849
|
}
|
846
850
|
|
@@ -1123,6 +1127,197 @@ class MP4 {
|
|
1123
1127
|
const result = appendUint8Array(MP4.FTYP, movie);
|
1124
1128
|
return result;
|
1125
1129
|
}
|
1130
|
+
|
1131
|
+
static hvc1(track) {
|
1132
|
+
const ps = track.params;
|
1133
|
+
const units = [track.vps, track.sps, track.pps];
|
1134
|
+
const NALuLengthSize = 4;
|
1135
|
+
const config = new Uint8Array([
|
1136
|
+
0x01,
|
1137
|
+
(ps.general_profile_space << 6) |
|
1138
|
+
(ps.general_tier_flag ? 32 : 0) |
|
1139
|
+
ps.general_profile_idc,
|
1140
|
+
ps.general_profile_compatibility_flags[0],
|
1141
|
+
ps.general_profile_compatibility_flags[1],
|
1142
|
+
ps.general_profile_compatibility_flags[2],
|
1143
|
+
ps.general_profile_compatibility_flags[3],
|
1144
|
+
ps.general_constraint_indicator_flags[0],
|
1145
|
+
ps.general_constraint_indicator_flags[1],
|
1146
|
+
ps.general_constraint_indicator_flags[2],
|
1147
|
+
ps.general_constraint_indicator_flags[3],
|
1148
|
+
ps.general_constraint_indicator_flags[4],
|
1149
|
+
ps.general_constraint_indicator_flags[5],
|
1150
|
+
ps.general_level_idc,
|
1151
|
+
240 | (ps.min_spatial_segmentation_idc >> 8),
|
1152
|
+
255 & ps.min_spatial_segmentation_idc,
|
1153
|
+
252 | ps.parallelismType,
|
1154
|
+
252 | ps.chroma_format_idc,
|
1155
|
+
248 | ps.bit_depth_luma_minus8,
|
1156
|
+
248 | ps.bit_depth_chroma_minus8,
|
1157
|
+
0x00,
|
1158
|
+
parseInt(ps.frame_rate.fps),
|
1159
|
+
(NALuLengthSize - 1) |
|
1160
|
+
(ps.temporal_id_nested << 2) |
|
1161
|
+
(ps.num_temporal_layers << 3) |
|
1162
|
+
(ps.frame_rate.fixed ? 64 : 0),
|
1163
|
+
units.length,
|
1164
|
+
]);
|
1165
|
+
|
1166
|
+
// compute hvcC size in bytes
|
1167
|
+
let length = config.length;
|
1168
|
+
for (let i = 0; i < units.length; i += 1) {
|
1169
|
+
length += 3;
|
1170
|
+
for (let j = 0; j < units[i].length; j += 1) {
|
1171
|
+
length += 2 + units[i][j].length;
|
1172
|
+
}
|
1173
|
+
}
|
1174
|
+
|
1175
|
+
const hvcC = new Uint8Array(length);
|
1176
|
+
hvcC.set(config, 0);
|
1177
|
+
length = config.length;
|
1178
|
+
// append parameter set units: one vps, one or more sps and pps
|
1179
|
+
const iMax = units.length - 1;
|
1180
|
+
for (let i = 0; i < units.length; i += 1) {
|
1181
|
+
hvcC.set(
|
1182
|
+
new Uint8Array([
|
1183
|
+
(32 + i) | (i === iMax ? 128 : 0),
|
1184
|
+
0x00,
|
1185
|
+
units[i].length,
|
1186
|
+
]),
|
1187
|
+
length,
|
1188
|
+
);
|
1189
|
+
length += 3;
|
1190
|
+
for (let j = 0; j < units[i].length; j += 1) {
|
1191
|
+
hvcC.set(
|
1192
|
+
new Uint8Array([units[i][j].length >> 8, units[i][j].length & 255]),
|
1193
|
+
length,
|
1194
|
+
);
|
1195
|
+
length += 2;
|
1196
|
+
hvcC.set(units[i][j], length);
|
1197
|
+
length += units[i][j].length;
|
1198
|
+
}
|
1199
|
+
}
|
1200
|
+
const hvcc = MP4.box(MP4.types.hvcC, hvcC);
|
1201
|
+
const width = track.width;
|
1202
|
+
const height = track.height;
|
1203
|
+
const hSpacing = track.pixelRatio[0];
|
1204
|
+
const vSpacing = track.pixelRatio[1];
|
1205
|
+
|
1206
|
+
return MP4.box(
|
1207
|
+
MP4.types.hvc1,
|
1208
|
+
new Uint8Array([
|
1209
|
+
0x00,
|
1210
|
+
0x00,
|
1211
|
+
0x00, // reserved
|
1212
|
+
0x00,
|
1213
|
+
0x00,
|
1214
|
+
0x00, // reserved
|
1215
|
+
0x00,
|
1216
|
+
0x01, // data_reference_index
|
1217
|
+
0x00,
|
1218
|
+
0x00, // pre_defined
|
1219
|
+
0x00,
|
1220
|
+
0x00, // reserved
|
1221
|
+
0x00,
|
1222
|
+
0x00,
|
1223
|
+
0x00,
|
1224
|
+
0x00,
|
1225
|
+
0x00,
|
1226
|
+
0x00,
|
1227
|
+
0x00,
|
1228
|
+
0x00,
|
1229
|
+
0x00,
|
1230
|
+
0x00,
|
1231
|
+
0x00,
|
1232
|
+
0x00, // pre_defined
|
1233
|
+
(width >> 8) & 0xff,
|
1234
|
+
width & 0xff, // width
|
1235
|
+
(height >> 8) & 0xff,
|
1236
|
+
height & 0xff, // height
|
1237
|
+
0x00,
|
1238
|
+
0x48,
|
1239
|
+
0x00,
|
1240
|
+
0x00, // horizresolution
|
1241
|
+
0x00,
|
1242
|
+
0x48,
|
1243
|
+
0x00,
|
1244
|
+
0x00, // vertresolution
|
1245
|
+
0x00,
|
1246
|
+
0x00,
|
1247
|
+
0x00,
|
1248
|
+
0x00, // reserved
|
1249
|
+
0x00,
|
1250
|
+
0x01, // frame_count
|
1251
|
+
0x12,
|
1252
|
+
0x64,
|
1253
|
+
0x61,
|
1254
|
+
0x69,
|
1255
|
+
0x6c, // dailymotion/hls.js
|
1256
|
+
0x79,
|
1257
|
+
0x6d,
|
1258
|
+
0x6f,
|
1259
|
+
0x74,
|
1260
|
+
0x69,
|
1261
|
+
0x6f,
|
1262
|
+
0x6e,
|
1263
|
+
0x2f,
|
1264
|
+
0x68,
|
1265
|
+
0x6c,
|
1266
|
+
0x73,
|
1267
|
+
0x2e,
|
1268
|
+
0x6a,
|
1269
|
+
0x73,
|
1270
|
+
0x00,
|
1271
|
+
0x00,
|
1272
|
+
0x00,
|
1273
|
+
0x00,
|
1274
|
+
0x00,
|
1275
|
+
0x00,
|
1276
|
+
0x00,
|
1277
|
+
0x00,
|
1278
|
+
0x00,
|
1279
|
+
0x00,
|
1280
|
+
0x00,
|
1281
|
+
0x00,
|
1282
|
+
0x00, // compressorname
|
1283
|
+
0x00,
|
1284
|
+
0x18, // depth = 24
|
1285
|
+
0x11,
|
1286
|
+
0x11,
|
1287
|
+
]), // pre_defined = -1
|
1288
|
+
hvcc,
|
1289
|
+
MP4.box(
|
1290
|
+
MP4.types.btrt,
|
1291
|
+
new Uint8Array([
|
1292
|
+
0x00,
|
1293
|
+
0x1c,
|
1294
|
+
0x9c,
|
1295
|
+
0x80, // bufferSizeDB
|
1296
|
+
0x00,
|
1297
|
+
0x2d,
|
1298
|
+
0xc6,
|
1299
|
+
0xc0, // maxBitrate
|
1300
|
+
0x00,
|
1301
|
+
0x2d,
|
1302
|
+
0xc6,
|
1303
|
+
0xc0,
|
1304
|
+
]),
|
1305
|
+
), // avgBitrate
|
1306
|
+
MP4.box(
|
1307
|
+
MP4.types.pasp,
|
1308
|
+
new Uint8Array([
|
1309
|
+
hSpacing >> 24, // hSpacing
|
1310
|
+
(hSpacing >> 16) & 0xff,
|
1311
|
+
(hSpacing >> 8) & 0xff,
|
1312
|
+
hSpacing & 0xff,
|
1313
|
+
vSpacing >> 24, // vSpacing
|
1314
|
+
(vSpacing >> 16) & 0xff,
|
1315
|
+
(vSpacing >> 8) & 0xff,
|
1316
|
+
vSpacing & 0xff,
|
1317
|
+
]),
|
1318
|
+
),
|
1319
|
+
);
|
1320
|
+
}
|
1126
1321
|
}
|
1127
1322
|
|
1128
1323
|
export default MP4;
|
package/src/remux/mp4-remuxer.ts
CHANGED
@@ -4,7 +4,7 @@ import type { HlsEventEmitter } from '../events';
|
|
4
4
|
import { Events } from '../events';
|
5
5
|
import { ErrorTypes, ErrorDetails } from '../errors';
|
6
6
|
import { logger } from '../utils/logger';
|
7
|
-
import {
|
7
|
+
import type {
|
8
8
|
InitSegmentData,
|
9
9
|
Remuxer,
|
10
10
|
RemuxerResult,
|
@@ -29,6 +29,7 @@ import type { TrackSet } from '../types/track';
|
|
29
29
|
import type { SourceBufferName } from '../types/buffer';
|
30
30
|
import type { Fragment } from '../loader/fragment';
|
31
31
|
import type { HlsConfig } from '../config';
|
32
|
+
import type { TypeSupported } from '../utils/codecs';
|
32
33
|
|
33
34
|
const MAX_SILENT_FRAME_DURATION = 10 * 1000; // 10 seconds
|
34
35
|
const AAC_SAMPLES_PER_FRAME = 1024;
|
@@ -41,7 +42,7 @@ let safariWebkitVersion: number | null = null;
|
|
41
42
|
export default class MP4Remuxer implements Remuxer {
|
42
43
|
private observer: HlsEventEmitter;
|
43
44
|
private config: HlsConfig;
|
44
|
-
private typeSupported:
|
45
|
+
private typeSupported: TypeSupported;
|
45
46
|
private ISGenerated: boolean = false;
|
46
47
|
private _initPTS: RationalTimestamp | null = null;
|
47
48
|
private _initDTS: RationalTimestamp | null = null;
|
@@ -159,15 +160,18 @@ export default class MP4Remuxer implements Remuxer {
|
|
159
160
|
if (this.ISGenerated) {
|
160
161
|
const config = this.videoTrackConfig;
|
161
162
|
if (
|
162
|
-
config &&
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
163
|
+
(config &&
|
164
|
+
(videoTrack.width !== config.width ||
|
165
|
+
videoTrack.height !== config.height ||
|
166
|
+
videoTrack.pixelRatio?.[0] !== config.pixelRatio?.[0] ||
|
167
|
+
videoTrack.pixelRatio?.[1] !== config.pixelRatio?.[1])) ||
|
168
|
+
(!config && enoughVideoSamples) ||
|
169
|
+
(this.nextAudioPts === null && enoughAudioSamples)
|
167
170
|
) {
|
168
171
|
this.resetInitSegment();
|
169
172
|
}
|
170
|
-
}
|
173
|
+
}
|
174
|
+
if (!this.ISGenerated) {
|
171
175
|
initSegment = this.generateIS(
|
172
176
|
audioTrack,
|
173
177
|
videoTrack,
|
@@ -515,7 +519,7 @@ export default class MP4Remuxer implements Remuxer {
|
|
515
519
|
if (foundHole || foundOverlap) {
|
516
520
|
if (foundHole) {
|
517
521
|
logger.warn(
|
518
|
-
|
522
|
+
`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(
|
519
523
|
delta,
|
520
524
|
true,
|
521
525
|
)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(
|
@@ -524,7 +528,7 @@ export default class MP4Remuxer implements Remuxer {
|
|
524
528
|
);
|
525
529
|
} else {
|
526
530
|
logger.warn(
|
527
|
-
|
531
|
+
`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(
|
528
532
|
-delta,
|
529
533
|
true,
|
530
534
|
)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(
|
@@ -543,12 +547,27 @@ export default class MP4Remuxer implements Remuxer {
|
|
543
547
|
inputSamples[0].dts = firstDTS;
|
544
548
|
inputSamples[0].pts = firstPTS;
|
545
549
|
} else {
|
550
|
+
let isPTSOrderRetained = true;
|
546
551
|
for (let i = 0; i < inputSamples.length; i++) {
|
547
|
-
if (inputSamples[i].dts > firstPTS) {
|
552
|
+
if (inputSamples[i].dts > firstPTS && isPTSOrderRetained) {
|
548
553
|
break;
|
549
554
|
}
|
555
|
+
|
556
|
+
const prevPTS = inputSamples[i].pts;
|
550
557
|
inputSamples[i].dts -= delta;
|
551
558
|
inputSamples[i].pts -= delta;
|
559
|
+
|
560
|
+
// check to see if this sample's PTS order has changed
|
561
|
+
// relative to the next one
|
562
|
+
if (i < inputSamples.length - 1) {
|
563
|
+
const nextSamplePTS = inputSamples[i + 1].pts;
|
564
|
+
const currentSamplePTS = inputSamples[i].pts;
|
565
|
+
|
566
|
+
const currentOrder = nextSamplePTS <= currentSamplePTS;
|
567
|
+
const prevOrder = nextSamplePTS <= prevPTS;
|
568
|
+
|
569
|
+
isPTSOrderRetained = currentOrder == prevOrder;
|
570
|
+
}
|
552
571
|
}
|
553
572
|
}
|
554
573
|
logger.log(
|
@@ -743,7 +762,7 @@ export default class MP4Remuxer implements Remuxer {
|
|
743
762
|
}
|
744
763
|
}
|
745
764
|
}
|
746
|
-
// next AVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
|
765
|
+
// next AVC/HEVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
|
747
766
|
mp4SampleDuration =
|
748
767
|
stretchedLastFrame || !mp4SampleDuration
|
749
768
|
? averageSampleDuration
|
@@ -927,7 +946,7 @@ export default class MP4Remuxer implements Remuxer {
|
|
927
946
|
for (let j = 0; j < missing; j++) {
|
928
947
|
const newStamp = Math.max(nextPts as number, 0);
|
929
948
|
let fillFrame = AAC.getSilentFrame(
|
930
|
-
track.manifestCodec || track.codec,
|
949
|
+
track.parsedCodec || track.manifestCodec || track.codec,
|
931
950
|
track.channelCount,
|
932
951
|
);
|
933
952
|
if (!fillFrame) {
|
@@ -1050,55 +1069,6 @@ export default class MP4Remuxer implements Remuxer {
|
|
1050
1069
|
this.isAudioContiguous = true;
|
1051
1070
|
return audioData;
|
1052
1071
|
}
|
1053
|
-
|
1054
|
-
remuxEmptyAudio(
|
1055
|
-
track: DemuxedAudioTrack,
|
1056
|
-
timeOffset: number,
|
1057
|
-
contiguous: boolean,
|
1058
|
-
videoData: Fragment,
|
1059
|
-
): RemuxedTrack | undefined {
|
1060
|
-
const inputTimeScale: number = track.inputTimeScale;
|
1061
|
-
const mp4timeScale: number = track.samplerate
|
1062
|
-
? track.samplerate
|
1063
|
-
: inputTimeScale;
|
1064
|
-
const scaleFactor: number = inputTimeScale / mp4timeScale;
|
1065
|
-
const nextAudioPts: number | null = this.nextAudioPts;
|
1066
|
-
// sync with video's timestamp
|
1067
|
-
const initDTS = this._initDTS as RationalTimestamp;
|
1068
|
-
const init90kHz = (initDTS.baseTime * 90000) / initDTS.timescale;
|
1069
|
-
const startDTS: number =
|
1070
|
-
(nextAudioPts !== null
|
1071
|
-
? nextAudioPts
|
1072
|
-
: videoData.startDTS * inputTimeScale) + init90kHz;
|
1073
|
-
const endDTS: number = videoData.endDTS * inputTimeScale + init90kHz;
|
1074
|
-
// one sample's duration value
|
1075
|
-
const frameDuration: number = scaleFactor * AAC_SAMPLES_PER_FRAME;
|
1076
|
-
// samples count of this segment's duration
|
1077
|
-
const nbSamples: number = Math.ceil((endDTS - startDTS) / frameDuration);
|
1078
|
-
// silent frame
|
1079
|
-
const silentFrame: Uint8Array | undefined = AAC.getSilentFrame(
|
1080
|
-
track.manifestCodec || track.codec,
|
1081
|
-
track.channelCount,
|
1082
|
-
);
|
1083
|
-
|
1084
|
-
logger.warn('[mp4-remuxer]: remux empty Audio');
|
1085
|
-
// Can't remux if we can't generate a silent frame...
|
1086
|
-
if (!silentFrame) {
|
1087
|
-
logger.trace(
|
1088
|
-
'[mp4-remuxer]: Unable to remuxEmptyAudio since we were unable to get a silent frame for given audio codec',
|
1089
|
-
);
|
1090
|
-
return;
|
1091
|
-
}
|
1092
|
-
|
1093
|
-
const samples: Array<any> = [];
|
1094
|
-
for (let i = 0; i < nbSamples; i++) {
|
1095
|
-
const stamp = startDTS + i * frameDuration;
|
1096
|
-
samples.push({ unit: silentFrame, pts: stamp, dts: stamp });
|
1097
|
-
}
|
1098
|
-
track.samples = samples;
|
1099
|
-
|
1100
|
-
return this.remuxAudio(track, timeOffset, contiguous, false);
|
1101
|
-
}
|
1102
1072
|
}
|
1103
1073
|
|
1104
1074
|
export function normalizePts(value: number, reference: number | null): number {
|
@@ -178,7 +178,7 @@ class PassThroughRemuxer implements Remuxer {
|
|
178
178
|
initSegment.initPTS = decodeTime - timeOffset;
|
179
179
|
if (initPTS && initPTS.timescale === 1) {
|
180
180
|
logger.warn(
|
181
|
-
`Adjusting initPTS
|
181
|
+
`Adjusting initPTS @${timeOffset} from ${initPTS.baseTime / initPTS.timescale} to ${initSegment.initPTS}`,
|
182
182
|
);
|
183
183
|
}
|
184
184
|
this.initPTS = initPTS = {
|
package/src/task-loop.ts
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
import { type ILogger, Logger } from './utils/logger';
|
2
|
+
|
1
3
|
/**
|
2
4
|
* @ignore
|
3
5
|
* Sub-class specialization of EventHandler base class.
|
@@ -27,13 +29,14 @@
|
|
27
29
|
* we are limiting the task execution per call stack to exactly one, but scheduling/post-poning further
|
28
30
|
* task processing on the next main loop iteration (also known as "next tick" in the Node/JS runtime lingo).
|
29
31
|
*/
|
30
|
-
export default class TaskLoop {
|
32
|
+
export default class TaskLoop extends Logger {
|
31
33
|
private readonly _boundTick: () => void;
|
32
34
|
private _tickTimer: number | null = null;
|
33
35
|
private _tickInterval: number | null = null;
|
34
36
|
private _tickCallCount = 0;
|
35
37
|
|
36
|
-
constructor() {
|
38
|
+
constructor(label: string, logger: ILogger) {
|
39
|
+
super(label, logger);
|
37
40
|
this._boundTick = this.tick.bind(this);
|
38
41
|
}
|
39
42
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import EwmaBandWidthEstimator from '../utils/ewma-bandwidth-estimator';
|
1
|
+
import type EwmaBandWidthEstimator from '../utils/ewma-bandwidth-estimator';
|
2
2
|
|
3
3
|
export interface ComponentAPI {
|
4
4
|
destroy(): void;
|
@@ -15,4 +15,6 @@ export interface AbrComponentAPI extends ComponentAPI {
|
|
15
15
|
export interface NetworkComponentAPI extends ComponentAPI {
|
16
16
|
startLoad(startPosition: number): void;
|
17
17
|
stopLoad(): void;
|
18
|
+
pauseBuffering?(): void;
|
19
|
+
resumeBuffering?(): void;
|
18
20
|
}
|
package/src/types/demuxer.ts
CHANGED
@@ -64,6 +64,7 @@ export interface DemuxedAudioTrack extends DemuxedTrack {
|
|
64
64
|
segmentCodec?: string;
|
65
65
|
channelCount?: number;
|
66
66
|
manifestCodec?: string;
|
67
|
+
parsedCodec?: string;
|
67
68
|
samples: AudioSample[];
|
68
69
|
}
|
69
70
|
|
@@ -72,12 +73,14 @@ export interface DemuxedVideoTrackBase extends DemuxedTrack {
|
|
72
73
|
height?: number;
|
73
74
|
pixelRatio?: [number, number];
|
74
75
|
audFound?: boolean;
|
76
|
+
vps?: Uint8Array[];
|
75
77
|
pps?: Uint8Array[];
|
76
78
|
sps?: Uint8Array[];
|
77
79
|
naluState?: number;
|
78
80
|
segmentCodec?: string;
|
79
81
|
manifestCodec?: string;
|
80
82
|
samples: VideoSample[] | Uint8Array;
|
83
|
+
params?: object;
|
81
84
|
}
|
82
85
|
|
83
86
|
export interface DemuxedVideoTrack extends DemuxedVideoTrackBase {
|
package/src/types/events.ts
CHANGED
@@ -1,7 +1,4 @@
|
|
1
|
-
|
2
|
-
import type { Fragment } from '../loader/fragment';
|
3
|
-
// eslint-disable-next-line import/no-duplicates
|
4
|
-
import type { Part } from '../loader/fragment';
|
1
|
+
import type { Fragment, MediaFragment, Part } from '../loader/fragment';
|
5
2
|
import type { LevelDetails } from '../loader/level-details';
|
6
3
|
import type {
|
7
4
|
HdcpLevel,
|
@@ -42,6 +39,10 @@ export interface MediaAttachedData {
|
|
42
39
|
mediaSource?: MediaSource;
|
43
40
|
}
|
44
41
|
|
42
|
+
export interface MediaEndedData {
|
43
|
+
stalled: boolean;
|
44
|
+
}
|
45
|
+
|
45
46
|
export interface BufferCodecsData {
|
46
47
|
video?: Track;
|
47
48
|
audio?: Track;
|
@@ -191,6 +192,18 @@ export interface LevelUpdatedData {
|
|
191
192
|
level: number;
|
192
193
|
}
|
193
194
|
|
195
|
+
export interface AudioTrackUpdatedData {
|
196
|
+
details: LevelDetails;
|
197
|
+
id: number;
|
198
|
+
groupId: string;
|
199
|
+
}
|
200
|
+
|
201
|
+
export interface SubtitleTrackUpdatedData {
|
202
|
+
details: LevelDetails;
|
203
|
+
id: number;
|
204
|
+
groupId: string;
|
205
|
+
}
|
206
|
+
|
194
207
|
export interface LevelPTSUpdatedData {
|
195
208
|
details: LevelDetails;
|
196
209
|
level: Level;
|
@@ -316,8 +329,8 @@ export interface NonNativeTextTracksData {
|
|
316
329
|
}
|
317
330
|
|
318
331
|
export interface InitPTSFoundData {
|
319
|
-
id:
|
320
|
-
frag:
|
332
|
+
id: PlaylistLevelType;
|
333
|
+
frag: MediaFragment;
|
321
334
|
initPTS: number;
|
322
335
|
timescale: number;
|
323
336
|
}
|
@@ -1,9 +1,9 @@
|
|
1
|
-
import type {
|
1
|
+
import type { MediaFragment } from '../loader/fragment';
|
2
2
|
import type { SourceBufferName } from './buffer';
|
3
3
|
import type { FragLoadedData } from './events';
|
4
4
|
|
5
5
|
export interface FragmentEntity {
|
6
|
-
body:
|
6
|
+
body: MediaFragment;
|
7
7
|
// appendedPTS is the latest buffered presentation time within the fragment's time range.
|
8
8
|
// It is used to determine: which fragment is appended at any given position, and hls.currentLevel.
|
9
9
|
appendedPTS: number | null;
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import type { AttrList } from '../utils/attr-list';
|
2
2
|
import type { LevelDetails } from '../loader/level-details';
|
3
|
-
import type { VideoRange } from './level';
|
3
|
+
import type { Level, VideoRange } from './level';
|
4
|
+
import type { PlaylistLevelType } from './loader';
|
4
5
|
|
5
6
|
export type AudioPlaylistType = 'AUDIO';
|
6
7
|
|
@@ -10,9 +11,16 @@ export type SubtitlePlaylistType = 'SUBTITLES' | 'CLOSED-CAPTIONS';
|
|
10
11
|
|
11
12
|
export type MediaPlaylistType = MainPlaylistType | SubtitlePlaylistType;
|
12
13
|
|
14
|
+
export type MediaSelection = {
|
15
|
+
[PlaylistLevelType.MAIN]: Level;
|
16
|
+
[PlaylistLevelType.AUDIO]?: MediaPlaylist;
|
17
|
+
[PlaylistLevelType.SUBTITLE]?: MediaPlaylist;
|
18
|
+
};
|
19
|
+
|
13
20
|
export type VideoSelectionOption = {
|
14
21
|
preferHDR?: boolean;
|
15
22
|
allowedVideoRanges?: Array<VideoRange>;
|
23
|
+
videoCodec?: string;
|
16
24
|
};
|
17
25
|
|
18
26
|
export type AudioSelectionOption = {
|
package/src/types/remuxer.ts
CHANGED
package/src/utils/attr-list.ts
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
import type { LevelDetails } from '../loader/level-details';
|
2
|
+
import type { ParsedMultivariantPlaylist } from '../loader/m3u8-parser';
|
3
|
+
import { logger } from './logger';
|
4
|
+
import { substituteVariables } from './variable-substitution';
|
5
|
+
|
1
6
|
const DECIMAL_RESOLUTION_REGEX = /^(\d+)x(\d+)$/;
|
2
7
|
const ATTR_LIST_REGEX = /(.+?)=(".*?"|.*?)(?:,|$)/g;
|
3
8
|
|
@@ -5,9 +10,15 @@ const ATTR_LIST_REGEX = /(.+?)=(".*?"|.*?)(?:,|$)/g;
|
|
5
10
|
export class AttrList {
|
6
11
|
[key: string]: any;
|
7
12
|
|
8
|
-
constructor(
|
13
|
+
constructor(
|
14
|
+
attrs: string | Record<string, any>,
|
15
|
+
parsed?: Pick<
|
16
|
+
ParsedMultivariantPlaylist | LevelDetails,
|
17
|
+
'variableList' | 'hasVariableRefs' | 'playlistParsingError'
|
18
|
+
>,
|
19
|
+
) {
|
9
20
|
if (typeof attrs === 'string') {
|
10
|
-
attrs = AttrList.parseAttrList(attrs);
|
21
|
+
attrs = AttrList.parseAttrList(attrs, parsed);
|
11
22
|
}
|
12
23
|
Object.assign(this, attrs);
|
13
24
|
}
|
@@ -63,6 +74,20 @@ export class AttrList {
|
|
63
74
|
return this[attrName];
|
64
75
|
}
|
65
76
|
|
77
|
+
enumeratedStringList<T extends { [key: string]: boolean }>(
|
78
|
+
attrName: string,
|
79
|
+
dict: T,
|
80
|
+
): { [key in keyof T]: boolean } {
|
81
|
+
const attrValue = this[attrName];
|
82
|
+
return (attrValue ? attrValue.split(/[ ,]+/) : []).reduce(
|
83
|
+
(result: { [key in keyof T]: boolean }, identifier: string) => {
|
84
|
+
result[identifier.toLowerCase() as keyof T] = true;
|
85
|
+
return result;
|
86
|
+
},
|
87
|
+
dict,
|
88
|
+
);
|
89
|
+
}
|
90
|
+
|
66
91
|
bool(attrName: string): boolean {
|
67
92
|
return this[attrName] === 'YES';
|
68
93
|
}
|
@@ -84,21 +109,83 @@ export class AttrList {
|
|
84
109
|
};
|
85
110
|
}
|
86
111
|
|
87
|
-
static parseAttrList(
|
88
|
-
|
112
|
+
static parseAttrList(
|
113
|
+
input: string,
|
114
|
+
parsed?: Pick<
|
115
|
+
ParsedMultivariantPlaylist | LevelDetails,
|
116
|
+
'variableList' | 'hasVariableRefs' | 'playlistParsingError'
|
117
|
+
>,
|
118
|
+
): Record<string, string> {
|
119
|
+
let match: RegExpExecArray | null;
|
89
120
|
const attrs = {};
|
90
121
|
const quote = '"';
|
91
122
|
ATTR_LIST_REGEX.lastIndex = 0;
|
92
123
|
while ((match = ATTR_LIST_REGEX.exec(input)) !== null) {
|
124
|
+
const name = match[1].trim();
|
93
125
|
let value = match[2];
|
94
|
-
|
95
|
-
if (
|
126
|
+
const quotedString =
|
96
127
|
value.indexOf(quote) === 0 &&
|
97
|
-
value.lastIndexOf(quote) === value.length - 1
|
98
|
-
|
128
|
+
value.lastIndexOf(quote) === value.length - 1;
|
129
|
+
let hexadecimalSequence = false;
|
130
|
+
if (quotedString) {
|
99
131
|
value = value.slice(1, -1);
|
132
|
+
} else {
|
133
|
+
switch (name) {
|
134
|
+
case 'IV':
|
135
|
+
case 'SCTE35-CMD':
|
136
|
+
case 'SCTE35-IN':
|
137
|
+
case 'SCTE35-OUT':
|
138
|
+
hexadecimalSequence = true;
|
139
|
+
}
|
140
|
+
}
|
141
|
+
if (parsed && (quotedString || hexadecimalSequence)) {
|
142
|
+
if (__USE_VARIABLE_SUBSTITUTION__) {
|
143
|
+
value = substituteVariables(parsed, value);
|
144
|
+
}
|
145
|
+
} else if (!hexadecimalSequence && !quotedString) {
|
146
|
+
switch (name) {
|
147
|
+
case 'CLOSED-CAPTIONS':
|
148
|
+
if (value === 'NONE') {
|
149
|
+
break;
|
150
|
+
}
|
151
|
+
// falls through
|
152
|
+
case 'ALLOWED-CPC':
|
153
|
+
case 'CLASS':
|
154
|
+
case 'ASSOC-LANGUAGE':
|
155
|
+
case 'AUDIO':
|
156
|
+
case 'BYTERANGE':
|
157
|
+
case 'CHANNELS':
|
158
|
+
case 'CHARACTERISTICS':
|
159
|
+
case 'CODECS':
|
160
|
+
case 'DATA-ID':
|
161
|
+
case 'END-DATE':
|
162
|
+
case 'GROUP-ID':
|
163
|
+
case 'ID':
|
164
|
+
case 'IMPORT':
|
165
|
+
case 'INSTREAM-ID':
|
166
|
+
case 'KEYFORMAT':
|
167
|
+
case 'KEYFORMATVERSIONS':
|
168
|
+
case 'LANGUAGE':
|
169
|
+
case 'NAME':
|
170
|
+
case 'PATHWAY-ID':
|
171
|
+
case 'QUERYPARAM':
|
172
|
+
case 'RECENTLY-REMOVED-DATERANGES':
|
173
|
+
case 'SERVER-URI':
|
174
|
+
case 'STABLE-RENDITION-ID':
|
175
|
+
case 'STABLE-VARIANT-ID':
|
176
|
+
case 'START-DATE':
|
177
|
+
case 'SUBTITLES':
|
178
|
+
case 'SUPPLEMENTAL-CODECS':
|
179
|
+
case 'URI':
|
180
|
+
case 'VALUE':
|
181
|
+
case 'VIDEO':
|
182
|
+
case 'X-ASSET-LIST':
|
183
|
+
case 'X-ASSET-URI':
|
184
|
+
// Since we are not checking tag:attribute combination, just warn rather than ignoring attribute
|
185
|
+
logger.warn(`${input}: attribute ${name} is missing quotes`);
|
186
|
+
// continue;
|
187
|
+
}
|
100
188
|
}
|
101
|
-
const name = match[1].trim();
|
102
189
|
attrs[name] = value;
|
103
190
|
}
|
104
191
|
return attrs;
|