hls.js 1.5.9 → 1.5.10-0.canary.10320

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 (89) 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 +3477 -2194
  5. package/dist/hls.js.d.ts +108 -85
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +2401 -1754
  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 +1989 -1315
  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 +2863 -1557
  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 +35 -35
  20. package/src/config.ts +3 -2
  21. package/src/controller/abr-controller.ts +24 -20
  22. package/src/controller/audio-stream-controller.ts +68 -74
  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 +160 -38
  26. package/src/controller/buffer-controller.ts +230 -92
  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 +6 -8
  33. package/src/controller/fps-controller.ts +8 -3
  34. package/src/controller/fragment-tracker.ts +15 -11
  35. package/src/controller/gap-controller.ts +43 -16
  36. package/src/controller/id3-track-controller.ts +7 -7
  37. package/src/controller/latency-controller.ts +9 -11
  38. package/src/controller/level-controller.ts +37 -19
  39. package/src/controller/stream-controller.ts +37 -32
  40. package/src/controller/subtitle-stream-controller.ts +28 -40
  41. package/src/controller/subtitle-track-controller.ts +5 -3
  42. package/src/controller/timeline-controller.ts +19 -21
  43. package/src/crypt/aes-crypto.ts +21 -2
  44. package/src/crypt/decrypter-aes-mode.ts +4 -0
  45. package/src/crypt/decrypter.ts +32 -16
  46. package/src/crypt/fast-aes-key.ts +28 -5
  47. package/src/demux/audio/aacdemuxer.ts +2 -2
  48. package/src/demux/audio/ac3-demuxer.ts +4 -3
  49. package/src/demux/audio/adts.ts +9 -4
  50. package/src/demux/audio/base-audio-demuxer.ts +16 -14
  51. package/src/demux/audio/mp3demuxer.ts +4 -3
  52. package/src/demux/audio/mpegaudio.ts +1 -1
  53. package/src/demux/mp4demuxer.ts +7 -7
  54. package/src/demux/sample-aes.ts +2 -0
  55. package/src/demux/transmuxer-interface.ts +4 -12
  56. package/src/demux/transmuxer-worker.ts +4 -4
  57. package/src/demux/transmuxer.ts +16 -3
  58. package/src/demux/tsdemuxer.ts +71 -37
  59. package/src/demux/video/avc-video-parser.ts +208 -119
  60. package/src/demux/video/base-video-parser.ts +147 -18
  61. package/src/demux/video/exp-golomb.ts +0 -208
  62. package/src/demux/video/hevc-video-parser.ts +749 -0
  63. package/src/empty-es.js +5 -0
  64. package/src/events.ts +8 -1
  65. package/src/exports-named.ts +1 -1
  66. package/src/hls.ts +61 -38
  67. package/src/loader/fragment-loader.ts +10 -3
  68. package/src/loader/key-loader.ts +3 -1
  69. package/src/loader/level-key.ts +10 -9
  70. package/src/loader/playlist-loader.ts +4 -5
  71. package/src/remux/mp4-generator.ts +196 -1
  72. package/src/remux/mp4-remuxer.ts +24 -8
  73. package/src/task-loop.ts +5 -2
  74. package/src/types/component-api.ts +3 -1
  75. package/src/types/demuxer.ts +4 -0
  76. package/src/types/events.ts +4 -0
  77. package/src/types/remuxer.ts +1 -1
  78. package/src/utils/buffer-helper.ts +12 -31
  79. package/src/utils/cea-608-parser.ts +1 -3
  80. package/src/utils/codecs.ts +34 -5
  81. package/src/utils/encryption-methods-util.ts +21 -0
  82. package/src/utils/fetch-loader.ts +1 -1
  83. package/src/utils/imsc1-ttml-parser.ts +1 -1
  84. package/src/utils/keysystem-util.ts +1 -6
  85. package/src/utils/logger.ts +58 -23
  86. package/src/utils/mp4-tools.ts +5 -3
  87. package/src/utils/utf8-utils.ts +18 -0
  88. package/src/utils/webvtt-parser.ts +1 -1
  89. package/src/demux/id3.ts +0 -411
@@ -1,12 +1,15 @@
1
1
  import TaskLoop from '../task-loop';
2
2
  import { FragmentState } from './fragment-tracker';
3
3
  import { Bufferable, BufferHelper, BufferInfo } from '../utils/buffer-helper';
4
- import { logger } from '../utils/logger';
5
4
  import { Events } from '../events';
6
5
  import { ErrorDetails, ErrorTypes } from '../errors';
7
6
  import { ChunkMetadata } from '../types/transmuxer';
8
7
  import { appendUint8Array } from '../utils/mp4-tools';
9
8
  import { alignStream } from '../utils/discontinuities';
9
+ import {
10
+ isFullSegmentEncryption,
11
+ getAesModeFromFullSegmentMethod,
12
+ } from '../utils/encryption-methods-util';
10
13
  import {
11
14
  findFragmentByPDT,
12
15
  findFragmentByPTS,
@@ -97,12 +100,8 @@ export default class BaseStreamController
97
100
  protected startFragRequested: boolean = false;
98
101
  protected decrypter: Decrypter;
99
102
  protected initPTS: RationalTimestamp[] = [];
100
- protected onvseeking: EventListener | null = null;
101
- protected onvended: EventListener | null = null;
102
-
103
- private readonly logPrefix: string = '';
104
- protected log: (msg: any) => void;
105
- protected warn: (msg: any) => void;
103
+ protected buffering: boolean = true;
104
+ private loadingParts: boolean = false;
106
105
 
107
106
  constructor(
108
107
  hls: Hls,
@@ -111,18 +110,32 @@ export default class BaseStreamController
111
110
  logPrefix: string,
112
111
  playlistType: PlaylistLevelType,
113
112
  ) {
114
- super();
113
+ super(logPrefix, hls.logger);
115
114
  this.playlistType = playlistType;
116
- this.logPrefix = logPrefix;
117
- this.log = logger.log.bind(logger, `${logPrefix}:`);
118
- this.warn = logger.warn.bind(logger, `${logPrefix}:`);
119
115
  this.hls = hls;
120
116
  this.fragmentLoader = new FragmentLoader(hls.config);
121
117
  this.keyLoader = keyLoader;
122
118
  this.fragmentTracker = fragmentTracker;
123
119
  this.config = hls.config;
124
120
  this.decrypter = new Decrypter(hls.config);
121
+ }
122
+
123
+ protected registerListeners() {
124
+ const { hls } = this;
125
+ hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
126
+ hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
127
+ hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
125
128
  hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
129
+ hls.on(Events.ERROR, this.onError, this);
130
+ }
131
+
132
+ protected unregisterListeners() {
133
+ const { hls } = this;
134
+ hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
135
+ hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
136
+ hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
137
+ hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
138
+ hls.off(Events.ERROR, this.onError, this);
126
139
  }
127
140
 
128
141
  protected doTick() {
@@ -150,6 +163,14 @@ export default class BaseStreamController
150
163
  this.state = State.STOPPED;
151
164
  }
152
165
 
166
+ public pauseBuffering() {
167
+ this.buffering = false;
168
+ }
169
+
170
+ public resumeBuffering() {
171
+ this.buffering = true;
172
+ }
173
+
153
174
  protected _streamEnded(
154
175
  bufferInfo: BufferInfo,
155
176
  levelDetails: LevelDetails,
@@ -197,10 +218,8 @@ export default class BaseStreamController
197
218
  data: MediaAttachedData,
198
219
  ) {
199
220
  const media = (this.media = this.mediaBuffer = data.media);
200
- this.onvseeking = this.onMediaSeeking.bind(this) as EventListener;
201
- this.onvended = this.onMediaEnded.bind(this) as EventListener;
202
- media.addEventListener('seeking', this.onvseeking);
203
- media.addEventListener('ended', this.onvended);
221
+ media.addEventListener('seeking', this.onMediaSeeking);
222
+ media.addEventListener('ended', this.onMediaEnded);
204
223
  const config = this.config;
205
224
  if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
206
225
  this.startLoad(config.startPosition);
@@ -215,10 +234,9 @@ export default class BaseStreamController
215
234
  }
216
235
 
217
236
  // remove video listeners
218
- if (media && this.onvseeking && this.onvended) {
219
- media.removeEventListener('seeking', this.onvseeking);
220
- media.removeEventListener('ended', this.onvended);
221
- this.onvseeking = this.onvended = null;
237
+ if (media) {
238
+ media.removeEventListener('seeking', this.onMediaSeeking);
239
+ media.removeEventListener('ended', this.onMediaEnded);
222
240
  }
223
241
  if (this.keyLoader) {
224
242
  this.keyLoader.detach();
@@ -229,7 +247,11 @@ export default class BaseStreamController
229
247
  this.stopLoad();
230
248
  }
231
249
 
232
- protected onMediaSeeking() {
250
+ protected onManifestLoading() {}
251
+
252
+ protected onError(event: Events.ERROR, data: ErrorData) {}
253
+
254
+ protected onMediaSeeking = () => {
233
255
  const { config, fragCurrent, media, mediaBuffer, state } = this;
234
256
  const currentTime: number = media ? media.currentTime : 0;
235
257
  const bufferInfo = BufferHelper.bufferInfo(
@@ -283,6 +305,21 @@ export default class BaseStreamController
283
305
  );
284
306
 
285
307
  this.lastCurrentTime = currentTime;
308
+ if (!this.loadingParts) {
309
+ const bufferEnd = Math.max(bufferInfo.end, currentTime);
310
+ const shouldLoadParts = this.shouldLoadParts(
311
+ this.getLevelDetails(),
312
+ bufferEnd,
313
+ );
314
+ if (shouldLoadParts) {
315
+ this.log(
316
+ `LL-Part loading ON after seeking to ${currentTime.toFixed(
317
+ 2,
318
+ )} with buffer @${bufferEnd.toFixed(2)}`,
319
+ );
320
+ this.loadingParts = shouldLoadParts;
321
+ }
322
+ }
286
323
  }
287
324
 
288
325
  // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
@@ -292,12 +329,17 @@ export default class BaseStreamController
292
329
 
293
330
  // Async tick to speed up processing
294
331
  this.tickImmediate();
295
- }
332
+ };
296
333
 
297
- protected onMediaEnded() {
334
+ protected onMediaEnded = () => {
298
335
  // reset startPosition and lastCurrentTime to restart playback @ stream beginning
299
336
  this.startPosition = this.lastCurrentTime = 0;
300
- }
337
+ if (this.playlistType === PlaylistLevelType.MAIN) {
338
+ this.hls.trigger(Events.MEDIA_ENDED, {
339
+ stalled: false,
340
+ });
341
+ }
342
+ };
301
343
 
302
344
  protected onManifestLoaded(
303
345
  event: Events.MANIFEST_LOADED,
@@ -308,11 +350,10 @@ export default class BaseStreamController
308
350
  }
309
351
 
310
352
  protected onHandlerDestroying() {
311
- this.hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
312
353
  this.stopLoad();
313
354
  super.onHandlerDestroying();
314
355
  // @ts-ignore
315
- this.hls = null;
356
+ this.hls = this.onMediaSeeking = this.onMediaEnded = null;
316
357
  }
317
358
 
318
359
  protected onHandlerDestroyed() {
@@ -496,7 +537,7 @@ export default class BaseStreamController
496
537
  payload.byteLength > 0 &&
497
538
  decryptData?.key &&
498
539
  decryptData.iv &&
499
- decryptData.method === 'AES-128'
540
+ isFullSegmentEncryption(decryptData.method)
500
541
  ) {
501
542
  const startTime = self.performance.now();
502
543
  // decrypt init segment data
@@ -505,6 +546,7 @@ export default class BaseStreamController
505
546
  new Uint8Array(payload),
506
547
  decryptData.key.buffer,
507
548
  decryptData.iv.buffer,
549
+ getAesModeFromFullSegmentMethod(decryptData.method),
508
550
  )
509
551
  .catch((err) => {
510
552
  hls.trigger(Events.ERROR, {
@@ -548,7 +590,9 @@ export default class BaseStreamController
548
590
  throw new Error('init load aborted, missing levels');
549
591
  }
550
592
  const stats = data.frag.stats;
551
- this.state = State.IDLE;
593
+ if (this.state !== State.STOPPED) {
594
+ this.state = State.IDLE;
595
+ }
552
596
  data.frag.data = new Uint8Array(data.payload);
553
597
  stats.parsing.start = stats.buffering.start = self.performance.now();
554
598
  stats.parsing.end = stats.buffering.end = self.performance.now();
@@ -659,7 +703,7 @@ export default class BaseStreamController
659
703
  if (frag.encrypted && !frag.decryptdata?.key) {
660
704
  this.log(
661
705
  `Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${
662
- this.logPrefix === '[stream-controller]' ? 'level' : 'track'
706
+ this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'
663
707
  } ${frag.level}`,
664
708
  );
665
709
  this.state = State.KEY_LOADING;
@@ -683,8 +727,23 @@ export default class BaseStreamController
683
727
  this.keyLoader.loadClear(frag, details.encryptedFragments);
684
728
  }
685
729
 
730
+ const fragPrevious = this.fragPrevious;
731
+ if (
732
+ frag.sn !== 'initSegment' &&
733
+ (!fragPrevious || frag.sn !== fragPrevious.sn)
734
+ ) {
735
+ const shouldLoadParts = this.shouldLoadParts(level.details, frag.end);
736
+ if (shouldLoadParts !== this.loadingParts) {
737
+ this.log(
738
+ `LL-Part loading ${
739
+ shouldLoadParts ? 'ON' : 'OFF'
740
+ } loading sn ${fragPrevious?.sn}->${frag.sn}`,
741
+ );
742
+ this.loadingParts = shouldLoadParts;
743
+ }
744
+ }
686
745
  targetBufferTime = Math.max(frag.start, targetBufferTime || 0);
687
- if (this.config.lowLatencyMode && frag.sn !== 'initSegment') {
746
+ if (this.loadingParts && frag.sn !== 'initSegment') {
688
747
  const partList = details.partList;
689
748
  if (partList && progressCallback) {
690
749
  if (targetBufferTime > frag.end && details.fragmentHint) {
@@ -699,7 +758,7 @@ export default class BaseStreamController
699
758
  } of playlist [${details.startSN}-${
700
759
  details.endSN
701
760
  }] parts [0-${partIndex}-${partList.length - 1}] ${
702
- this.logPrefix === '[stream-controller]' ? 'level' : 'track'
761
+ this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'
703
762
  }: ${frag.level}, target: ${parseFloat(
704
763
  targetBufferTime.toFixed(3),
705
764
  )}`,
@@ -755,10 +814,22 @@ export default class BaseStreamController
755
814
  }
756
815
  }
757
816
 
817
+ if (frag.sn !== 'initSegment' && this.loadingParts) {
818
+ this.log(
819
+ `LL-Part loading OFF after next part miss @${targetBufferTime.toFixed(
820
+ 2,
821
+ )}`,
822
+ );
823
+ this.loadingParts = false;
824
+ } else if (!frag.url) {
825
+ // Selected fragment hint for part but not loading parts
826
+ return Promise.resolve(null);
827
+ }
828
+
758
829
  this.log(
759
830
  `Loading fragment ${frag.sn} cc: ${frag.cc} ${
760
831
  details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''
761
- }${this.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${
832
+ }${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'}: ${
762
833
  frag.level
763
834
  }, target: ${parseFloat(targetBufferTime.toFixed(3))}`,
764
835
  );
@@ -882,9 +953,50 @@ export default class BaseStreamController
882
953
  if (part) {
883
954
  part.stats.parsing.end = now;
884
955
  }
956
+ // See if part loading should be disabled/enabled based on buffer and playback position.
957
+ if (frag.sn !== 'initSegment') {
958
+ const levelDetails = this.getLevelDetails();
959
+ const loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
960
+ const shouldLoadParts =
961
+ loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
962
+ if (shouldLoadParts !== this.loadingParts) {
963
+ this.log(
964
+ `LL-Part loading ${
965
+ shouldLoadParts ? 'ON' : 'OFF'
966
+ } after parsing segment ending @${frag.end.toFixed(2)}`,
967
+ );
968
+ this.loadingParts = shouldLoadParts;
969
+ }
970
+ }
971
+
885
972
  this.updateLevelTiming(frag, part, level, chunkMeta.partial);
886
973
  }
887
974
 
975
+ private shouldLoadParts(
976
+ details: LevelDetails | undefined,
977
+ bufferEnd: number,
978
+ ): boolean {
979
+ if (this.config.lowLatencyMode) {
980
+ if (!details) {
981
+ return this.loadingParts;
982
+ }
983
+ if (details?.partList) {
984
+ // Buffer must be ahead of first part + duration of parts after last segment
985
+ // and playback must be at or past segment adjacent to part list
986
+ const firstPart = details.partList[0];
987
+ const safePartStart =
988
+ firstPart.end + (details.fragmentHint?.duration || 0);
989
+ if (
990
+ bufferEnd >= safePartStart &&
991
+ this.lastCurrentTime > firstPart.start - firstPart.fragment.duration
992
+ ) {
993
+ return true;
994
+ }
995
+ }
996
+ }
997
+ return false;
998
+ }
999
+
888
1000
  protected getCurrentContext(
889
1001
  chunkMeta: ChunkMetadata,
890
1002
  ): { frag: Fragment; part: Part | null; level: Level } | null {
@@ -1001,7 +1113,10 @@ export default class BaseStreamController
1001
1113
  // Workaround flaw in getting forward buffer when maxBufferHole is smaller than gap at current pos
1002
1114
  if (bufferInfo.len === 0 && bufferInfo.nextStart !== undefined) {
1003
1115
  const bufferedFragAtPos = this.fragmentTracker.getBufferedFrag(pos, type);
1004
- if (bufferedFragAtPos && bufferInfo.nextStart < bufferedFragAtPos.end) {
1116
+ if (
1117
+ bufferedFragAtPos &&
1118
+ (bufferInfo.nextStart <= bufferedFragAtPos.end || bufferedFragAtPos.gap)
1119
+ ) {
1005
1120
  return BufferHelper.bufferInfo(
1006
1121
  bufferable,
1007
1122
  pos,
@@ -1014,7 +1129,7 @@ export default class BaseStreamController
1014
1129
 
1015
1130
  protected getMaxBufferLength(levelBitrate?: number): number {
1016
1131
  const { config } = this;
1017
- let maxBufLen;
1132
+ let maxBufLen: number;
1018
1133
  if (levelBitrate) {
1019
1134
  maxBufLen = Math.max(
1020
1135
  (8 * config.maxBufferSize) / levelBitrate,
@@ -1067,7 +1182,8 @@ export default class BaseStreamController
1067
1182
  // find fragment index, contiguous with end of buffer position
1068
1183
  const { config } = this;
1069
1184
  const start = fragments[0].start;
1070
- let frag;
1185
+ const canLoadParts = config.lowLatencyMode && !!levelDetails.partList;
1186
+ let frag: Fragment | null = null;
1071
1187
 
1072
1188
  if (levelDetails.live) {
1073
1189
  const initialLiveManifestSize = config.initialLiveManifestSize;
@@ -1087,6 +1203,10 @@ export default class BaseStreamController
1087
1203
  this.startPosition === -1) ||
1088
1204
  pos < start
1089
1205
  ) {
1206
+ if (canLoadParts && !this.loadingParts) {
1207
+ this.log(`LL-Part loading ON for initial live fragment`);
1208
+ this.loadingParts = true;
1209
+ }
1090
1210
  frag = this.getInitialLiveFragment(levelDetails, fragments);
1091
1211
  this.startPosition = this.nextLoadPosition = frag
1092
1212
  ? this.hls.liveSyncPosition || frag.start
@@ -1099,7 +1219,7 @@ export default class BaseStreamController
1099
1219
 
1100
1220
  // If we haven't run into any special cases already, just load the fragment most closely matching the requested position
1101
1221
  if (!frag) {
1102
- const end = config.lowLatencyMode
1222
+ const end = this.loadingParts
1103
1223
  ? levelDetails.partEnd
1104
1224
  : levelDetails.fragmentEnd;
1105
1225
  frag = this.getFragmentAtPosition(pos, end, levelDetails);
@@ -1282,7 +1402,7 @@ export default class BaseStreamController
1282
1402
  const partList = levelDetails.partList;
1283
1403
 
1284
1404
  const loadingParts = !!(
1285
- config.lowLatencyMode &&
1405
+ this.loadingParts &&
1286
1406
  partList?.length &&
1287
1407
  fragmentHint
1288
1408
  );
@@ -1561,7 +1681,7 @@ export default class BaseStreamController
1561
1681
  errorAction.resolved = true;
1562
1682
  }
1563
1683
  } else {
1564
- logger.warn(
1684
+ this.warn(
1565
1685
  `${data.details} reached or exceeded max retry (${retryCount})`,
1566
1686
  );
1567
1687
  return;
@@ -1650,7 +1770,9 @@ export default class BaseStreamController
1650
1770
  this.log('Reset loading state');
1651
1771
  this.fragCurrent = null;
1652
1772
  this.fragPrevious = null;
1653
- this.state = State.IDLE;
1773
+ if (this.state !== State.STOPPED) {
1774
+ this.state = State.IDLE;
1775
+ }
1654
1776
  }
1655
1777
 
1656
1778
  protected resetStartWhenNotLoaded(level: Level | null): void {