hls.js 1.6.0-beta.1.0.canary.10764 → 1.6.0-beta.1.0.canary.10766

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -130,5 +130,5 @@
130
130
  "url-toolkit": "2.2.5",
131
131
  "wrangler": "3.87.0"
132
132
  },
133
- "version": "1.6.0-beta.1.0.canary.10764"
133
+ "version": "1.6.0-beta.1.0.canary.10766"
134
134
  }
@@ -795,14 +795,15 @@ class AbrController extends Logger implements AbrComponentAPI {
795
795
  | undefined;
796
796
  if (
797
797
  typeof mediaCapabilities?.decodingInfo === 'function' &&
798
- requiresMediaCapabilitiesDecodingInfo(
798
+ (requiresMediaCapabilitiesDecodingInfo(
799
799
  levelInfo,
800
800
  audioTracksByGroup,
801
801
  currentVideoRange,
802
802
  currentFrameRate,
803
803
  currentBw,
804
804
  audioPreference,
805
- )
805
+ ) ||
806
+ levelInfo.videoCodec?.substring(0, 4) === 'hvc1') // Force media capabilities check for HEVC to avoid failure on Windows
806
807
  ) {
807
808
  levelInfo.supportedPromise = getMediaDecodingInfoPromise(
808
809
  levelInfo,
@@ -831,6 +832,9 @@ class AbrController extends Logger implements AbrComponentAPI {
831
832
  if (index > -1 && levels.length > 1) {
832
833
  this.log(`Removing unsupported level ${index}`);
833
834
  this.hls.removeLevel(index);
835
+ if (this.hls.loadLevel === -1) {
836
+ this.hls.nextLoadLevel = 0;
837
+ }
834
838
  }
835
839
  }
836
840
  });
@@ -5,7 +5,7 @@ import ChunkCache from '../demux/chunk-cache';
5
5
  import TransmuxerInterface from '../demux/transmuxer-interface';
6
6
  import { ErrorDetails } from '../errors';
7
7
  import { Events } from '../events';
8
- import { ElementaryStreamTypes } from '../loader/fragment';
8
+ import { ElementaryStreamTypes, isMediaFragment } from '../loader/fragment';
9
9
  import { Level } from '../types/level';
10
10
  import { PlaylistContextType, PlaylistLevelType } from '../types/loader';
11
11
  import { ChunkMetadata } from '../types/transmuxer';
@@ -409,8 +409,8 @@ class AudioStreamController
409
409
  if (
410
410
  this.startFragRequested &&
411
411
  mainFragLoading &&
412
- mainFragLoading.sn !== 'initSegment' &&
413
- frag.sn !== 'initSegment' &&
412
+ isMediaFragment(mainFragLoading) &&
413
+ isMediaFragment(frag) &&
414
414
  !frag.endList &&
415
415
  (!trackDetails.live ||
416
416
  (!this.loadingParts && targetBufferTime < this.hls.liveSyncPosition!))
@@ -694,7 +694,7 @@ class AudioStreamController
694
694
  private onFragLoading(event: Events.FRAG_LOADING, data: FragLoadingData) {
695
695
  if (
696
696
  data.frag.type === PlaylistLevelType.MAIN &&
697
- data.frag.sn !== 'initSegment'
697
+ isMediaFragment(data.frag)
698
698
  ) {
699
699
  this.mainFragLoading = data;
700
700
  if (this.state === State.IDLE) {
@@ -722,8 +722,8 @@ class AudioStreamController
722
722
  );
723
723
  return;
724
724
  }
725
- if (frag.sn !== 'initSegment') {
726
- this.fragPrevious = frag as MediaFragment;
725
+ if (isMediaFragment(frag)) {
726
+ this.fragPrevious = frag;
727
727
  const track = this.switchingTrack;
728
728
  if (track) {
729
729
  this.bufferedTrack = track;
@@ -962,7 +962,7 @@ class AudioStreamController
962
962
  fragState === FragmentState.NOT_LOADED ||
963
963
  fragState === FragmentState.PARTIAL
964
964
  ) {
965
- if (frag.sn === 'initSegment') {
965
+ if (!isMediaFragment(frag)) {
966
966
  this._loadInitSegment(frag, track);
967
967
  } else if (track.details?.live && !this.initPTS[frag.cc]) {
968
968
  this.log(
@@ -434,6 +434,7 @@ class AudioTrackController extends BasePlaylistController {
434
434
  id,
435
435
  groupId,
436
436
  deliveryDirectives: hlsUrlParameters || null,
437
+ track: audioTrack,
437
438
  });
438
439
  }
439
440
  }
@@ -1,4 +1,4 @@
1
- import { NetworkErrorAction } from './error-controller';
1
+ import { ErrorActionFlags, NetworkErrorAction } from './error-controller';
2
2
  import {
3
3
  findFragmentByPDT,
4
4
  findFragmentByPTS,
@@ -8,6 +8,12 @@ import { FragmentState } from './fragment-tracker';
8
8
  import Decrypter from '../crypt/decrypter';
9
9
  import { ErrorDetails, ErrorTypes } from '../errors';
10
10
  import { Events } from '../events';
11
+ import {
12
+ type Fragment,
13
+ isMediaFragment,
14
+ type MediaFragment,
15
+ type Part,
16
+ } from '../loader/fragment';
11
17
  import FragmentLoader from '../loader/fragment-loader';
12
18
  import TaskLoop from '../task-loop';
13
19
  import { PlaylistLevelType } from '../types/loader';
@@ -31,7 +37,6 @@ import type { FragmentTracker } from './fragment-tracker';
31
37
  import type { HlsConfig } from '../config';
32
38
  import type TransmuxerInterface from '../demux/transmuxer-interface';
33
39
  import type Hls from '../hls';
34
- import type { Fragment, MediaFragment, Part } from '../loader/fragment';
35
40
  import type {
36
41
  FragmentLoadProgressCallback,
37
42
  LoadError,
@@ -714,7 +719,7 @@ export default class BaseStreamController
714
719
  : '(detached)'
715
720
  })`,
716
721
  );
717
- if (frag.sn !== 'initSegment') {
722
+ if (isMediaFragment(frag)) {
718
723
  if (frag.type !== PlaylistLevelType.SUBTITLE) {
719
724
  const el = frag.elementaryStreams;
720
725
  if (!Object.keys(el).some((type) => !!el[type])) {
@@ -803,7 +808,7 @@ export default class BaseStreamController
803
808
 
804
809
  const fragPrevious = this.fragPrevious;
805
810
  if (
806
- frag.sn !== 'initSegment' &&
811
+ isMediaFragment(frag) &&
807
812
  (!fragPrevious || frag.sn !== fragPrevious.sn)
808
813
  ) {
809
814
  const shouldLoadParts = this.shouldLoadParts(level.details, frag.end);
@@ -817,7 +822,7 @@ export default class BaseStreamController
817
822
  }
818
823
  }
819
824
  targetBufferTime = Math.max(frag.start, targetBufferTime || 0);
820
- if (this.loadingParts && frag.sn !== 'initSegment') {
825
+ if (this.loadingParts && isMediaFragment(frag)) {
821
826
  const partList = details.partList;
822
827
  if (partList && progressCallback) {
823
828
  if (targetBufferTime > frag.end && details.fragmentHint) {
@@ -885,7 +890,7 @@ export default class BaseStreamController
885
890
  }
886
891
  }
887
892
 
888
- if (frag.sn !== 'initSegment' && this.loadingParts) {
893
+ if (isMediaFragment(frag) && this.loadingParts) {
889
894
  this.log(
890
895
  `LL-Part loading OFF after next part miss @${targetBufferTime.toFixed(
891
896
  2,
@@ -1661,7 +1666,7 @@ export default class BaseStreamController
1661
1666
  }
1662
1667
 
1663
1668
  private handleFragLoadAborted(frag: Fragment, part: Part | undefined) {
1664
- if (this.transmuxer && frag.sn !== 'initSegment' && frag.stats.aborted) {
1669
+ if (this.transmuxer && isMediaFragment(frag) && frag.stats.aborted) {
1665
1670
  this.warn(
1666
1671
  `Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${
1667
1672
  frag.level
@@ -1708,12 +1713,18 @@ export default class BaseStreamController
1708
1713
  }
1709
1714
  // keep retrying until the limit will be reached
1710
1715
  const errorAction = data.errorAction;
1711
- const { action, retryCount = 0, retryConfig } = errorAction || {};
1712
- if (
1713
- errorAction &&
1714
- action === NetworkErrorAction.RetryRequest &&
1715
- retryConfig
1716
- ) {
1716
+ const { action, flags, retryCount = 0, retryConfig } = errorAction || {};
1717
+ const couldRetry = !!errorAction && !!retryConfig;
1718
+ const retry = couldRetry && action === NetworkErrorAction.RetryRequest;
1719
+ const noAlternate =
1720
+ couldRetry &&
1721
+ !errorAction.resolved &&
1722
+ flags === ErrorActionFlags.MoveAllAlternatesMatchingHost;
1723
+ if (!retry && noAlternate && isMediaFragment(frag) && !frag.endList) {
1724
+ this.resetFragmentErrors(filterType);
1725
+ this.treatAsGap(frag);
1726
+ errorAction.resolved = true;
1727
+ } else if ((retry || noAlternate) && retryCount < retryConfig.maxNumRetry) {
1717
1728
  this.resetStartWhenNotLoaded(this.levelLastLoaded);
1718
1729
  const delay = getRetryDelay(retryConfig, retryCount);
1719
1730
  this.warn(
@@ -1742,9 +1753,7 @@ export default class BaseStreamController
1742
1753
  );
1743
1754
  return;
1744
1755
  }
1745
- } else if (
1746
- errorAction?.action === NetworkErrorAction.SendAlternateToPenaltyBox
1747
- ) {
1756
+ } else if (action === NetworkErrorAction.SendAlternateToPenaltyBox) {
1748
1757
  this.state = State.WAITING_LEVEL;
1749
1758
  } else {
1750
1759
  this.state = State.ERROR;
@@ -1925,10 +1934,7 @@ export default class BaseStreamController
1925
1934
  );
1926
1935
  if (level.fragmentError === 0) {
1927
1936
  // Mark and track the odd empty segment as a gap to avoid reloading
1928
- level.fragmentError++;
1929
- frag.gap = true;
1930
- this.fragmentTracker.removeFragment(frag);
1931
- this.fragmentTracker.fragBuffered(frag, true);
1937
+ this.treatAsGap(frag, level);
1932
1938
  }
1933
1939
  this.warn(error.message);
1934
1940
  this.hls.trigger(Events.ERROR, {
@@ -1970,6 +1976,15 @@ export default class BaseStreamController
1970
1976
  )}]${part && frag.type === 'main' ? 'INDEPENDENT=' + (part.independent ? 'YES' : 'NO') : ''}`;
1971
1977
  }
1972
1978
 
1979
+ private treatAsGap(frag: MediaFragment, level?: Level) {
1980
+ if (level) {
1981
+ level.fragmentError++;
1982
+ }
1983
+ frag.gap = true;
1984
+ this.fragmentTracker.removeFragment(frag);
1985
+ this.fragmentTracker.fragBuffered(frag, true);
1986
+ }
1987
+
1973
1988
  protected resetTransmuxer() {
1974
1989
  this.transmuxer?.reset();
1975
1990
  }
@@ -601,7 +601,7 @@ export default class LevelController extends BasePlaylistController {
601
601
 
602
602
  protected onLevelLoaded(event: Events.LEVEL_LOADED, data: LevelLoadedData) {
603
603
  const { level, details } = data;
604
- const curLevel = this._levels[level];
604
+ const curLevel = data.levelInfo;
605
605
 
606
606
  if (!curLevel) {
607
607
  this.warn(`Invalid level index ${level}`);
@@ -612,7 +612,7 @@ export default class LevelController extends BasePlaylistController {
612
612
  }
613
613
 
614
614
  // only process level loaded events matching with expected level
615
- if (level === this.currentLevelIndex) {
615
+ if (curLevel === this.currentLevel) {
616
616
  // reset level load error counter on successful level loaded only if there is no issues with fragments
617
617
  if (curLevel.fragmentError === 0) {
618
618
  curLevel.loadError = 0;
@@ -665,6 +665,7 @@ export default class LevelController extends BasePlaylistController {
665
665
  this.hls.trigger(Events.LEVEL_LOADING, {
666
666
  url,
667
667
  level: currentLevelIndex,
668
+ levelInfo: currentLevel,
668
669
  pathwayId: currentLevel.attrs['PATHWAY-ID'],
669
670
  id: 0, // Deprecated Level urlId
670
671
  deliveryDirectives: hlsUrlParameters || null,
@@ -6,7 +6,7 @@ import TransmuxerInterface from '../demux/transmuxer-interface';
6
6
  import { ErrorDetails } from '../errors';
7
7
  import { Events } from '../events';
8
8
  import { changeTypeSupported } from '../is-supported';
9
- import { ElementaryStreamTypes } from '../loader/fragment';
9
+ import { ElementaryStreamTypes, isMediaFragment } from '../loader/fragment';
10
10
  import { PlaylistContextType, PlaylistLevelType } from '../types/loader';
11
11
  import { ChunkMetadata } from '../types/transmuxer';
12
12
  import { BufferHelper } from '../utils/buffer-helper';
@@ -319,7 +319,7 @@ export default class StreamController
319
319
  this.couldBacktrack &&
320
320
  !this.fragPrevious &&
321
321
  frag &&
322
- frag.sn !== 'initSegment' &&
322
+ isMediaFragment(frag) &&
323
323
  this.fragmentTracker.getState(frag) !== FragmentState.OK
324
324
  ) {
325
325
  const backtrackSn = (this.backtrackFragment ?? frag).sn as number;
@@ -378,7 +378,7 @@ export default class StreamController
378
378
  fragState === FragmentState.NOT_LOADED ||
379
379
  fragState === FragmentState.PARTIAL
380
380
  ) {
381
- if (frag.sn === 'initSegment') {
381
+ if (!isMediaFragment(frag)) {
382
382
  this._loadInitSegment(frag, level);
383
383
  } else if (this.bitrateTest) {
384
384
  this.log(
@@ -646,7 +646,7 @@ export default class StreamController
646
646
  if (!levels || this.state !== State.IDLE) {
647
647
  return;
648
648
  }
649
- const level = levels[data.level];
649
+ const level = data.levelInfo;
650
650
  if (
651
651
  !level.details ||
652
652
  (level.details.live && this.levelLastLoaded !== level) ||
@@ -674,7 +674,7 @@ export default class StreamController
674
674
  }, cc [${newDetails.startCC}, ${newDetails.endCC}] duration:${duration}`,
675
675
  );
676
676
 
677
- const curLevel = levels[newLevelId];
677
+ const curLevel = data.levelInfo;
678
678
  const fragCurrent = this.fragCurrent;
679
679
  if (
680
680
  fragCurrent &&
@@ -963,8 +963,8 @@ export default class StreamController
963
963
  this.fragLastKbps = Math.round(
964
964
  (8 * stats.total) / (stats.buffering.end - stats.loading.first),
965
965
  );
966
- if (frag.sn !== 'initSegment') {
967
- this.fragPrevious = frag as MediaFragment;
966
+ if (isMediaFragment(frag)) {
967
+ this.fragPrevious = frag;
968
968
  }
969
969
  this.fragBufferedComplete(frag, part);
970
970
  }
@@ -3,6 +3,11 @@ import { findFragmentByPTS } from './fragment-finders';
3
3
  import { FragmentState } from './fragment-tracker';
4
4
  import { ErrorDetails, ErrorTypes } from '../errors';
5
5
  import { Events } from '../events';
6
+ import {
7
+ type Fragment,
8
+ isMediaFragment,
9
+ type MediaFragment,
10
+ } from '../loader/fragment';
6
11
  import { Level } from '../types/level';
7
12
  import { PlaylistLevelType } from '../types/loader';
8
13
  import { BufferHelper } from '../utils/buffer-helper';
@@ -15,7 +20,6 @@ import { addSliding } from '../utils/level-helper';
15
20
  import { subtitleOptionsIdentical } from '../utils/media-option-attributes';
16
21
  import type Hls from '../hls';
17
22
  import type { FragmentTracker } from './fragment-tracker';
18
- import type { Fragment, MediaFragment } from '../loader/fragment';
19
23
  import type KeyLoader from '../loader/key-loader';
20
24
  import type { LevelDetails } from '../loader/level-details';
21
25
  import type { NetworkComponentAPI } from '../types/component-api';
@@ -126,8 +130,8 @@ export class SubtitleStreamController
126
130
  data: SubtitleFragProcessed,
127
131
  ) {
128
132
  const { frag, success } = data;
129
- if (frag.sn !== 'initSegment') {
130
- this.fragPrevious = frag as MediaFragment;
133
+ if (isMediaFragment(frag)) {
134
+ this.fragPrevious = frag;
131
135
  }
132
136
  this.state = State.IDLE;
133
137
  if (!success) {
@@ -466,7 +470,7 @@ export class SubtitleStreamController
466
470
  return;
467
471
  }
468
472
  foundFrag = this.mapToInitFragWhenRequired(foundFrag) as Fragment;
469
- if (foundFrag.sn !== 'initSegment') {
473
+ if (isMediaFragment(foundFrag)) {
470
474
  // Load earlier fragment in same discontinuity to make up for misaligned playlists and cues that extend beyond end of segment
471
475
  const curSNIdx = foundFrag.sn - trackDetails.startSN;
472
476
  const prevFrag = fragments[curSNIdx - 1];
@@ -492,7 +496,7 @@ export class SubtitleStreamController
492
496
  level: Level,
493
497
  targetBufferTime: number,
494
498
  ) {
495
- if (frag.sn === 'initSegment') {
499
+ if (!isMediaFragment(frag)) {
496
500
  this._loadInitSegment(frag, level);
497
501
  } else {
498
502
  super.loadFragment(frag, level, targetBufferTime);
@@ -453,6 +453,7 @@ class SubtitleTrackController extends BasePlaylistController {
453
453
  id,
454
454
  groupId,
455
455
  deliveryDirectives: hlsUrlParameters || null,
456
+ track: currentTrack,
456
457
  });
457
458
  }
458
459
  }
@@ -152,6 +152,10 @@ export type MediaFragmentRef = {
152
152
  programDateTime: number | null;
153
153
  };
154
154
 
155
+ export function isMediaFragment(frag: Fragment): frag is MediaFragment {
156
+ return frag.sn !== 'initSegment';
157
+ }
158
+
155
159
  /**
156
160
  * Object representing parsed data from an HLS Segment. Found in {@link hls.js#LevelDetails.fragments}.
157
161
  */
@@ -323,7 +327,7 @@ export class Fragment extends BaseSegment {
323
327
  }
324
328
 
325
329
  get ref(): MediaFragmentRef | null {
326
- if (this.sn === 'initSegment') {
330
+ if (!isMediaFragment(this)) {
327
331
  return null;
328
332
  }
329
333
  if (!this._ref) {
@@ -18,10 +18,11 @@ import type { NetworkComponentAPI } from '../types/component-api';
18
18
  import type {
19
19
  ErrorData,
20
20
  LevelLoadingData,
21
+ LevelsUpdatedData,
21
22
  ManifestLoadingData,
22
23
  TrackLoadingData,
23
24
  } from '../types/events';
24
- import type { LevelParsed, VariableMap } from '../types/level';
25
+ import type { Level, LevelParsed, VariableMap } from '../types/level';
25
26
  import type {
26
27
  Loader,
27
28
  LoaderCallbacks,
@@ -31,7 +32,7 @@ import type {
31
32
  LoaderStats,
32
33
  PlaylistLoaderContext,
33
34
  } from '../types/loader';
34
- import type { MediaAttributes } from '../types/media-playlist';
35
+ import type { MediaAttributes, MediaPlaylist } from '../types/media-playlist';
35
36
 
36
37
  function mapContextToLevelType(
37
38
  context: PlaylistLoaderContext,
@@ -86,6 +87,7 @@ class PlaylistLoader implements NetworkComponentAPI {
86
87
  hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
87
88
  hls.on(Events.AUDIO_TRACK_LOADING, this.onAudioTrackLoading, this);
88
89
  hls.on(Events.SUBTITLE_TRACK_LOADING, this.onSubtitleTrackLoading, this);
90
+ hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
89
91
  }
90
92
 
91
93
  private unregisterListeners() {
@@ -94,6 +96,7 @@ class PlaylistLoader implements NetworkComponentAPI {
94
96
  hls.off(Events.LEVEL_LOADING, this.onLevelLoading, this);
95
97
  hls.off(Events.AUDIO_TRACK_LOADING, this.onAudioTrackLoading, this);
96
98
  hls.off(Events.SUBTITLE_TRACK_LOADING, this.onSubtitleTrackLoading, this);
99
+ hls.off(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
97
100
  }
98
101
 
99
102
  /**
@@ -118,7 +121,7 @@ class PlaylistLoader implements NetworkComponentAPI {
118
121
  return this.loaders[context.type];
119
122
  }
120
123
 
121
- private resetInternalLoader(contextType): void {
124
+ private resetInternalLoader(contextType: PlaylistContextType): void {
122
125
  if (this.loaders[contextType]) {
123
126
  delete this.loaders[contextType];
124
127
  }
@@ -134,7 +137,7 @@ class PlaylistLoader implements NetworkComponentAPI {
134
137
  loader.destroy();
135
138
  }
136
139
 
137
- this.resetInternalLoader(contextType);
140
+ this.resetInternalLoader(contextType as PlaylistContextType);
138
141
  }
139
142
  }
140
143
 
@@ -157,11 +160,12 @@ class PlaylistLoader implements NetworkComponentAPI {
157
160
  type: PlaylistContextType.MANIFEST,
158
161
  url,
159
162
  deliveryDirectives: null,
163
+ levelOrTrack: null,
160
164
  });
161
165
  }
162
166
 
163
167
  private onLevelLoading(event: Events.LEVEL_LOADING, data: LevelLoadingData) {
164
- const { id, level, pathwayId, url, deliveryDirectives } = data;
168
+ const { id, level, pathwayId, url, deliveryDirectives, levelInfo } = data;
165
169
  this.load({
166
170
  id,
167
171
  level,
@@ -170,6 +174,7 @@ class PlaylistLoader implements NetworkComponentAPI {
170
174
  type: PlaylistContextType.LEVEL,
171
175
  url,
172
176
  deliveryDirectives,
177
+ levelOrTrack: levelInfo,
173
178
  });
174
179
  }
175
180
 
@@ -177,7 +182,7 @@ class PlaylistLoader implements NetworkComponentAPI {
177
182
  event: Events.AUDIO_TRACK_LOADING,
178
183
  data: TrackLoadingData,
179
184
  ) {
180
- const { id, groupId, url, deliveryDirectives } = data;
185
+ const { id, groupId, url, deliveryDirectives, track } = data;
181
186
  this.load({
182
187
  id,
183
188
  groupId,
@@ -186,6 +191,7 @@ class PlaylistLoader implements NetworkComponentAPI {
186
191
  type: PlaylistContextType.AUDIO_TRACK,
187
192
  url,
188
193
  deliveryDirectives,
194
+ levelOrTrack: track,
189
195
  });
190
196
  }
191
197
 
@@ -193,7 +199,7 @@ class PlaylistLoader implements NetworkComponentAPI {
193
199
  event: Events.SUBTITLE_TRACK_LOADING,
194
200
  data: TrackLoadingData,
195
201
  ) {
196
- const { id, groupId, url, deliveryDirectives } = data;
202
+ const { id, groupId, url, deliveryDirectives, track } = data;
197
203
  this.load({
198
204
  id,
199
205
  groupId,
@@ -202,9 +208,30 @@ class PlaylistLoader implements NetworkComponentAPI {
202
208
  type: PlaylistContextType.SUBTITLE_TRACK,
203
209
  url,
204
210
  deliveryDirectives,
211
+ levelOrTrack: track,
205
212
  });
206
213
  }
207
214
 
215
+ private onLevelsUpdated(
216
+ event: Events.LEVELS_UPDATED,
217
+ data: LevelsUpdatedData,
218
+ ) {
219
+ // abort and delete loader of removed levels
220
+ const loader = this.loaders[PlaylistContextType.LEVEL];
221
+ if (loader) {
222
+ const context = loader.context;
223
+ if (
224
+ context &&
225
+ !data.levels.some(
226
+ (lvl) => lvl === (context as PlaylistLoaderContext).levelOrTrack,
227
+ )
228
+ ) {
229
+ loader.abort();
230
+ delete this.loaders[PlaylistContextType.LEVEL];
231
+ }
232
+ }
233
+ }
234
+
208
235
  private load(context: PlaylistLoaderContext): void {
209
236
  const config = this.hls.config;
210
237
 
@@ -217,7 +244,7 @@ class PlaylistLoader implements NetworkComponentAPI {
217
244
  if (
218
245
  loaderContext &&
219
246
  loaderContext.url === context.url &&
220
- loaderContext.level === context.level
247
+ loaderContext.levelOrTrack === context.levelOrTrack
221
248
  ) {
222
249
  // same URL can't overlap
223
250
  this.hls.logger.trace('[playlist-loader]: playlist request ongoing');
@@ -680,6 +707,7 @@ class PlaylistLoader implements NetworkComponentAPI {
680
707
  case PlaylistContextType.LEVEL:
681
708
  hls.trigger(Events.LEVEL_LOADED, {
682
709
  details: levelDetails,
710
+ levelInfo: (context.levelOrTrack as Level) || hls.levels[0],
683
711
  level: levelIndex || 0,
684
712
  id: id || 0,
685
713
  stats,
@@ -690,6 +718,7 @@ class PlaylistLoader implements NetworkComponentAPI {
690
718
  case PlaylistContextType.AUDIO_TRACK:
691
719
  hls.trigger(Events.AUDIO_TRACK_LOADED, {
692
720
  details: levelDetails,
721
+ track: context.levelOrTrack as MediaPlaylist,
693
722
  id: id || 0,
694
723
  groupId: groupId || '',
695
724
  stats,
@@ -700,6 +729,7 @@ class PlaylistLoader implements NetworkComponentAPI {
700
729
  case PlaylistContextType.SUBTITLE_TRACK:
701
730
  hls.trigger(Events.SUBTITLE_TRACK_LOADED, {
702
731
  details: levelDetails,
732
+ track: context.levelOrTrack as MediaPlaylist,
703
733
  id: id || 0,
704
734
  groupId: groupId || '',
705
735
  stats,
@@ -188,6 +188,7 @@ export interface LevelSwitchedData {
188
188
  export interface TrackLoadingData {
189
189
  id: number;
190
190
  groupId: string;
191
+ track: MediaPlaylist;
191
192
  url: string;
192
193
  deliveryDirectives: HlsUrlParameters | null;
193
194
  }
@@ -195,6 +196,7 @@ export interface TrackLoadingData {
195
196
  export interface LevelLoadingData {
196
197
  id: number;
197
198
  level: number;
199
+ levelInfo: Level;
198
200
  pathwayId: string | undefined;
199
201
  url: string;
200
202
  deliveryDirectives: HlsUrlParameters | null;
@@ -207,12 +209,14 @@ export interface TrackLoadedData {
207
209
  networkDetails: any;
208
210
  stats: LoaderStats;
209
211
  deliveryDirectives: HlsUrlParameters | null;
212
+ track: MediaPlaylist;
210
213
  }
211
214
 
212
215
  export interface LevelLoadedData {
213
216
  details: LevelDetails;
214
217
  id: number;
215
218
  level: number;
219
+ levelInfo: Level;
216
220
  networkDetails: any;
217
221
  stats: LoaderStats;
218
222
  deliveryDirectives: HlsUrlParameters | null;
@@ -1,5 +1,6 @@
1
1
  import type { LoaderConfig } from '../config';
2
- import type { HlsUrlParameters } from './level';
2
+ import type { HlsUrlParameters, Level } from './level';
3
+ import type { MediaPlaylist } from './media-playlist';
3
4
  import type { Fragment } from '../loader/fragment';
4
5
  import type { Part } from '../loader/fragment';
5
6
  import type { KeyLoaderInfo } from '../loader/key-loader';
@@ -191,4 +192,6 @@ export interface PlaylistLoaderContext extends LoaderContext {
191
192
  levelDetails?: LevelDetails;
192
193
  // Blocking playlist request delivery directives (or null id none were added to playlist url
193
194
  deliveryDirectives: HlsUrlParameters | null;
195
+ // Reference to level or track object in hls.levels, hls.allAudioTracks, or hls.allSubtitleTracks (null when loading MVP)
196
+ levelOrTrack: Level | MediaPlaylist | null;
194
197
  }