hls.js 1.5.7 → 1.5.8-0.canary.10044

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 (72) hide show
  1. package/README.md +2 -1
  2. package/dist/hls-demo.js +10 -0
  3. package/dist/hls-demo.js.map +1 -1
  4. package/dist/hls.js +2314 -1298
  5. package/dist/hls.js.d.ts +97 -84
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +1486 -1075
  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 +1195 -789
  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 +1979 -982
  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 +22 -22
  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 +20 -8
  25. package/src/controller/base-stream-controller.ts +157 -36
  26. package/src/controller/buffer-controller.ts +203 -67
  27. package/src/controller/buffer-operation-queue.ts +16 -19
  28. package/src/controller/cap-level-controller.ts +2 -2
  29. package/src/controller/cmcd-controller.ts +27 -6
  30. package/src/controller/content-steering-controller.ts +8 -6
  31. package/src/controller/eme-controller.ts +9 -22
  32. package/src/controller/error-controller.ts +6 -8
  33. package/src/controller/fps-controller.ts +2 -3
  34. package/src/controller/fragment-tracker.ts +15 -11
  35. package/src/controller/gap-controller.ts +43 -16
  36. package/src/controller/latency-controller.ts +9 -11
  37. package/src/controller/level-controller.ts +12 -18
  38. package/src/controller/stream-controller.ts +36 -31
  39. package/src/controller/subtitle-stream-controller.ts +28 -40
  40. package/src/controller/subtitle-track-controller.ts +5 -3
  41. package/src/controller/timeline-controller.ts +23 -30
  42. package/src/crypt/aes-crypto.ts +21 -2
  43. package/src/crypt/decrypter-aes-mode.ts +4 -0
  44. package/src/crypt/decrypter.ts +32 -18
  45. package/src/crypt/fast-aes-key.ts +24 -5
  46. package/src/demux/audio/adts.ts +9 -4
  47. package/src/demux/sample-aes.ts +2 -0
  48. package/src/demux/transmuxer-interface.ts +4 -12
  49. package/src/demux/transmuxer-worker.ts +4 -4
  50. package/src/demux/transmuxer.ts +16 -3
  51. package/src/demux/tsdemuxer.ts +71 -37
  52. package/src/demux/video/avc-video-parser.ts +208 -119
  53. package/src/demux/video/base-video-parser.ts +134 -2
  54. package/src/demux/video/exp-golomb.ts +0 -208
  55. package/src/demux/video/hevc-video-parser.ts +746 -0
  56. package/src/events.ts +7 -0
  57. package/src/hls.ts +49 -37
  58. package/src/loader/fragment-loader.ts +9 -2
  59. package/src/loader/key-loader.ts +2 -0
  60. package/src/loader/level-key.ts +10 -9
  61. package/src/loader/playlist-loader.ts +4 -5
  62. package/src/remux/mp4-generator.ts +196 -1
  63. package/src/remux/mp4-remuxer.ts +23 -7
  64. package/src/task-loop.ts +5 -2
  65. package/src/types/component-api.ts +2 -0
  66. package/src/types/demuxer.ts +3 -0
  67. package/src/types/events.ts +4 -0
  68. package/src/utils/buffer-helper.ts +12 -31
  69. package/src/utils/codecs.ts +34 -5
  70. package/src/utils/encryption-methods-util.ts +21 -0
  71. package/src/utils/logger.ts +54 -24
  72. package/src/utils/mp4-tools.ts +4 -2
@@ -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,
@@ -312,7 +354,7 @@ export default class BaseStreamController
312
354
  this.stopLoad();
313
355
  super.onHandlerDestroying();
314
356
  // @ts-ignore
315
- this.hls = null;
357
+ this.hls = this.onMediaSeeking = this.onMediaEnded = null;
316
358
  }
317
359
 
318
360
  protected onHandlerDestroyed() {
@@ -486,7 +528,7 @@ export default class BaseStreamController
486
528
  payload.byteLength > 0 &&
487
529
  decryptData?.key &&
488
530
  decryptData.iv &&
489
- decryptData.method === 'AES-128'
531
+ isFullSegmentEncryption(decryptData.method)
490
532
  ) {
491
533
  const startTime = self.performance.now();
492
534
  // decrypt init segment data
@@ -495,6 +537,7 @@ export default class BaseStreamController
495
537
  new Uint8Array(payload),
496
538
  decryptData.key.buffer,
497
539
  decryptData.iv.buffer,
540
+ getAesModeFromFullSegmentMethod(decryptData.method),
498
541
  )
499
542
  .catch((err) => {
500
543
  hls.trigger(Events.ERROR, {
@@ -649,7 +692,7 @@ export default class BaseStreamController
649
692
  if (frag.encrypted && !frag.decryptdata?.key) {
650
693
  this.log(
651
694
  `Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${
652
- this.logPrefix === '[stream-controller]' ? 'level' : 'track'
695
+ this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'
653
696
  } ${frag.level}`,
654
697
  );
655
698
  this.state = State.KEY_LOADING;
@@ -673,8 +716,23 @@ export default class BaseStreamController
673
716
  this.keyLoader.loadClear(frag, details.encryptedFragments);
674
717
  }
675
718
 
719
+ const fragPrevious = this.fragPrevious;
720
+ if (
721
+ frag.sn !== 'initSegment' &&
722
+ (!fragPrevious || frag.sn !== fragPrevious.sn)
723
+ ) {
724
+ const shouldLoadParts = this.shouldLoadParts(level.details, frag.end);
725
+ if (shouldLoadParts !== this.loadingParts) {
726
+ this.log(
727
+ `LL-Part loading ${
728
+ shouldLoadParts ? 'ON' : 'OFF'
729
+ } loading sn ${fragPrevious?.sn}->${frag.sn}`,
730
+ );
731
+ this.loadingParts = shouldLoadParts;
732
+ }
733
+ }
676
734
  targetBufferTime = Math.max(frag.start, targetBufferTime || 0);
677
- if (this.config.lowLatencyMode && frag.sn !== 'initSegment') {
735
+ if (this.loadingParts && frag.sn !== 'initSegment') {
678
736
  const partList = details.partList;
679
737
  if (partList && progressCallback) {
680
738
  if (targetBufferTime > frag.end && details.fragmentHint) {
@@ -689,7 +747,7 @@ export default class BaseStreamController
689
747
  } of playlist [${details.startSN}-${
690
748
  details.endSN
691
749
  }] parts [0-${partIndex}-${partList.length - 1}] ${
692
- this.logPrefix === '[stream-controller]' ? 'level' : 'track'
750
+ this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'
693
751
  }: ${frag.level}, target: ${parseFloat(
694
752
  targetBufferTime.toFixed(3),
695
753
  )}`,
@@ -745,10 +803,22 @@ export default class BaseStreamController
745
803
  }
746
804
  }
747
805
 
806
+ if (frag.sn !== 'initSegment' && this.loadingParts) {
807
+ this.log(
808
+ `LL-Part loading OFF after next part miss @${targetBufferTime.toFixed(
809
+ 2,
810
+ )}`,
811
+ );
812
+ this.loadingParts = false;
813
+ } else if (!frag.url) {
814
+ // Selected fragment hint for part but not loading parts
815
+ return Promise.resolve(null);
816
+ }
817
+
748
818
  this.log(
749
819
  `Loading fragment ${frag.sn} cc: ${frag.cc} ${
750
820
  details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''
751
- }${this.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${
821
+ }${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'}: ${
752
822
  frag.level
753
823
  }, target: ${parseFloat(targetBufferTime.toFixed(3))}`,
754
824
  );
@@ -872,9 +942,50 @@ export default class BaseStreamController
872
942
  if (part) {
873
943
  part.stats.parsing.end = now;
874
944
  }
945
+ // See if part loading should be disabled/enabled based on buffer and playback position.
946
+ if (frag.sn !== 'initSegment') {
947
+ const levelDetails = this.getLevelDetails();
948
+ const loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
949
+ const shouldLoadParts =
950
+ loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
951
+ if (shouldLoadParts !== this.loadingParts) {
952
+ this.log(
953
+ `LL-Part loading ${
954
+ shouldLoadParts ? 'ON' : 'OFF'
955
+ } after parsing segment ending @${frag.end.toFixed(2)}`,
956
+ );
957
+ this.loadingParts = shouldLoadParts;
958
+ }
959
+ }
960
+
875
961
  this.updateLevelTiming(frag, part, level, chunkMeta.partial);
876
962
  }
877
963
 
964
+ private shouldLoadParts(
965
+ details: LevelDetails | undefined,
966
+ bufferEnd: number,
967
+ ): boolean {
968
+ if (this.config.lowLatencyMode) {
969
+ if (!details) {
970
+ return this.loadingParts;
971
+ }
972
+ if (details?.partList) {
973
+ // Buffer must be ahead of first part + duration of parts after last segment
974
+ // and playback must be at or past segment adjacent to part list
975
+ const firstPart = details.partList[0];
976
+ const safePartStart =
977
+ firstPart.end + (details.fragmentHint?.duration || 0);
978
+ if (
979
+ bufferEnd >= safePartStart &&
980
+ this.lastCurrentTime > firstPart.start - firstPart.fragment.duration
981
+ ) {
982
+ return true;
983
+ }
984
+ }
985
+ }
986
+ return false;
987
+ }
988
+
878
989
  protected getCurrentContext(
879
990
  chunkMeta: ChunkMetadata,
880
991
  ): { frag: Fragment; part: Part | null; level: Level } | null {
@@ -991,7 +1102,10 @@ export default class BaseStreamController
991
1102
  // Workaround flaw in getting forward buffer when maxBufferHole is smaller than gap at current pos
992
1103
  if (bufferInfo.len === 0 && bufferInfo.nextStart !== undefined) {
993
1104
  const bufferedFragAtPos = this.fragmentTracker.getBufferedFrag(pos, type);
994
- if (bufferedFragAtPos && bufferInfo.nextStart < bufferedFragAtPos.end) {
1105
+ if (
1106
+ bufferedFragAtPos &&
1107
+ (bufferInfo.nextStart <= bufferedFragAtPos.end || bufferedFragAtPos.gap)
1108
+ ) {
995
1109
  return BufferHelper.bufferInfo(
996
1110
  bufferable,
997
1111
  pos,
@@ -1004,7 +1118,7 @@ export default class BaseStreamController
1004
1118
 
1005
1119
  protected getMaxBufferLength(levelBitrate?: number): number {
1006
1120
  const { config } = this;
1007
- let maxBufLen;
1121
+ let maxBufLen: number;
1008
1122
  if (levelBitrate) {
1009
1123
  maxBufLen = Math.max(
1010
1124
  (8 * config.maxBufferSize) / levelBitrate,
@@ -1056,7 +1170,8 @@ export default class BaseStreamController
1056
1170
  // find fragment index, contiguous with end of buffer position
1057
1171
  const { config } = this;
1058
1172
  const start = fragments[0].start;
1059
- let frag;
1173
+ const canLoadParts = config.lowLatencyMode && !!levelDetails.partList;
1174
+ let frag: Fragment | null = null;
1060
1175
 
1061
1176
  if (levelDetails.live) {
1062
1177
  const initialLiveManifestSize = config.initialLiveManifestSize;
@@ -1076,6 +1191,10 @@ export default class BaseStreamController
1076
1191
  this.startPosition === -1) ||
1077
1192
  pos < start
1078
1193
  ) {
1194
+ if (canLoadParts && !this.loadingParts) {
1195
+ this.log(`LL-Part loading ON for initial live fragment`);
1196
+ this.loadingParts = true;
1197
+ }
1079
1198
  frag = this.getInitialLiveFragment(levelDetails, fragments);
1080
1199
  this.startPosition = this.nextLoadPosition = frag
1081
1200
  ? this.hls.liveSyncPosition || frag.start
@@ -1088,7 +1207,7 @@ export default class BaseStreamController
1088
1207
 
1089
1208
  // If we haven't run into any special cases already, just load the fragment most closely matching the requested position
1090
1209
  if (!frag) {
1091
- const end = config.lowLatencyMode
1210
+ const end = this.loadingParts
1092
1211
  ? levelDetails.partEnd
1093
1212
  : levelDetails.fragmentEnd;
1094
1213
  frag = this.getFragmentAtPosition(pos, end, levelDetails);
@@ -1271,7 +1390,7 @@ export default class BaseStreamController
1271
1390
  const partList = levelDetails.partList;
1272
1391
 
1273
1392
  const loadingParts = !!(
1274
- config.lowLatencyMode &&
1393
+ this.loadingParts &&
1275
1394
  partList?.length &&
1276
1395
  fragmentHint
1277
1396
  );
@@ -1550,7 +1669,7 @@ export default class BaseStreamController
1550
1669
  errorAction.resolved = true;
1551
1670
  }
1552
1671
  } else {
1553
- logger.warn(
1672
+ this.warn(
1554
1673
  `${data.details} reached or exceeded max retry (${retryCount})`,
1555
1674
  );
1556
1675
  return;
@@ -1639,7 +1758,9 @@ export default class BaseStreamController
1639
1758
  this.log('Reset loading state');
1640
1759
  this.fragCurrent = null;
1641
1760
  this.fragPrevious = null;
1642
- this.state = State.IDLE;
1761
+ if (this.state !== State.STOPPED) {
1762
+ this.state = State.IDLE;
1763
+ }
1643
1764
  }
1644
1765
 
1645
1766
  protected resetStartWhenNotLoaded(level: Level | null): void {