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.
Files changed (103) hide show
  1. package/README.md +4 -3
  2. package/dist/hls-demo.js +41 -38
  3. package/dist/hls-demo.js.map +1 -1
  4. package/dist/hls.js +4211 -2666
  5. package/dist/hls.js.d.ts +179 -110
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +2841 -1921
  8. package/dist/hls.light.js.map +1 -1
  9. package/dist/hls.light.min.js +1 -1
  10. package/dist/hls.light.min.js.map +1 -1
  11. package/dist/hls.light.mjs +2569 -1639
  12. package/dist/hls.light.mjs.map +1 -1
  13. package/dist/hls.min.js +1 -1
  14. package/dist/hls.min.js.map +1 -1
  15. package/dist/hls.mjs +3572 -2017
  16. package/dist/hls.mjs.map +1 -1
  17. package/dist/hls.worker.js +1 -1
  18. package/dist/hls.worker.js.map +1 -1
  19. package/package.json +38 -38
  20. package/src/config.ts +5 -2
  21. package/src/controller/abr-controller.ts +39 -25
  22. package/src/controller/audio-stream-controller.ts +156 -136
  23. package/src/controller/audio-track-controller.ts +1 -1
  24. package/src/controller/base-playlist-controller.ts +27 -10
  25. package/src/controller/base-stream-controller.ts +234 -89
  26. package/src/controller/buffer-controller.ts +250 -97
  27. package/src/controller/buffer-operation-queue.ts +16 -19
  28. package/src/controller/cap-level-controller.ts +3 -2
  29. package/src/controller/cmcd-controller.ts +51 -14
  30. package/src/controller/content-steering-controller.ts +29 -15
  31. package/src/controller/eme-controller.ts +10 -23
  32. package/src/controller/error-controller.ts +28 -22
  33. package/src/controller/fps-controller.ts +8 -3
  34. package/src/controller/fragment-finders.ts +44 -16
  35. package/src/controller/fragment-tracker.ts +58 -25
  36. package/src/controller/gap-controller.ts +43 -16
  37. package/src/controller/id3-track-controller.ts +45 -35
  38. package/src/controller/latency-controller.ts +18 -13
  39. package/src/controller/level-controller.ts +37 -19
  40. package/src/controller/stream-controller.ts +100 -83
  41. package/src/controller/subtitle-stream-controller.ts +35 -47
  42. package/src/controller/subtitle-track-controller.ts +5 -3
  43. package/src/controller/timeline-controller.ts +20 -22
  44. package/src/crypt/aes-crypto.ts +21 -2
  45. package/src/crypt/decrypter-aes-mode.ts +4 -0
  46. package/src/crypt/decrypter.ts +32 -16
  47. package/src/crypt/fast-aes-key.ts +28 -5
  48. package/src/demux/audio/aacdemuxer.ts +2 -2
  49. package/src/demux/audio/ac3-demuxer.ts +4 -3
  50. package/src/demux/audio/adts.ts +9 -4
  51. package/src/demux/audio/base-audio-demuxer.ts +16 -14
  52. package/src/demux/audio/mp3demuxer.ts +4 -3
  53. package/src/demux/audio/mpegaudio.ts +1 -1
  54. package/src/demux/mp4demuxer.ts +7 -7
  55. package/src/demux/sample-aes.ts +2 -0
  56. package/src/demux/transmuxer-interface.ts +8 -16
  57. package/src/demux/transmuxer-worker.ts +4 -4
  58. package/src/demux/transmuxer.ts +16 -3
  59. package/src/demux/tsdemuxer.ts +75 -38
  60. package/src/demux/video/avc-video-parser.ts +210 -121
  61. package/src/demux/video/base-video-parser.ts +135 -2
  62. package/src/demux/video/exp-golomb.ts +0 -208
  63. package/src/demux/video/hevc-video-parser.ts +749 -0
  64. package/src/events.ts +8 -1
  65. package/src/exports-named.ts +1 -1
  66. package/src/hls.ts +84 -47
  67. package/src/loader/date-range.ts +71 -5
  68. package/src/loader/fragment-loader.ts +23 -21
  69. package/src/loader/fragment.ts +8 -4
  70. package/src/loader/key-loader.ts +3 -1
  71. package/src/loader/level-details.ts +6 -6
  72. package/src/loader/level-key.ts +10 -9
  73. package/src/loader/m3u8-parser.ts +138 -144
  74. package/src/loader/playlist-loader.ts +5 -7
  75. package/src/remux/mp4-generator.ts +196 -1
  76. package/src/remux/mp4-remuxer.ts +32 -62
  77. package/src/remux/passthrough-remuxer.ts +1 -1
  78. package/src/task-loop.ts +5 -2
  79. package/src/types/component-api.ts +3 -1
  80. package/src/types/demuxer.ts +3 -0
  81. package/src/types/events.ts +19 -6
  82. package/src/types/fragment-tracker.ts +2 -2
  83. package/src/types/media-playlist.ts +9 -1
  84. package/src/types/remuxer.ts +1 -1
  85. package/src/utils/attr-list.ts +96 -9
  86. package/src/utils/buffer-helper.ts +12 -31
  87. package/src/utils/cea-608-parser.ts +1 -3
  88. package/src/utils/codecs.ts +34 -5
  89. package/src/utils/encryption-methods-util.ts +21 -0
  90. package/src/utils/fetch-loader.ts +1 -1
  91. package/src/utils/hash.ts +10 -0
  92. package/src/utils/hdr.ts +4 -7
  93. package/src/utils/imsc1-ttml-parser.ts +1 -1
  94. package/src/utils/keysystem-util.ts +1 -6
  95. package/src/utils/level-helper.ts +71 -44
  96. package/src/utils/logger.ts +58 -23
  97. package/src/utils/mp4-tools.ts +5 -3
  98. package/src/utils/rendition-helper.ts +100 -74
  99. package/src/utils/utf8-utils.ts +18 -0
  100. package/src/utils/variable-substitution.ts +0 -19
  101. package/src/utils/webvtt-parser.ts +2 -12
  102. package/src/demux/id3.ts +0 -411
  103. 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;
@@ -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: any;
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
- (videoTrack.width !== config.width ||
164
- videoTrack.height !== config.height ||
165
- videoTrack.pixelRatio?.[0] !== config.pixelRatio?.[0] ||
166
- videoTrack.pixelRatio?.[1] !== config.pixelRatio?.[1])
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
- } else {
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
- `AVC: ${toMsFromMpegTsClock(
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
- `AVC: ${toMsFromMpegTsClock(
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 by ${initSegment.initPTS - initPTS.baseTime}`,
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
  }
@@ -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 {
@@ -1,7 +1,4 @@
1
- // eslint-disable-next-line import/no-duplicates
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: string;
320
- frag: Fragment;
332
+ id: PlaylistLevelType;
333
+ frag: MediaFragment;
321
334
  initPTS: number;
322
335
  timescale: number;
323
336
  }
@@ -1,9 +1,9 @@
1
- import type { Fragment } from '../loader/fragment';
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: Fragment;
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 = {
@@ -1,5 +1,5 @@
1
1
  import type { TrackSet } from './track';
2
- import {
2
+ import type {
3
3
  DemuxedAudioTrack,
4
4
  DemuxedMetadataTrack,
5
5
  DemuxedUserdataTrack,
@@ -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(attrs: string | Record<string, any>) {
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(input: string): Record<string, any> {
88
- let match;
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;