hls.js 1.5.13 → 1.5.14-0.canary.10415

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
@@ -1,4 +1,4 @@
1
- import {
1
+ import type {
2
2
  ManifestLoadedData,
3
3
  ManifestParsedData,
4
4
  LevelLoadedData,
@@ -27,8 +27,6 @@ import type Hls from '../hls';
27
27
  import type { HlsUrlParameters, LevelParsed } from '../types/level';
28
28
  import type { MediaPlaylist } from '../types/media-playlist';
29
29
 
30
- let chromeOrFirefox: boolean;
31
-
32
30
  export default class LevelController extends BasePlaylistController {
33
31
  private _levels: Level[] = [];
34
32
  private _firstLevel: number = -1;
@@ -45,7 +43,7 @@ export default class LevelController extends BasePlaylistController {
45
43
  hls: Hls,
46
44
  contentSteeringController: ContentSteeringController | null,
47
45
  ) {
48
- super(hls, '[level-controller]');
46
+ super(hls, 'level-controller');
49
47
  this.steering = contentSteeringController;
50
48
  this._registerListeners();
51
49
  }
@@ -119,22 +117,12 @@ export default class LevelController extends BasePlaylistController {
119
117
 
120
118
  data.levels.forEach((levelParsed: LevelParsed) => {
121
119
  const attributes = levelParsed.attrs;
122
-
123
- // erase audio codec info if browser does not support mp4a.40.34.
124
- // demuxer will autodetect codec and fallback to mpeg/audio
125
120
  let { audioCodec, videoCodec } = levelParsed;
126
- if (audioCodec?.indexOf('mp4a.40.34') !== -1) {
127
- chromeOrFirefox ||= /chrome|firefox/i.test(navigator.userAgent);
128
- if (chromeOrFirefox) {
129
- levelParsed.audioCodec = audioCodec = undefined;
130
- }
131
- }
132
-
133
121
  if (audioCodec) {
134
- levelParsed.audioCodec = audioCodec = getCodecCompatibleName(
135
- audioCodec,
136
- preferManagedMediaSource,
137
- );
122
+ // Returns empty and set to undefined for 'mp4a.40.34' with fallback to 'audio/mpeg' SourceBuffer
123
+ levelParsed.audioCodec = audioCodec =
124
+ getCodecCompatibleName(audioCodec, preferManagedMediaSource) ||
125
+ undefined;
138
126
  }
139
127
 
140
128
  if (videoCodec?.indexOf('avc1') === 0) {
@@ -521,6 +509,30 @@ export default class LevelController extends BasePlaylistController {
521
509
  this._startLevel = newLevel;
522
510
  }
523
511
 
512
+ get pathwayPriority(): string[] | null {
513
+ if (this.steering) {
514
+ return this.steering.pathwayPriority;
515
+ }
516
+
517
+ return null;
518
+ }
519
+
520
+ set pathwayPriority(pathwayPriority: string[]) {
521
+ if (this.steering) {
522
+ const pathwaysList = this.steering.pathways();
523
+ const filteredPathwayPriority = pathwayPriority.filter((pathwayId) => {
524
+ return pathwaysList.indexOf(pathwayId) !== -1;
525
+ });
526
+ if (pathwayPriority.length < 1) {
527
+ this.warn(
528
+ `pathwayPriority ${pathwayPriority} should contain at least one pathway from list: ${pathwaysList}`,
529
+ );
530
+ return;
531
+ }
532
+ this.steering.pathwayPriority = filteredPathwayPriority;
533
+ }
534
+ }
535
+
524
536
  protected onError(event: Events.ERROR, data: ErrorData) {
525
537
  if (data.fatal || !data.context) {
526
538
  return;
@@ -572,7 +584,13 @@ export default class LevelController extends BasePlaylistController {
572
584
  if (curLevel.fragmentError === 0) {
573
585
  curLevel.loadError = 0;
574
586
  }
575
- this.playlistLoaded(level, data, curLevel.details);
587
+ // Ignore matching details populated by loading a Media Playlist directly
588
+ let previousDetails = curLevel.details;
589
+ if (previousDetails === data.details && previousDetails.advanced) {
590
+ previousDetails = undefined;
591
+ }
592
+
593
+ this.playlistLoaded(level, data, previousDetails);
576
594
  } else if (data.deliveryDirectives?.skip) {
577
595
  // received a delta playlist update that cannot be merged
578
596
  details.deltaUpdateFailed = true;
@@ -2,9 +2,14 @@ import BaseStreamController, { State } from './base-stream-controller';
2
2
  import { changeTypeSupported } from '../is-supported';
3
3
  import { Events } from '../events';
4
4
  import { BufferHelper, BufferInfo } from '../utils/buffer-helper';
5
+ import { findFragmentByPTS } from './fragment-finders';
5
6
  import { FragmentState } from './fragment-tracker';
6
7
  import { PlaylistContextType, PlaylistLevelType } from '../types/loader';
7
- import { ElementaryStreamTypes, Fragment } from '../loader/fragment';
8
+ import {
9
+ ElementaryStreamTypes,
10
+ Fragment,
11
+ MediaFragment,
12
+ } from '../loader/fragment';
8
13
  import TransmuxerInterface from '../demux/transmuxer-interface';
9
14
  import { ChunkMetadata } from '../types/transmuxer';
10
15
  import GapController, { MAX_START_GAP_JUMP } from './gap-controller';
@@ -12,15 +17,15 @@ import { ErrorDetails } from '../errors';
12
17
  import type { NetworkComponentAPI } from '../types/component-api';
13
18
  import type Hls from '../hls';
14
19
  import type { Level } from '../types/level';
15
- import type { LevelDetails } from '../loader/level-details';
16
20
  import type { FragmentTracker } from './fragment-tracker';
17
21
  import type KeyLoader from '../loader/key-loader';
18
22
  import type { TransmuxerResult } from '../types/transmuxer';
19
- import type { TrackSet } from '../types/track';
23
+ import type { Track, TrackSet } from '../types/track';
20
24
  import type { SourceBufferName } from '../types/buffer';
21
25
  import type {
22
26
  AudioTrackSwitchedData,
23
27
  AudioTrackSwitchingData,
28
+ BufferCodecsData,
24
29
  BufferCreatedData,
25
30
  BufferEOSData,
26
31
  BufferFlushedData,
@@ -49,8 +54,6 @@ export default class StreamController
49
54
  private altAudio: boolean = false;
50
55
  private audioOnly: boolean = false;
51
56
  private fragPlaying: Fragment | null = null;
52
- private onvplaying: EventListener | null = null;
53
- private onvseeked: EventListener | null = null;
54
57
  private fragLastKbps: number = 0;
55
58
  private couldBacktrack: boolean = false;
56
59
  private backtrackFragment: Fragment | null = null;
@@ -66,17 +69,15 @@ export default class StreamController
66
69
  hls,
67
70
  fragmentTracker,
68
71
  keyLoader,
69
- '[stream-controller]',
72
+ 'stream-controller',
70
73
  PlaylistLevelType.MAIN,
71
74
  );
72
- this._registerListeners();
75
+ this.registerListeners();
73
76
  }
74
77
 
75
- private _registerListeners() {
78
+ protected registerListeners() {
79
+ super.registerListeners();
76
80
  const { hls } = this;
77
- hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
78
- hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
79
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
80
81
  hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
81
82
  hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
82
83
  hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
@@ -85,7 +86,6 @@ export default class StreamController
85
86
  this.onFragLoadEmergencyAborted,
86
87
  this,
87
88
  );
88
- hls.on(Events.ERROR, this.onError, this);
89
89
  hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
90
90
  hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
91
91
  hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
@@ -94,11 +94,9 @@ export default class StreamController
94
94
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
95
95
  }
96
96
 
97
- protected _unregisterListeners() {
97
+ protected unregisterListeners() {
98
+ super.unregisterListeners();
98
99
  const { hls } = this;
99
- hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
100
- hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
101
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
102
100
  hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
103
101
  hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
104
102
  hls.off(
@@ -106,7 +104,6 @@ export default class StreamController
106
104
  this.onFragLoadEmergencyAborted,
107
105
  this,
108
106
  );
109
- hls.off(Events.ERROR, this.onError, this);
110
107
  hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
111
108
  hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
112
109
  hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
@@ -116,7 +113,9 @@ export default class StreamController
116
113
  }
117
114
 
118
115
  protected onHandlerDestroying() {
119
- this._unregisterListeners();
116
+ // @ts-ignore
117
+ this.onMediaPlaying = this.onMediaSeeked = null;
118
+ this.unregisterListeners();
120
119
  super.onHandlerDestroying();
121
120
  }
122
121
 
@@ -220,6 +219,9 @@ export default class StreamController
220
219
  }
221
220
 
222
221
  private doTickIdle() {
222
+ if (!this.buffering) {
223
+ return;
224
+ }
223
225
  const { hls, levelLastLoaded, levels, media } = this;
224
226
 
225
227
  // if start level not parsed yet OR
@@ -363,7 +365,6 @@ export default class StreamController
363
365
  ) {
364
366
  // Check if fragment is not loaded
365
367
  const fragState = this.fragmentTracker.getState(frag);
366
- this.fragCurrent = frag;
367
368
  if (
368
369
  fragState === FragmentState.NOT_LOADED ||
369
370
  fragState === FragmentState.PARTIAL
@@ -376,7 +377,6 @@ export default class StreamController
376
377
  );
377
378
  this._loadBitrateTestFrag(frag, level);
378
379
  } else {
379
- this.startFragRequested = true;
380
380
  super.loadFragment(frag, level, targetBufferTime);
381
381
  }
382
382
  } else {
@@ -516,10 +516,8 @@ export default class StreamController
516
516
  ) {
517
517
  super.onMediaAttached(event, data);
518
518
  const media = data.media;
519
- this.onvplaying = this.onMediaPlaying.bind(this);
520
- this.onvseeked = this.onMediaSeeked.bind(this);
521
- media.addEventListener('playing', this.onvplaying as EventListener);
522
- media.addEventListener('seeked', this.onvseeked as EventListener);
519
+ media.addEventListener('playing', this.onMediaPlaying);
520
+ media.addEventListener('seeked', this.onMediaSeeked);
523
521
  this.gapController = new GapController(
524
522
  this.config,
525
523
  media,
@@ -530,12 +528,11 @@ export default class StreamController
530
528
 
531
529
  protected onMediaDetaching() {
532
530
  const { media } = this;
533
- if (media && this.onvplaying && this.onvseeked) {
534
- media.removeEventListener('playing', this.onvplaying);
535
- media.removeEventListener('seeked', this.onvseeked);
536
- this.onvplaying = this.onvseeked = null;
537
- this.videoBuffer = null;
531
+ if (media) {
532
+ media.removeEventListener('playing', this.onMediaPlaying);
533
+ media.removeEventListener('seeked', this.onMediaSeeked);
538
534
  }
535
+ this.videoBuffer = null;
539
536
  this.fragPlaying = null;
540
537
  if (this.gapController) {
541
538
  this.gapController.destroy();
@@ -544,12 +541,12 @@ export default class StreamController
544
541
  super.onMediaDetaching();
545
542
  }
546
543
 
547
- private onMediaPlaying() {
544
+ private onMediaPlaying = () => {
548
545
  // tick to speed up FRAG_CHANGED triggering
549
546
  this.tick();
550
- }
547
+ };
551
548
 
552
- private onMediaSeeked() {
549
+ private onMediaSeeked = () => {
553
550
  const media = this.media;
554
551
  const currentTime = media ? media.currentTime : null;
555
552
  if (Number.isFinite(currentTime)) {
@@ -569,21 +566,17 @@ export default class StreamController
569
566
 
570
567
  // tick to speed up FRAG_CHANGED triggering
571
568
  this.tick();
572
- }
569
+ };
573
570
 
574
- private onManifestLoading() {
571
+ protected onManifestLoading() {
572
+ super.onManifestLoading();
575
573
  // reset buffer on manifest loading
576
574
  this.log('Trigger BUFFER_RESET');
577
575
  this.hls.trigger(Events.BUFFER_RESET, undefined);
578
- this.fragmentTracker.removeAllFragments();
579
576
  this.couldBacktrack = false;
580
- this.startPosition = this.lastCurrentTime = this.fragLastKbps = 0;
581
- this.levels =
582
- this.fragPlaying =
583
- this.backtrackFragment =
584
- this.levelLastLoaded =
585
- null;
586
- this.altAudio = this.audioOnly = this.startFragRequested = false;
577
+ this.fragLastKbps = 0;
578
+ this.fragPlaying = this.backtrackFragment = null;
579
+ this.altAudio = this.audioOnly = false;
587
580
  }
588
581
 
589
582
  private onManifestParsed(
@@ -697,7 +690,8 @@ export default class StreamController
697
690
  }
698
691
 
699
692
  protected _handleFragmentLoadProgress(data: FragLoadedData) {
700
- const { frag, part, payload } = data;
693
+ const frag = data.frag as MediaFragment;
694
+ const { part, payload } = data;
701
695
  const { levels } = this;
702
696
  if (!levels) {
703
697
  this.warn(
@@ -706,7 +700,7 @@ export default class StreamController
706
700
  return;
707
701
  }
708
702
  const currentLevel = levels[frag.level];
709
- const details = currentLevel.details as LevelDetails;
703
+ const details = currentLevel.details;
710
704
  if (!details) {
711
705
  this.warn(
712
706
  `Dropping fragment ${frag.sn} of level ${frag.level} after level details were reset`,
@@ -735,7 +729,7 @@ export default class StreamController
735
729
  const partial = partIndex !== -1;
736
730
  const chunkMeta = new ChunkMetadata(
737
731
  frag.level,
738
- frag.sn as number,
732
+ frag.sn,
739
733
  frag.stats.chunkCount,
740
734
  payload.byteLength,
741
735
  partIndex,
@@ -879,12 +873,12 @@ export default class StreamController
879
873
  (8 * stats.total) / (stats.buffering.end - stats.loading.first),
880
874
  );
881
875
  if (frag.sn !== 'initSegment') {
882
- this.fragPrevious = frag;
876
+ this.fragPrevious = frag as MediaFragment;
883
877
  }
884
878
  this.fragBufferedComplete(frag, part);
885
879
  }
886
880
 
887
- private onError(event: Events.ERROR, data: ErrorData) {
881
+ protected onError(event: Events.ERROR, data: ErrorData) {
888
882
  if (data.fatal) {
889
883
  this.state = State.ERROR;
890
884
  return;
@@ -942,8 +936,10 @@ export default class StreamController
942
936
 
943
937
  if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) {
944
938
  // Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
945
- const activeFrag = this.state !== State.IDLE ? this.fragCurrent : null;
946
- gapController.poll(this.lastCurrentTime, activeFrag);
939
+ const state = this.state;
940
+ const activeFrag = state !== State.IDLE ? this.fragCurrent : null;
941
+ const levelDetails = this.getLevelDetails();
942
+ gapController.poll(this.lastCurrentTime, activeFrag, levelDetails, state);
947
943
  }
948
944
 
949
945
  this.lastCurrentTime = media.currentTime;
@@ -955,7 +951,7 @@ export default class StreamController
955
951
  // in that case, reset startFragRequested flag
956
952
  if (!this.loadedmetadata) {
957
953
  this.startFragRequested = false;
958
- this.nextLoadPosition = this.startPosition;
954
+ this.nextLoadPosition = this.lastCurrentTime;
959
955
  }
960
956
  this.tickImmediate();
961
957
  }
@@ -1067,7 +1063,7 @@ export default class StreamController
1067
1063
  }
1068
1064
 
1069
1065
  private _handleTransmuxComplete(transmuxResult: TransmuxerResult) {
1070
- const id = 'main';
1066
+ const id = this.playlistType;
1071
1067
  const { hls } = this;
1072
1068
  const { remuxResult, chunkMeta } = transmuxResult;
1073
1069
 
@@ -1117,7 +1113,7 @@ export default class StreamController
1117
1113
  }
1118
1114
 
1119
1115
  // Avoid buffering if backtracking this fragment
1120
- if (video && details && frag.sn !== 'initSegment') {
1116
+ if (video && details) {
1121
1117
  const prevFrag = details.fragments[frag.sn - 1 - details.startSN];
1122
1118
  const isFirstFragment = frag.sn === details.startSN;
1123
1119
  const isFirstInDiscontinuity = !prevFrag || frag.cc > prevFrag.cc;
@@ -1307,6 +1303,7 @@ export default class StreamController
1307
1303
  currentLevel.audioCodec || ''
1308
1304
  }/${audio.codec}]`,
1309
1305
  );
1306
+ delete tracks.audiovideo;
1310
1307
  }
1311
1308
  if (video) {
1312
1309
  video.levelCodec = currentLevel.videoCodec;
@@ -1318,28 +1315,34 @@ export default class StreamController
1318
1315
  video.codec
1319
1316
  }]`,
1320
1317
  );
1318
+ delete tracks.audiovideo;
1321
1319
  }
1322
1320
  if (audiovideo) {
1323
1321
  this.log(
1324
1322
  `Init audiovideo buffer, container:${audiovideo.container}, codecs[level/parsed]=[${currentLevel.codecs}/${audiovideo.codec}]`,
1325
1323
  );
1324
+ delete tracks.video;
1325
+ delete tracks.audio;
1326
+ }
1327
+ const trackTypes = Object.keys(tracks);
1328
+ if (trackTypes.length) {
1329
+ this.hls.trigger(Events.BUFFER_CODECS, tracks as BufferCodecsData);
1330
+ // loop through tracks that are going to be provided to bufferController
1331
+ trackTypes.forEach((trackName) => {
1332
+ const track = tracks[trackName] as Track;
1333
+ const initSegment = track.initSegment;
1334
+ if (initSegment?.byteLength) {
1335
+ this.hls.trigger(Events.BUFFER_APPENDING, {
1336
+ type: trackName as SourceBufferName,
1337
+ data: initSegment,
1338
+ frag,
1339
+ part: null,
1340
+ chunkMeta,
1341
+ parent: frag.type,
1342
+ });
1343
+ }
1344
+ });
1326
1345
  }
1327
- this.hls.trigger(Events.BUFFER_CODECS, tracks);
1328
- // loop through tracks that are going to be provided to bufferController
1329
- Object.keys(tracks).forEach((trackName) => {
1330
- const track = tracks[trackName];
1331
- const initSegment = track.initSegment;
1332
- if (initSegment?.byteLength) {
1333
- this.hls.trigger(Events.BUFFER_APPENDING, {
1334
- type: trackName as SourceBufferName,
1335
- data: initSegment,
1336
- frag,
1337
- part: null,
1338
- chunkMeta,
1339
- parent: frag.type,
1340
- });
1341
- }
1342
- });
1343
1346
  // trigger handler right now
1344
1347
  this.tickImmediate();
1345
1348
  }
@@ -1351,6 +1354,15 @@ export default class StreamController
1351
1354
  );
1352
1355
  }
1353
1356
 
1357
+ public get maxBufferLength(): number {
1358
+ const { levels, level } = this;
1359
+ const levelInfo = levels?.[level];
1360
+ if (!levelInfo) {
1361
+ return this.config.maxBufferLength;
1362
+ }
1363
+ return this.getMaxBufferLength(levelInfo.maxBitrate);
1364
+ }
1365
+
1354
1366
  private backtrack(frag: Fragment) {
1355
1367
  this.couldBacktrack = true;
1356
1368
  // Causes findFragments to backtrack through fragments to find the keyframe
@@ -1415,26 +1427,31 @@ export default class StreamController
1415
1427
  }
1416
1428
 
1417
1429
  get currentFrag(): Fragment | null {
1418
- const media = this.media;
1419
- if (media) {
1420
- return this.fragPlaying || this.getAppendedFrag(media.currentTime);
1430
+ if (this.fragPlaying) {
1431
+ return this.fragPlaying;
1432
+ }
1433
+ const currentTime = this.media?.currentTime || this.lastCurrentTime;
1434
+ if (Number.isFinite(currentTime)) {
1435
+ return this.getAppendedFrag(currentTime);
1421
1436
  }
1422
1437
  return null;
1423
1438
  }
1424
1439
 
1425
1440
  get currentProgramDateTime(): Date | null {
1426
- const media = this.media;
1427
- if (media) {
1428
- const currentTime = media.currentTime;
1429
- const frag = this.currentFrag;
1430
- if (
1431
- frag &&
1432
- Number.isFinite(currentTime) &&
1433
- Number.isFinite(frag.programDateTime)
1434
- ) {
1435
- const epocMs =
1436
- (frag.programDateTime as number) + (currentTime - frag.start) * 1000;
1437
- return new Date(epocMs);
1441
+ const currentTime = this.media?.currentTime || this.lastCurrentTime;
1442
+ if (Number.isFinite(currentTime)) {
1443
+ const details = this.getLevelDetails();
1444
+ const frag =
1445
+ this.currentFrag ||
1446
+ (details
1447
+ ? findFragmentByPTS(null, details.fragments, currentTime)
1448
+ : null);
1449
+ if (frag) {
1450
+ const programDateTime = frag.programDateTime;
1451
+ if (programDateTime !== null) {
1452
+ const epocMs = programDateTime + (currentTime - frag.start) * 1000;
1453
+ return new Date(epocMs);
1454
+ }
1438
1455
  }
1439
1456
  }
1440
1457
  return null;