hls.js 1.5.12-0.canary.10366 → 1.5.12-0.canary.10367

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.
@@ -13,7 +13,7 @@ import type Hls from '../hls';
13
13
  import type { RetryConfig } from '../config';
14
14
  import type { NetworkComponentAPI } from '../types/component-api';
15
15
  import type { ErrorData } from '../types/events';
16
- import type { Fragment } from '../loader/fragment';
16
+ import type { Fragment, MediaFragment } from '../loader/fragment';
17
17
  import type { LevelDetails } from '../loader/level-details';
18
18
 
19
19
  export const enum NetworkErrorAction {
@@ -127,10 +127,7 @@ export default class ErrorController
127
127
  case ErrorDetails.FRAG_PARSING_ERROR:
128
128
  // ignore empty segment errors marked as gap
129
129
  if (data.frag?.gap) {
130
- data.errorAction = {
131
- action: NetworkErrorAction.DoNothing,
132
- flags: ErrorActionFlags.None,
133
- };
130
+ data.errorAction = createDoNothingErrorAction();
134
131
  return;
135
132
  }
136
133
  // falls through
@@ -218,10 +215,13 @@ export default class ErrorController
218
215
  case ErrorDetails.BUFFER_ADD_CODEC_ERROR:
219
216
  case ErrorDetails.REMUX_ALLOC_ERROR:
220
217
  case ErrorDetails.BUFFER_APPEND_ERROR:
221
- data.errorAction = this.getLevelSwitchAction(
222
- data,
223
- data.level ?? hls.loadLevel,
224
- );
218
+ // Buffer-controller can set errorAction when append errors can be ignored or resolved locally
219
+ if (!data.errorAction) {
220
+ data.errorAction = this.getLevelSwitchAction(
221
+ data,
222
+ data.level ?? hls.loadLevel,
223
+ );
224
+ }
225
225
  return;
226
226
  case ErrorDetails.INTERNAL_EXCEPTION:
227
227
  case ErrorDetails.BUFFER_APPENDING_ERROR:
@@ -230,10 +230,7 @@ export default class ErrorController
230
230
  case ErrorDetails.BUFFER_STALLED_ERROR:
231
231
  case ErrorDetails.BUFFER_SEEK_OVER_HOLE:
232
232
  case ErrorDetails.BUFFER_NUDGE_ON_STALL:
233
- data.errorAction = {
234
- action: NetworkErrorAction.DoNothing,
235
- flags: ErrorActionFlags.None,
236
- };
233
+ data.errorAction = createDoNothingErrorAction();
237
234
  return;
238
235
  }
239
236
 
@@ -387,7 +384,7 @@ export default class ErrorController
387
384
  const levelDetails = levels[candidate].details;
388
385
  if (levelDetails) {
389
386
  const fragCandidate = findFragmentByPTS(
390
- data.frag,
387
+ data.frag as MediaFragment,
391
388
  levelDetails.fragments,
392
389
  data.frag.start,
393
390
  );
@@ -511,3 +508,14 @@ export default class ErrorController
511
508
  }
512
509
  }
513
510
  }
511
+
512
+ export function createDoNothingErrorAction(resolved?: boolean): IErrorAction {
513
+ const errorAction: IErrorAction = {
514
+ action: NetworkErrorAction.DoNothing,
515
+ flags: ErrorActionFlags.None,
516
+ };
517
+ if (resolved) {
518
+ errorAction.resolved = true;
519
+ }
520
+ return errorAction;
521
+ }
@@ -1,5 +1,6 @@
1
1
  import BinarySearch from '../utils/binary-search';
2
2
  import type { Fragment, MediaFragment } from '../loader/fragment';
3
+ import type { LevelDetails } from '../loader/level-details';
3
4
 
4
5
  /**
5
6
  * Returns first fragment whose endPdt value exceeds the given PDT, or null.
@@ -54,7 +55,7 @@ export function findFragmentByPDT(
54
55
  * @returns a matching fragment or null
55
56
  */
56
57
  export function findFragmentByPTS(
57
- fragPrevious: Fragment | null,
58
+ fragPrevious: MediaFragment | null,
58
59
  fragments: MediaFragment[],
59
60
  bufferEnd: number = 0,
60
61
  maxFragLookUpTolerance: number = 0,
@@ -62,13 +63,19 @@ export function findFragmentByPTS(
62
63
  ): MediaFragment | null {
63
64
  let fragNext: MediaFragment | null = null;
64
65
  if (fragPrevious) {
65
- fragNext =
66
- fragments[(fragPrevious.sn as number) - fragments[0].sn + 1] || null;
66
+ fragNext = fragments[1 + fragPrevious.sn - fragments[0].sn] || null;
67
67
  // check for buffer-end rounding error
68
68
  const bufferEdgeError = (fragPrevious.endDTS as number) - bufferEnd;
69
69
  if (bufferEdgeError > 0 && bufferEdgeError < 0.0000015) {
70
70
  bufferEnd += 0.0000015;
71
71
  }
72
+ if (
73
+ fragNext &&
74
+ fragPrevious.level !== fragNext.level &&
75
+ fragNext.end <= fragPrevious.end
76
+ ) {
77
+ fragNext = fragments[2 + fragPrevious.sn - fragments[0].sn] || null;
78
+ }
72
79
  } else if (bufferEnd === 0 && fragments[0].start === 0) {
73
80
  fragNext = fragments[0];
74
81
  }
@@ -214,3 +221,26 @@ export function findFragWithCC(
214
221
  }
215
222
  });
216
223
  }
224
+
225
+ export function findNearestWithCC(
226
+ details: LevelDetails | undefined,
227
+ cc: number,
228
+ fragment: MediaFragment,
229
+ ): MediaFragment | null {
230
+ if (details) {
231
+ if (details.startCC <= cc && details.endCC >= cc) {
232
+ const start = fragment.start;
233
+ const end = fragment.end;
234
+ return BinarySearch.search(details.fragments, (candidate) => {
235
+ if (candidate.cc < cc || candidate.end <= start) {
236
+ return 1;
237
+ } else if (candidate.cc > cc || candidate.start >= end) {
238
+ return -1;
239
+ } else {
240
+ return 0;
241
+ }
242
+ });
243
+ }
244
+ }
245
+ return null;
246
+ }
@@ -47,6 +47,7 @@ export class FragmentTracker implements ComponentAPI {
47
47
 
48
48
  private _registerListeners() {
49
49
  const { hls } = this;
50
+ hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
50
51
  hls.on(Events.BUFFER_APPENDED, this.onBufferAppended, this);
51
52
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
52
53
  hls.on(Events.FRAG_LOADED, this.onFragLoaded, this);
@@ -54,6 +55,7 @@ export class FragmentTracker implements ComponentAPI {
54
55
 
55
56
  private _unregisterListeners() {
56
57
  const { hls } = this;
58
+ hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
57
59
  hls.off(Events.BUFFER_APPENDED, this.onBufferAppended, this);
58
60
  hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
59
61
  hls.off(Events.FRAG_LOADED, this.onFragLoaded, this);
@@ -143,6 +145,7 @@ export class FragmentTracker implements ComponentAPI {
143
145
  timeRange: TimeRanges,
144
146
  playlistType: PlaylistLevelType,
145
147
  appendedPart?: Part | null,
148
+ removeAppending?: boolean,
146
149
  ) {
147
150
  if (this.timeRanges) {
148
151
  this.timeRanges[elementaryStream] = timeRange;
@@ -158,7 +161,10 @@ export class FragmentTracker implements ComponentAPI {
158
161
  if (appendedPartSn >= fragmentEntity.body.sn) {
159
162
  return;
160
163
  }
161
- if (!fragmentEntity.buffered && !fragmentEntity.loaded) {
164
+ if (
165
+ !fragmentEntity.buffered &&
166
+ (!fragmentEntity.loaded || removeAppending)
167
+ ) {
162
168
  if (fragmentEntity.body.type === playlistType) {
163
169
  this.removeFragment(fragmentEntity.body);
164
170
  }
@@ -168,6 +174,10 @@ export class FragmentTracker implements ComponentAPI {
168
174
  if (!esData) {
169
175
  return;
170
176
  }
177
+ if (esData.time.length === 0) {
178
+ this.removeFragment(fragmentEntity.body);
179
+ return;
180
+ }
171
181
  esData.time.some((time: FragmentTimeRange) => {
172
182
  const isNotBuffered = !this.isTimeBuffered(
173
183
  time.startPTS,
@@ -387,6 +397,10 @@ export class FragmentTracker implements ComponentAPI {
387
397
  return false;
388
398
  }
389
399
 
400
+ private onManifestLoading() {
401
+ this.removeAllFragments();
402
+ }
403
+
390
404
  private onFragLoaded(event: Events.FRAG_LOADED, data: FragLoadedData) {
391
405
  // don't track initsegment (for which sn is not a number)
392
406
  // don't track frags used for bitrateTest, they're irrelevant.
@@ -439,6 +453,21 @@ export class FragmentTracker implements ComponentAPI {
439
453
  return !!this.fragments[fragKey];
440
454
  }
441
455
 
456
+ public hasFragments(type?: PlaylistLevelType): boolean {
457
+ const { fragments } = this;
458
+ const keys = Object.keys(fragments);
459
+ if (!type) {
460
+ return keys.length > 0;
461
+ }
462
+ for (let i = keys.length; i--; ) {
463
+ const fragmentEntity = fragments[keys[i]];
464
+ if (fragmentEntity?.body.type === type) {
465
+ return true;
466
+ }
467
+ }
468
+ return false;
469
+ }
470
+
442
471
  public hasParts(type: PlaylistLevelType): boolean {
443
472
  return !!this.activePartLists[type]?.length;
444
473
  }
@@ -2,6 +2,7 @@ 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
8
  import {
@@ -16,15 +17,15 @@ import { ErrorDetails } from '../errors';
16
17
  import type { NetworkComponentAPI } from '../types/component-api';
17
18
  import type Hls from '../hls';
18
19
  import type { Level } from '../types/level';
19
- import type { LevelDetails } from '../loader/level-details';
20
20
  import type { FragmentTracker } from './fragment-tracker';
21
21
  import type KeyLoader from '../loader/key-loader';
22
22
  import type { TransmuxerResult } from '../types/transmuxer';
23
- import type { TrackSet } from '../types/track';
23
+ import type { Track, TrackSet } from '../types/track';
24
24
  import type { SourceBufferName } from '../types/buffer';
25
25
  import type {
26
26
  AudioTrackSwitchedData,
27
27
  AudioTrackSwitchingData,
28
+ BufferCodecsData,
28
29
  BufferCreatedData,
29
30
  BufferEOSData,
30
31
  BufferFlushedData,
@@ -364,7 +365,6 @@ export default class StreamController
364
365
  ) {
365
366
  // Check if fragment is not loaded
366
367
  const fragState = this.fragmentTracker.getState(frag);
367
- this.fragCurrent = frag;
368
368
  if (
369
369
  fragState === FragmentState.NOT_LOADED ||
370
370
  fragState === FragmentState.PARTIAL
@@ -377,7 +377,6 @@ export default class StreamController
377
377
  );
378
378
  this._loadBitrateTestFrag(frag, level);
379
379
  } else {
380
- this.startFragRequested = true;
381
380
  super.loadFragment(frag, level, targetBufferTime);
382
381
  }
383
382
  } else {
@@ -570,18 +569,14 @@ export default class StreamController
570
569
  };
571
570
 
572
571
  protected onManifestLoading() {
572
+ super.onManifestLoading();
573
573
  // reset buffer on manifest loading
574
574
  this.log('Trigger BUFFER_RESET');
575
575
  this.hls.trigger(Events.BUFFER_RESET, undefined);
576
- this.fragmentTracker.removeAllFragments();
577
576
  this.couldBacktrack = false;
578
- this.startPosition = this.lastCurrentTime = this.fragLastKbps = 0;
579
- this.levels =
580
- this.fragPlaying =
581
- this.backtrackFragment =
582
- this.levelLastLoaded =
583
- null;
584
- this.altAudio = this.audioOnly = this.startFragRequested = false;
577
+ this.fragLastKbps = 0;
578
+ this.fragPlaying = this.backtrackFragment = null;
579
+ this.altAudio = this.audioOnly = false;
585
580
  }
586
581
 
587
582
  private onManifestParsed(
@@ -705,7 +700,7 @@ export default class StreamController
705
700
  return;
706
701
  }
707
702
  const currentLevel = levels[frag.level];
708
- const details = currentLevel.details as LevelDetails;
703
+ const details = currentLevel.details;
709
704
  if (!details) {
710
705
  this.warn(
711
706
  `Dropping fragment ${frag.sn} of level ${frag.level} after level details were reset`,
@@ -878,7 +873,7 @@ export default class StreamController
878
873
  (8 * stats.total) / (stats.buffering.end - stats.loading.first),
879
874
  );
880
875
  if (frag.sn !== 'initSegment') {
881
- this.fragPrevious = frag;
876
+ this.fragPrevious = frag as MediaFragment;
882
877
  }
883
878
  this.fragBufferedComplete(frag, part);
884
879
  }
@@ -956,7 +951,7 @@ export default class StreamController
956
951
  // in that case, reset startFragRequested flag
957
952
  if (!this.loadedmetadata) {
958
953
  this.startFragRequested = false;
959
- this.nextLoadPosition = this.startPosition;
954
+ this.nextLoadPosition = this.lastCurrentTime;
960
955
  }
961
956
  this.tickImmediate();
962
957
  }
@@ -1068,7 +1063,7 @@ export default class StreamController
1068
1063
  }
1069
1064
 
1070
1065
  private _handleTransmuxComplete(transmuxResult: TransmuxerResult) {
1071
- const id = 'main';
1066
+ const id = this.playlistType;
1072
1067
  const { hls } = this;
1073
1068
  const { remuxResult, chunkMeta } = transmuxResult;
1074
1069
 
@@ -1308,6 +1303,7 @@ export default class StreamController
1308
1303
  currentLevel.audioCodec || ''
1309
1304
  }/${audio.codec}]`,
1310
1305
  );
1306
+ delete tracks.audiovideo;
1311
1307
  }
1312
1308
  if (video) {
1313
1309
  video.levelCodec = currentLevel.videoCodec;
@@ -1319,28 +1315,34 @@ export default class StreamController
1319
1315
  video.codec
1320
1316
  }]`,
1321
1317
  );
1318
+ delete tracks.audiovideo;
1322
1319
  }
1323
1320
  if (audiovideo) {
1324
1321
  this.log(
1325
1322
  `Init audiovideo buffer, container:${audiovideo.container}, codecs[level/parsed]=[${currentLevel.codecs}/${audiovideo.codec}]`,
1326
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
+ });
1327
1345
  }
1328
- this.hls.trigger(Events.BUFFER_CODECS, tracks);
1329
- // loop through tracks that are going to be provided to bufferController
1330
- Object.keys(tracks).forEach((trackName) => {
1331
- const track = tracks[trackName];
1332
- const initSegment = track.initSegment;
1333
- if (initSegment?.byteLength) {
1334
- this.hls.trigger(Events.BUFFER_APPENDING, {
1335
- type: trackName as SourceBufferName,
1336
- data: initSegment,
1337
- frag,
1338
- part: null,
1339
- chunkMeta,
1340
- parent: frag.type,
1341
- });
1342
- }
1343
- });
1344
1346
  // trigger handler right now
1345
1347
  this.tickImmediate();
1346
1348
  }
@@ -1425,26 +1427,31 @@ export default class StreamController
1425
1427
  }
1426
1428
 
1427
1429
  get currentFrag(): Fragment | null {
1428
- const media = this.media;
1429
- if (media) {
1430
- 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);
1431
1436
  }
1432
1437
  return null;
1433
1438
  }
1434
1439
 
1435
1440
  get currentProgramDateTime(): Date | null {
1436
- const media = this.media;
1437
- if (media) {
1438
- const currentTime = media.currentTime;
1439
- const frag = this.currentFrag;
1440
- if (
1441
- frag &&
1442
- Number.isFinite(currentTime) &&
1443
- Number.isFinite(frag.programDateTime)
1444
- ) {
1445
- const epocMs =
1446
- (frag.programDateTime as number) + (currentTime - frag.start) * 1000;
1447
- 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
+ }
1448
1455
  }
1449
1456
  }
1450
1457
  return null;
@@ -106,8 +106,8 @@ export class SubtitleStreamController
106
106
  }
107
107
 
108
108
  protected onManifestLoading() {
109
+ super.onManifestLoading();
109
110
  this.mainDetails = null;
110
- this.fragmentTracker.removeAllFragments();
111
111
  }
112
112
 
113
113
  protected onMediaDetaching(): void {
@@ -124,7 +124,9 @@ export class SubtitleStreamController
124
124
  data: SubtitleFragProcessed,
125
125
  ) {
126
126
  const { frag, success } = data;
127
- this.fragPrevious = frag;
127
+ if (frag.sn !== 'initSegment') {
128
+ this.fragPrevious = frag as MediaFragment;
129
+ }
128
130
  this.state = State.IDLE;
129
131
  if (!success) {
130
132
  return;
@@ -491,11 +493,9 @@ export class SubtitleStreamController
491
493
  level: Level,
492
494
  targetBufferTime: number,
493
495
  ) {
494
- this.fragCurrent = frag;
495
496
  if (frag.sn === 'initSegment') {
496
497
  this._loadInitSegment(frag, level);
497
498
  } else {
498
- this.startFragRequested = true;
499
499
  super.loadFragment(frag, level, targetBufferTime);
500
500
  }
501
501
  }
@@ -192,7 +192,7 @@ export class TimelineController implements ComponentAPI {
192
192
  { frag, id, initPTS, timescale }: InitPTSFoundData,
193
193
  ) {
194
194
  const { unparsedVttFrags } = this;
195
- if (id === 'main') {
195
+ if (id === PlaylistLevelType.MAIN) {
196
196
  this.initPTS[frag.cc] = { baseTime: initPTS, timescale };
197
197
  }
198
198
 
package/src/hls.ts CHANGED
@@ -232,7 +232,7 @@ export default class Hls implements HlsEventEmitter {
232
232
  new AudioStreamControllerClass(this, fragmentTracker, keyLoader),
233
233
  );
234
234
  }
235
- // subtitleTrackController must be defined before subtitleStreamController because the order of event handling is important
235
+ // Instantiate subtitleTrackController before SubtitleStreamController to receive level events first
236
236
  this.subtitleTrackController = this.createController(
237
237
  config.subtitleTrackController,
238
238
  networkControllers,
@@ -1,18 +1,17 @@
1
1
  import { ErrorTypes, ErrorDetails } from '../errors';
2
- import { Fragment } from './fragment';
3
- import type {
4
- Loader,
5
- LoaderConfiguration,
6
- FragmentLoaderContext,
7
- } from '../types/loader';
8
2
  import { getLoaderConfigWithoutReties } from '../utils/error-helper';
9
3
  import type { HlsConfig } from '../config';
10
- import type { BaseSegment, Part } from './fragment';
4
+ import type { BaseSegment, Fragment, Part } from './fragment';
11
5
  import type {
12
6
  ErrorData,
13
7
  FragLoadedData,
14
8
  PartsLoadedData,
15
9
  } from '../types/events';
10
+ import type {
11
+ Loader,
12
+ LoaderConfiguration,
13
+ FragmentLoaderContext,
14
+ } from '../types/loader';
16
15
 
17
16
  const MIN_CHUNK_SIZE = Math.pow(2, 17); // 128kb
18
17
 
@@ -77,13 +76,11 @@ export default class FragmentLoader {
77
76
  frag.gap = false;
78
77
  }
79
78
  }
80
- const loader =
81
- (this.loader =
82
- frag.loader =
83
- FragmentILoader
84
- ? new FragmentILoader(config)
85
- : (new DefaultILoader(config) as Loader<FragmentLoaderContext>));
79
+ const loader = (this.loader = FragmentILoader
80
+ ? new FragmentILoader(config)
81
+ : (new DefaultILoader(config) as Loader<FragmentLoaderContext>));
86
82
  const loaderContext = createLoaderContext(frag);
83
+ frag.loader = loader;
87
84
  const loadPolicy = getLoaderConfigWithoutReties(
88
85
  config.fragLoadPolicy.default,
89
86
  );
@@ -188,13 +185,11 @@ export default class FragmentLoader {
188
185
  reject(createGapLoadError(frag, part));
189
186
  return;
190
187
  }
191
- const loader =
192
- (this.loader =
193
- frag.loader =
194
- FragmentILoader
195
- ? new FragmentILoader(config)
196
- : (new DefaultILoader(config) as Loader<FragmentLoaderContext>));
188
+ const loader = (this.loader = FragmentILoader
189
+ ? new FragmentILoader(config)
190
+ : (new DefaultILoader(config) as Loader<FragmentLoaderContext>));
197
191
  const loaderContext = createLoaderContext(frag, part);
192
+ frag.loader = loader;
198
193
  // Should we define another load policy for parts?
199
194
  const loadPolicy = getLoaderConfigWithoutReties(
200
195
  config.fragLoadPolicy.default,
@@ -127,9 +127,9 @@ export class Fragment extends BaseSegment {
127
127
  // The ending Presentation Time Stamp (PTS) of the fragment. Set after transmux complete.
128
128
  public endPTS?: number;
129
129
  // The starting Decode Time Stamp (DTS) of the fragment. Set after transmux complete.
130
- public startDTS!: number;
130
+ public startDTS?: number;
131
131
  // The ending Decode Time Stamp (DTS) of the fragment. Set after transmux complete.
132
- public endDTS!: number;
132
+ public endDTS?: number;
133
133
  // The start time of the fragment, as listed in the manifest. Updated after transmux complete.
134
134
  public start: number = 0;
135
135
  // Set by `updateFragPTSDTS` in level-helper
@@ -1,6 +1,5 @@
1
- import type { Part } from './fragment';
1
+ import type { Fragment, MediaFragment, Part } from './fragment';
2
2
  import type { DateRange } from './date-range';
3
- import type { Fragment, MediaFragment } from './fragment';
4
3
  import type { AttrList } from '../utils/attr-list';
5
4
  import type { VariableMap } from '../types/level';
6
5
 
@@ -452,7 +452,6 @@ class PlaylistLoader implements NetworkComponentAPI {
452
452
  const { id, level, type } = context;
453
453
 
454
454
  const url = getResponseUrl(response, context);
455
- const levelUrlId = 0;
456
455
  const levelId = Number.isFinite(level as number)
457
456
  ? (level as number)
458
457
  : Number.isFinite(id as number)
@@ -464,7 +463,7 @@ class PlaylistLoader implements NetworkComponentAPI {
464
463
  url,
465
464
  levelId,
466
465
  levelType,
467
- levelUrlId,
466
+ 0,
468
467
  this.variableList,
469
468
  );
470
469
 
@@ -160,15 +160,18 @@ export default class MP4Remuxer implements Remuxer {
160
160
  if (this.ISGenerated) {
161
161
  const config = this.videoTrackConfig;
162
162
  if (
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])
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)
168
170
  ) {
169
171
  this.resetInitSegment();
170
172
  }
171
- } else {
173
+ }
174
+ if (!this.ISGenerated) {
172
175
  initSegment = this.generateIS(
173
176
  audioTrack,
174
177
  videoTrack,
@@ -1085,8 +1088,9 @@ export default class MP4Remuxer implements Remuxer {
1085
1088
  const startDTS: number =
1086
1089
  (nextAudioPts !== null
1087
1090
  ? nextAudioPts
1088
- : videoData.startDTS * inputTimeScale) + init90kHz;
1089
- const endDTS: number = videoData.endDTS * inputTimeScale + init90kHz;
1091
+ : (videoData.startDTS as number) * inputTimeScale) + init90kHz;
1092
+ const endDTS: number =
1093
+ (videoData.endDTS as number) * inputTimeScale + init90kHz;
1090
1094
  // one sample's duration value
1091
1095
  const frameDuration: number = scaleFactor * AAC_SAMPLES_PER_FRAME;
1092
1096
  // samples count of this segment's duration
@@ -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 = {
@@ -192,6 +192,18 @@ export interface LevelUpdatedData {
192
192
  level: number;
193
193
  }
194
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
+
195
207
  export interface LevelPTSUpdatedData {
196
208
  details: LevelDetails;
197
209
  level: Level;
@@ -317,7 +329,7 @@ export interface NonNativeTextTracksData {
317
329
  }
318
330
 
319
331
  export interface InitPTSFoundData {
320
- id: string;
332
+ id: PlaylistLevelType;
321
333
  frag: MediaFragment;
322
334
  initPTS: number;
323
335
  timescale: number;
@@ -461,11 +461,10 @@ export function computeReloadInterval(
461
461
  }
462
462
 
463
463
  export function getFragmentWithSN(
464
- level: Level,
464
+ details: LevelDetails | undefined,
465
465
  sn: number,
466
466
  fragCurrent: Fragment | null,
467
467
  ): MediaFragment | null {
468
- const details = level?.details;
469
468
  if (!details) {
470
469
  return null;
471
470
  }
@@ -485,14 +484,14 @@ export function getFragmentWithSN(
485
484
  }
486
485
 
487
486
  export function getPartWith(
488
- level: Level,
487
+ details: LevelDetails | undefined,
489
488
  sn: number,
490
489
  partIndex: number,
491
490
  ): Part | null {
492
- if (!level?.details) {
491
+ if (!details) {
493
492
  return null;
494
493
  }
495
- return findPart(level.details?.partList, sn, partIndex);
494
+ return findPart(details.partList, sn, partIndex);
496
495
  }
497
496
 
498
497
  export function findPart(