hls.js 1.5.5 → 1.5.6-0.canary.10003

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 (68) hide show
  1. package/README.md +1 -0
  2. package/dist/hls-demo.js +10 -0
  3. package/dist/hls-demo.js.map +1 -1
  4. package/dist/hls.js +2075 -1166
  5. package/dist/hls.js.d.ts +65 -50
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +1148 -859
  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 +984 -696
  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 +1757 -863
  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 +20 -20
  20. package/src/config.ts +3 -2
  21. package/src/controller/abr-controller.ts +21 -20
  22. package/src/controller/audio-stream-controller.ts +15 -16
  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 +149 -33
  26. package/src/controller/buffer-controller.ts +11 -11
  27. package/src/controller/cap-level-controller.ts +1 -2
  28. package/src/controller/cmcd-controller.ts +27 -6
  29. package/src/controller/content-steering-controller.ts +8 -6
  30. package/src/controller/eme-controller.ts +9 -22
  31. package/src/controller/error-controller.ts +6 -8
  32. package/src/controller/fps-controller.ts +2 -3
  33. package/src/controller/gap-controller.ts +43 -16
  34. package/src/controller/latency-controller.ts +9 -11
  35. package/src/controller/level-controller.ts +12 -18
  36. package/src/controller/stream-controller.ts +25 -32
  37. package/src/controller/subtitle-stream-controller.ts +13 -14
  38. package/src/controller/subtitle-track-controller.ts +5 -3
  39. package/src/controller/timeline-controller.ts +23 -30
  40. package/src/crypt/aes-crypto.ts +21 -2
  41. package/src/crypt/decrypter-aes-mode.ts +4 -0
  42. package/src/crypt/decrypter.ts +32 -18
  43. package/src/crypt/fast-aes-key.ts +24 -5
  44. package/src/demux/audio/adts.ts +9 -4
  45. package/src/demux/sample-aes.ts +2 -0
  46. package/src/demux/transmuxer-interface.ts +4 -12
  47. package/src/demux/transmuxer-worker.ts +4 -4
  48. package/src/demux/transmuxer.ts +16 -3
  49. package/src/demux/tsdemuxer.ts +71 -37
  50. package/src/demux/video/avc-video-parser.ts +208 -119
  51. package/src/demux/video/base-video-parser.ts +134 -2
  52. package/src/demux/video/exp-golomb.ts +0 -208
  53. package/src/demux/video/hevc-video-parser.ts +746 -0
  54. package/src/events.ts +7 -0
  55. package/src/hls.ts +42 -34
  56. package/src/loader/fragment-loader.ts +9 -2
  57. package/src/loader/key-loader.ts +2 -0
  58. package/src/loader/level-key.ts +10 -9
  59. package/src/loader/playlist-loader.ts +4 -5
  60. package/src/remux/mp4-generator.ts +196 -1
  61. package/src/remux/mp4-remuxer.ts +23 -7
  62. package/src/task-loop.ts +5 -2
  63. package/src/types/component-api.ts +2 -0
  64. package/src/types/demuxer.ts +3 -0
  65. package/src/types/events.ts +4 -0
  66. package/src/utils/codecs.ts +33 -4
  67. package/src/utils/encryption-methods-util.ts +21 -0
  68. package/src/utils/logger.ts +54 -24
package/package.json CHANGED
@@ -58,39 +58,39 @@
58
58
  "test:func:sauce": "SAUCE=1 UA=safari OS='OS X 10.15' BABEL_ENV=development mocha --require @babel/register tests/functional/auto/setup.js --timeout 40000 --exit",
59
59
  "type-check": "tsc --noEmit",
60
60
  "type-check:watch": "npm run type-check -- --watch",
61
- "prepare": "husky install"
61
+ "prepare": "husky"
62
62
  },
63
63
  "devDependencies": {
64
- "@babel/core": "7.23.7",
64
+ "@babel/core": "7.23.9",
65
65
  "@babel/helper-module-imports": "7.22.15",
66
66
  "@babel/plugin-proposal-class-properties": "7.18.6",
67
67
  "@babel/plugin-proposal-object-rest-spread": "7.20.7",
68
68
  "@babel/plugin-proposal-optional-chaining": "7.21.0",
69
69
  "@babel/plugin-transform-object-assign": "7.23.3",
70
- "@babel/preset-env": "7.23.7",
70
+ "@babel/preset-env": "7.23.9",
71
71
  "@babel/preset-typescript": "7.23.3",
72
72
  "@babel/register": "7.23.7",
73
- "@microsoft/api-documenter": "7.23.16",
74
- "@microsoft/api-extractor": "7.39.1",
73
+ "@microsoft/api-documenter": "7.23.23",
74
+ "@microsoft/api-extractor": "7.40.1",
75
75
  "@rollup/plugin-alias": "5.1.0",
76
76
  "@rollup/plugin-babel": "6.0.4",
77
77
  "@rollup/plugin-commonjs": "25.0.7",
78
78
  "@rollup/plugin-node-resolve": "15.2.3",
79
79
  "@rollup/plugin-replace": "5.0.5",
80
80
  "@rollup/plugin-terser": "0.4.4",
81
- "@rollup/plugin-typescript": "11.1.5",
82
- "@svta/common-media-library": "0.6.1",
81
+ "@rollup/plugin-typescript": "11.1.6",
82
+ "@svta/common-media-library": "0.6.2",
83
83
  "@types/chai": "4.3.11",
84
84
  "@types/chart.js": "2.9.41",
85
85
  "@types/mocha": "10.0.6",
86
86
  "@types/sinon-chai": "3.2.12",
87
- "@typescript-eslint/eslint-plugin": "6.17.0",
88
- "@typescript-eslint/parser": "6.17.0",
87
+ "@typescript-eslint/eslint-plugin": "6.21.0",
88
+ "@typescript-eslint/parser": "6.21.0",
89
89
  "babel-loader": "9.1.3",
90
90
  "babel-plugin-transform-remove-console": "6.9.4",
91
- "chai": "4.3.10",
91
+ "chai": "4.4.1",
92
92
  "chart.js": "2.9.4",
93
- "chromedriver": "120.0.1",
93
+ "chromedriver": "121.0.0",
94
94
  "doctoc": "2.2.1",
95
95
  "es-check": "7.1.1",
96
96
  "eslint": "8.56.0",
@@ -101,7 +101,7 @@
101
101
  "eslint-plugin-promise": "6.1.1",
102
102
  "eventemitter3": "5.0.1",
103
103
  "http-server": "14.1.1",
104
- "husky": "8.0.3",
104
+ "husky": "9.0.10",
105
105
  "jsonpack": "1.1.5",
106
106
  "karma": "6.4.2",
107
107
  "karma-chrome-launcher": "3.2.0",
@@ -111,24 +111,24 @@
111
111
  "karma-rollup-preprocessor": "github:jlmakes/karma-rollup-preprocessor#7a7268d91149307b3cf2888ee4e65ccd079955a3",
112
112
  "karma-sinon-chai": "2.0.2",
113
113
  "karma-sourcemap-loader": "0.4.0",
114
- "lint-staged": "15.2.0",
114
+ "lint-staged": "15.2.2",
115
115
  "markdown-styles": "3.2.0",
116
116
  "micromatch": "4.0.5",
117
117
  "mocha": "10.2.0",
118
118
  "node-fetch": "3.3.2",
119
- "npm-run-all": "4.1.5",
120
- "prettier": "3.1.1",
119
+ "npm-run-all2": "5.0.2",
120
+ "prettier": "3.2.4",
121
121
  "promise-polyfill": "8.3.0",
122
- "rollup": "4.9.4",
122
+ "rollup": "4.9.6",
123
123
  "rollup-plugin-istanbul": "5.0.0",
124
124
  "sauce-connect-launcher": "1.3.2",
125
- "selenium-webdriver": "4.16.0",
126
- "semver": "7.5.4",
125
+ "selenium-webdriver": "4.17.0",
126
+ "semver": "7.6.0",
127
127
  "sinon": "17.0.1",
128
128
  "sinon-chai": "3.7.0",
129
129
  "typescript": "5.3.3",
130
130
  "url-toolkit": "2.2.5",
131
- "wrangler": "3.22.4"
131
+ "wrangler": "3.28.2"
132
132
  },
133
- "version": "1.5.5"
133
+ "version": "1.5.6-0.canary.10003"
134
134
  }
package/src/config.ts CHANGED
@@ -17,10 +17,10 @@ import XhrLoader from './utils/xhr-loader';
17
17
  import FetchLoader, { fetchSupported } from './utils/fetch-loader';
18
18
  import Cues from './utils/cues';
19
19
  import { requestMediaKeySystemAccess } from './utils/mediakeys-helper';
20
- import { ILogger, logger } from './utils/logger';
21
20
 
22
21
  import type Hls from './hls';
23
22
  import type { CuesInterface } from './utils/cues';
23
+ import type { ILogger } from './utils/logger';
24
24
  import type { MediaKeyFunc, KeySystems } from './utils/mediakeys-helper';
25
25
  import type {
26
26
  FragmentLoaderContext,
@@ -558,6 +558,7 @@ function timelineConfig(): TimelineControllerConfig {
558
558
  export function mergeConfig(
559
559
  defaultConfig: HlsConfig,
560
560
  userConfig: Partial<HlsConfig>,
561
+ logger: ILogger,
561
562
  ): HlsConfig {
562
563
  if (
563
564
  (userConfig.liveSyncDurationCount ||
@@ -664,7 +665,7 @@ function deepCpy(obj: any): any {
664
665
  /**
665
666
  * @ignore
666
667
  */
667
- export function enableStreamingMode(config) {
668
+ export function enableStreamingMode(config: HlsConfig, logger: ILogger) {
668
669
  const currentLoader = config.loader;
669
670
  if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
670
671
  // If a developer has configured their own loader, respect that choice
@@ -2,7 +2,7 @@ import EwmaBandWidthEstimator from '../utils/ewma-bandwidth-estimator';
2
2
  import { Events } from '../events';
3
3
  import { ErrorDetails } from '../errors';
4
4
  import { PlaylistLevelType } from '../types/loader';
5
- import { logger } from '../utils/logger';
5
+ import { Logger } from '../utils/logger';
6
6
  import {
7
7
  SUPPORTED_INFO_DEFAULT,
8
8
  getMediaDecodingInfoPromise,
@@ -31,7 +31,7 @@ import type {
31
31
  } from '../types/events';
32
32
  import type { AbrComponentAPI } from '../types/component-api';
33
33
 
34
- class AbrController implements AbrComponentAPI {
34
+ class AbrController extends Logger implements AbrComponentAPI {
35
35
  protected hls: Hls;
36
36
  private lastLevelLoadSec: number = 0;
37
37
  private lastLoadedFragLevel: number = -1;
@@ -48,6 +48,7 @@ class AbrController implements AbrComponentAPI {
48
48
  public bwEstimator: EwmaBandWidthEstimator;
49
49
 
50
50
  constructor(hls: Hls) {
51
+ super('abr', hls.logger);
51
52
  this.hls = hls;
52
53
  this.bwEstimator = this.initEstimator();
53
54
  this.registerListeners();
@@ -55,7 +56,7 @@ class AbrController implements AbrComponentAPI {
55
56
 
56
57
  public resetEstimator(abrEwmaDefaultEstimate?: number) {
57
58
  if (abrEwmaDefaultEstimate) {
58
- logger.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
59
+ this.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
59
60
  this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate;
60
61
  }
61
62
  this.firstSelection = -1;
@@ -355,7 +356,7 @@ class AbrController implements AbrComponentAPI {
355
356
  }
356
357
 
357
358
  this.clearTimer();
358
- logger.warn(`[abr] Fragment ${frag.sn}${
359
+ this.warn(`Fragment ${frag.sn}${
359
360
  part ? ' part ' + part.index : ''
360
361
  } of level ${frag.level} is loading too slowly;
361
362
  Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s
@@ -479,8 +480,8 @@ class AbrController implements AbrComponentAPI {
479
480
  }
480
481
  const firstLevel = this.hls.firstLevel;
481
482
  const clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel);
482
- logger.warn(
483
- `[abr] Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`,
483
+ this.warn(
484
+ `Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`,
484
485
  );
485
486
  return clamped;
486
487
  }
@@ -591,8 +592,8 @@ class AbrController implements AbrComponentAPI {
591
592
  ? Math.min(currentFragDuration, config.maxLoadingDelay)
592
593
  : config.maxLoadingDelay;
593
594
  maxStarvationDelay = maxLoadingDelay - bitrateTestDelay;
594
- logger.info(
595
- `[abr] bitrate test took ${Math.round(
595
+ this.info(
596
+ `bitrate test took ${Math.round(
596
597
  1000 * bitrateTestDelay,
597
598
  )}ms, set first fragment max fetchDuration to ${Math.round(
598
599
  1000 * maxStarvationDelay,
@@ -611,8 +612,8 @@ class AbrController implements AbrComponentAPI {
611
612
  bwFactor,
612
613
  bwUpFactor,
613
614
  );
614
- logger.info(
615
- `[abr] ${
615
+ this.info(
616
+ `${
616
617
  bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'
617
618
  }, optimal quality level ${bestLevel}`,
618
619
  );
@@ -691,7 +692,7 @@ class AbrController implements AbrComponentAPI {
691
692
  : videoRanges[0];
692
693
  currentFrameRate = minFramerate;
693
694
  currentBw = Math.max(currentBw, minBitrate);
694
- logger.log(`[abr] picked start tier ${JSON.stringify(startTier)}`);
695
+ this.log(`picked start tier ${JSON.stringify(startTier)}`);
695
696
  } else {
696
697
  currentCodecSet = level?.codecSet;
697
698
  currentVideoRange = level?.videoRange;
@@ -741,19 +742,19 @@ class AbrController implements AbrComponentAPI {
741
742
  const levels = this.hls.levels;
742
743
  const index = levels.indexOf(levelInfo);
743
744
  if (decodingInfo.error) {
744
- logger.warn(
745
- `[abr] MediaCapabilities decodingInfo error: "${
745
+ this.warn(
746
+ `MediaCapabilities decodingInfo error: "${
746
747
  decodingInfo.error
747
748
  }" for level ${index} ${JSON.stringify(decodingInfo)}`,
748
749
  );
749
750
  } else if (!decodingInfo.supported) {
750
- logger.warn(
751
- `[abr] Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify(
751
+ this.warn(
752
+ `Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify(
752
753
  decodingInfo,
753
754
  )}`,
754
755
  );
755
756
  if (index > -1 && levels.length > 1) {
756
- logger.log(`[abr] Removing unsupported level ${index}`);
757
+ this.log(`Removing unsupported level ${index}`);
757
758
  this.hls.removeLevel(index);
758
759
  }
759
760
  }
@@ -832,8 +833,8 @@ class AbrController implements AbrComponentAPI {
832
833
  (forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)
833
834
  ) {
834
835
  if (levelsSkipped.length) {
835
- logger.trace(
836
- `[abr] Skipped level(s) ${levelsSkipped.join(
836
+ this.trace(
837
+ `Skipped level(s) ${levelsSkipped.join(
837
838
  ',',
838
839
  )} of ${maxAutoLevel} max with CODECS and VIDEO-RANGE:"${
839
840
  levels[levelsSkipped[0]].codecs
@@ -842,8 +843,8 @@ class AbrController implements AbrComponentAPI {
842
843
  }" ${currentVideoRange}`,
843
844
  );
844
845
  }
845
- logger.info(
846
- `[abr] switch candidate:${selectionBaseLevel}->${i} adjustedbw(${Math.round(
846
+ this.info(
847
+ `switch candidate:${selectionBaseLevel}->${i} adjustedbw(${Math.round(
847
848
  adjustedbw,
848
849
  )})-bitrate=${Math.round(
849
850
  adjustedbw - bitrate,
@@ -71,30 +71,27 @@ class AudioStreamController
71
71
  hls,
72
72
  fragmentTracker,
73
73
  keyLoader,
74
- '[audio-stream-controller]',
74
+ 'audio-stream-controller',
75
75
  PlaylistLevelType.AUDIO,
76
76
  );
77
- this._registerListeners();
77
+ this.registerListeners();
78
78
  }
79
79
 
80
80
  protected onHandlerDestroying() {
81
- this._unregisterListeners();
81
+ this.unregisterListeners();
82
82
  super.onHandlerDestroying();
83
83
  this.mainDetails = null;
84
84
  this.bufferedTrack = null;
85
85
  this.switchingTrack = null;
86
86
  }
87
87
 
88
- private _registerListeners() {
88
+ protected registerListeners() {
89
+ super.registerListeners();
89
90
  const { hls } = this;
90
- hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
91
- hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
92
- hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
93
91
  hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
94
92
  hls.on(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
95
93
  hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
96
94
  hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
97
- hls.on(Events.ERROR, this.onError, this);
98
95
  hls.on(Events.BUFFER_RESET, this.onBufferReset, this);
99
96
  hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
100
97
  hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
@@ -103,16 +100,16 @@ class AudioStreamController
103
100
  hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
104
101
  }
105
102
 
106
- private _unregisterListeners() {
103
+ protected unregisterListeners() {
107
104
  const { hls } = this;
108
- hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
109
- hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
110
- hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
105
+ if (!hls) {
106
+ return;
107
+ }
108
+ super.unregisterListeners();
111
109
  hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
112
110
  hls.off(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
113
111
  hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
114
112
  hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
115
- hls.off(Events.ERROR, this.onError, this);
116
113
  hls.off(Events.BUFFER_RESET, this.onBufferReset, this);
117
114
  hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
118
115
  hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
@@ -281,12 +278,14 @@ class AudioStreamController
281
278
  const { hls, levels, media, trackId } = this;
282
279
  const config = hls.config;
283
280
 
284
- // 1. if video not attached AND
281
+ // 1. if buffering is suspended
282
+ // 2. if video not attached AND
285
283
  // start fragment already requested OR start frag prefetch not enabled
286
- // 2. if tracks or track not loaded and selected
284
+ // 3. if tracks or track not loaded and selected
287
285
  // then exit loop
288
286
  // => if media not attached but start frag prefetch is enabled and start frag not requested yet, we will not exit loop
289
287
  if (
288
+ !this.buffering ||
290
289
  (!media && (this.startFragRequested || !config.startFragPrefetch)) ||
291
290
  !levels?.[trackId]
292
291
  ) {
@@ -713,7 +712,7 @@ class AudioStreamController
713
712
  this.fragBufferedComplete(frag, part);
714
713
  }
715
714
 
716
- private onError(event: Events.ERROR, data: ErrorData) {
715
+ protected onError(event: Events.ERROR, data: ErrorData) {
717
716
  if (data.fatal) {
718
717
  this.state = State.ERROR;
719
718
  return;
@@ -33,7 +33,7 @@ class AudioTrackController extends BasePlaylistController {
33
33
  private selectDefaultTrack: boolean = true;
34
34
 
35
35
  constructor(hls: Hls) {
36
- super(hls, '[audio-track-controller]');
36
+ super(hls, 'audio-track-controller');
37
37
  this.registerListeners();
38
38
  }
39
39
 
@@ -5,7 +5,7 @@ import { computeReloadInterval, mergeDetails } from '../utils/level-helper';
5
5
  import { ErrorData } from '../types/events';
6
6
  import { getRetryDelay, isTimeoutError } from '../utils/error-helper';
7
7
  import { NetworkErrorAction } from './error-controller';
8
- import { logger } from '../utils/logger';
8
+ import { Logger } from '../utils/logger';
9
9
  import type { LevelDetails } from '../loader/level-details';
10
10
  import type { MediaPlaylist } from '../types/media-playlist';
11
11
  import type {
@@ -14,17 +14,17 @@ import type {
14
14
  TrackLoadedData,
15
15
  } from '../types/events';
16
16
 
17
- export default class BasePlaylistController implements NetworkComponentAPI {
17
+ export default class BasePlaylistController
18
+ extends Logger
19
+ implements NetworkComponentAPI
20
+ {
18
21
  protected hls: Hls;
19
22
  protected timer: number = -1;
20
23
  protected requestScheduled: number = -1;
21
24
  protected canLoad: boolean = false;
22
- protected log: (msg: any) => void;
23
- protected warn: (msg: any) => void;
24
25
 
25
26
  constructor(hls: Hls, logPrefix: string) {
26
- this.log = logger.log.bind(logger, `${logPrefix}:`);
27
- this.warn = logger.warn.bind(logger, `${logPrefix}:`);
27
+ super(logPrefix, hls.logger);
28
28
  this.hls = hls;
29
29
  }
30
30
 
@@ -65,7 +65,7 @@ export default class BasePlaylistController implements NetworkComponentAPI {
65
65
  try {
66
66
  uri = new self.URL(attr.URI, previous.url).href;
67
67
  } catch (error) {
68
- logger.warn(
68
+ this.warn(
69
69
  `Could not construct new URL for Rendition Report: ${error}`,
70
70
  );
71
71
  uri = attr.URI || '';
@@ -192,7 +192,19 @@ export default class BasePlaylistController implements NetworkComponentAPI {
192
192
  details.targetduration * 1.5,
193
193
  );
194
194
  if (currentGoal > 0) {
195
- if (previousDetails && currentGoal > previousDetails.tuneInGoal) {
195
+ if (cdnAge > details.targetduration * 3) {
196
+ // Omit segment and part directives when the last response was more than 3 target durations ago,
197
+ this.log(
198
+ `Playlist last advanced ${lastAdvanced.toFixed(
199
+ 2,
200
+ )}s ago. Omitting segment and part directives.`,
201
+ );
202
+ msn = undefined;
203
+ part = undefined;
204
+ } else if (
205
+ previousDetails?.tuneInGoal &&
206
+ cdnAge - details.partTarget > previousDetails.tuneInGoal
207
+ ) {
196
208
  // If we attempted to get the next or latest playlist update, but currentGoal increased,
197
209
  // then we either can't catchup, or the "age" header cannot be trusted.
198
210
  this.warn(