hls.js 1.5.6 → 1.5.7-0.canary.10014

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 +2074 -1165
  5. package/dist/hls.js.d.ts +65 -50
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +1147 -858
  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 +983 -695
  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 +1756 -862
  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 +21 -21
  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 +24 -31
  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
@@ -1,12 +1,15 @@
1
1
  import TaskLoop from '../task-loop';
2
2
  import { FragmentState } from './fragment-tracker';
3
3
  import { Bufferable, BufferHelper, BufferInfo } from '../utils/buffer-helper';
4
- import { logger } from '../utils/logger';
5
4
  import { Events } from '../events';
6
5
  import { ErrorDetails, ErrorTypes } from '../errors';
7
6
  import { ChunkMetadata } from '../types/transmuxer';
8
7
  import { appendUint8Array } from '../utils/mp4-tools';
9
8
  import { alignStream } from '../utils/discontinuities';
9
+ import {
10
+ isFullSegmentEncryption,
11
+ getAesModeFromFullSegmentMethod,
12
+ } from '../utils/encryption-methods-util';
10
13
  import {
11
14
  findFragmentByPDT,
12
15
  findFragmentByPTS,
@@ -97,12 +100,8 @@ export default class BaseStreamController
97
100
  protected startFragRequested: boolean = false;
98
101
  protected decrypter: Decrypter;
99
102
  protected initPTS: RationalTimestamp[] = [];
100
- protected onvseeking: EventListener | null = null;
101
- protected onvended: EventListener | null = null;
102
-
103
- private readonly logPrefix: string = '';
104
- protected log: (msg: any) => void;
105
- protected warn: (msg: any) => void;
103
+ protected buffering: boolean = true;
104
+ private loadingParts: boolean = false;
106
105
 
107
106
  constructor(
108
107
  hls: Hls,
@@ -111,18 +110,32 @@ export default class BaseStreamController
111
110
  logPrefix: string,
112
111
  playlistType: PlaylistLevelType,
113
112
  ) {
114
- super();
113
+ super(logPrefix, hls.logger);
115
114
  this.playlistType = playlistType;
116
- this.logPrefix = logPrefix;
117
- this.log = logger.log.bind(logger, `${logPrefix}:`);
118
- this.warn = logger.warn.bind(logger, `${logPrefix}:`);
119
115
  this.hls = hls;
120
116
  this.fragmentLoader = new FragmentLoader(hls.config);
121
117
  this.keyLoader = keyLoader;
122
118
  this.fragmentTracker = fragmentTracker;
123
119
  this.config = hls.config;
124
120
  this.decrypter = new Decrypter(hls.config);
121
+ }
122
+
123
+ protected registerListeners() {
124
+ const { hls } = this;
125
+ hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
126
+ hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
127
+ hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
125
128
  hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
129
+ hls.on(Events.ERROR, this.onError, this);
130
+ }
131
+
132
+ protected unregisterListeners() {
133
+ const { hls } = this;
134
+ hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
135
+ hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
136
+ hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
137
+ hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
138
+ hls.off(Events.ERROR, this.onError, this);
126
139
  }
127
140
 
128
141
  protected doTick() {
@@ -150,6 +163,14 @@ export default class BaseStreamController
150
163
  this.state = State.STOPPED;
151
164
  }
152
165
 
166
+ public pauseBuffering() {
167
+ this.buffering = false;
168
+ }
169
+
170
+ public resumeBuffering() {
171
+ this.buffering = true;
172
+ }
173
+
153
174
  protected _streamEnded(
154
175
  bufferInfo: BufferInfo,
155
176
  levelDetails: LevelDetails,
@@ -197,10 +218,8 @@ export default class BaseStreamController
197
218
  data: MediaAttachedData,
198
219
  ) {
199
220
  const media = (this.media = this.mediaBuffer = data.media);
200
- this.onvseeking = this.onMediaSeeking.bind(this) as EventListener;
201
- this.onvended = this.onMediaEnded.bind(this) as EventListener;
202
- media.addEventListener('seeking', this.onvseeking);
203
- media.addEventListener('ended', this.onvended);
221
+ media.addEventListener('seeking', this.onMediaSeeking);
222
+ media.addEventListener('ended', this.onMediaEnded);
204
223
  const config = this.config;
205
224
  if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
206
225
  this.startLoad(config.startPosition);
@@ -215,10 +234,9 @@ export default class BaseStreamController
215
234
  }
216
235
 
217
236
  // remove video listeners
218
- if (media && this.onvseeking && this.onvended) {
219
- media.removeEventListener('seeking', this.onvseeking);
220
- media.removeEventListener('ended', this.onvended);
221
- this.onvseeking = this.onvended = null;
237
+ if (media) {
238
+ media.removeEventListener('seeking', this.onMediaSeeking);
239
+ media.removeEventListener('ended', this.onMediaEnded);
222
240
  }
223
241
  if (this.keyLoader) {
224
242
  this.keyLoader.detach();
@@ -229,7 +247,11 @@ export default class BaseStreamController
229
247
  this.stopLoad();
230
248
  }
231
249
 
232
- protected onMediaSeeking() {
250
+ protected onManifestLoading() {}
251
+
252
+ protected onError(event: Events.ERROR, data: ErrorData) {}
253
+
254
+ protected onMediaSeeking = () => {
233
255
  const { config, fragCurrent, media, mediaBuffer, state } = this;
234
256
  const currentTime: number = media ? media.currentTime : 0;
235
257
  const bufferInfo = BufferHelper.bufferInfo(
@@ -283,6 +305,21 @@ export default class BaseStreamController
283
305
  );
284
306
 
285
307
  this.lastCurrentTime = currentTime;
308
+ if (!this.loadingParts) {
309
+ const bufferEnd = Math.max(bufferInfo.end, currentTime);
310
+ const shouldLoadParts = this.shouldLoadParts(
311
+ this.getLevelDetails(),
312
+ bufferEnd,
313
+ );
314
+ if (shouldLoadParts) {
315
+ this.log(
316
+ `LL-Part loading ON after seeking to ${currentTime.toFixed(
317
+ 2,
318
+ )} with buffer @${bufferEnd.toFixed(2)}`,
319
+ );
320
+ this.loadingParts = shouldLoadParts;
321
+ }
322
+ }
286
323
  }
287
324
 
288
325
  // in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
@@ -292,12 +329,17 @@ export default class BaseStreamController
292
329
 
293
330
  // Async tick to speed up processing
294
331
  this.tickImmediate();
295
- }
332
+ };
296
333
 
297
- protected onMediaEnded() {
334
+ protected onMediaEnded = () => {
298
335
  // reset startPosition and lastCurrentTime to restart playback @ stream beginning
299
336
  this.startPosition = this.lastCurrentTime = 0;
300
- }
337
+ if (this.playlistType === PlaylistLevelType.MAIN) {
338
+ this.hls.trigger(Events.MEDIA_ENDED, {
339
+ stalled: false,
340
+ });
341
+ }
342
+ };
301
343
 
302
344
  protected onManifestLoaded(
303
345
  event: Events.MANIFEST_LOADED,
@@ -312,7 +354,7 @@ export default class BaseStreamController
312
354
  this.stopLoad();
313
355
  super.onHandlerDestroying();
314
356
  // @ts-ignore
315
- this.hls = null;
357
+ this.hls = this.onMediaSeeking = this.onMediaEnded = null;
316
358
  }
317
359
 
318
360
  protected onHandlerDestroyed() {
@@ -486,7 +528,7 @@ export default class BaseStreamController
486
528
  payload.byteLength > 0 &&
487
529
  decryptData?.key &&
488
530
  decryptData.iv &&
489
- decryptData.method === 'AES-128'
531
+ isFullSegmentEncryption(decryptData.method)
490
532
  ) {
491
533
  const startTime = self.performance.now();
492
534
  // decrypt init segment data
@@ -495,6 +537,7 @@ export default class BaseStreamController
495
537
  new Uint8Array(payload),
496
538
  decryptData.key.buffer,
497
539
  decryptData.iv.buffer,
540
+ getAesModeFromFullSegmentMethod(decryptData.method),
498
541
  )
499
542
  .catch((err) => {
500
543
  hls.trigger(Events.ERROR, {
@@ -649,7 +692,7 @@ export default class BaseStreamController
649
692
  if (frag.encrypted && !frag.decryptdata?.key) {
650
693
  this.log(
651
694
  `Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${
652
- this.logPrefix === '[stream-controller]' ? 'level' : 'track'
695
+ this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'
653
696
  } ${frag.level}`,
654
697
  );
655
698
  this.state = State.KEY_LOADING;
@@ -673,8 +716,23 @@ export default class BaseStreamController
673
716
  this.keyLoader.loadClear(frag, details.encryptedFragments);
674
717
  }
675
718
 
719
+ const fragPrevious = this.fragPrevious;
720
+ if (
721
+ frag.sn !== 'initSegment' &&
722
+ (!fragPrevious || frag.sn !== fragPrevious.sn)
723
+ ) {
724
+ const shouldLoadParts = this.shouldLoadParts(level.details, frag.end);
725
+ if (shouldLoadParts !== this.loadingParts) {
726
+ this.log(
727
+ `LL-Part loading ${
728
+ shouldLoadParts ? 'ON' : 'OFF'
729
+ } loading sn ${fragPrevious?.sn}->${frag.sn}`,
730
+ );
731
+ this.loadingParts = shouldLoadParts;
732
+ }
733
+ }
676
734
  targetBufferTime = Math.max(frag.start, targetBufferTime || 0);
677
- if (this.config.lowLatencyMode && frag.sn !== 'initSegment') {
735
+ if (this.loadingParts && frag.sn !== 'initSegment') {
678
736
  const partList = details.partList;
679
737
  if (partList && progressCallback) {
680
738
  if (targetBufferTime > frag.end && details.fragmentHint) {
@@ -689,7 +747,7 @@ export default class BaseStreamController
689
747
  } of playlist [${details.startSN}-${
690
748
  details.endSN
691
749
  }] parts [0-${partIndex}-${partList.length - 1}] ${
692
- this.logPrefix === '[stream-controller]' ? 'level' : 'track'
750
+ this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'
693
751
  }: ${frag.level}, target: ${parseFloat(
694
752
  targetBufferTime.toFixed(3),
695
753
  )}`,
@@ -745,10 +803,22 @@ export default class BaseStreamController
745
803
  }
746
804
  }
747
805
 
806
+ if (frag.sn !== 'initSegment' && this.loadingParts) {
807
+ this.log(
808
+ `LL-Part loading OFF after next part miss @${targetBufferTime.toFixed(
809
+ 2,
810
+ )}`,
811
+ );
812
+ this.loadingParts = false;
813
+ } else if (!frag.url) {
814
+ // Selected fragment hint for part but not loading parts
815
+ return Promise.resolve(null);
816
+ }
817
+
748
818
  this.log(
749
819
  `Loading fragment ${frag.sn} cc: ${frag.cc} ${
750
820
  details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''
751
- }${this.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${
821
+ }${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'}: ${
752
822
  frag.level
753
823
  }, target: ${parseFloat(targetBufferTime.toFixed(3))}`,
754
824
  );
@@ -872,9 +942,50 @@ export default class BaseStreamController
872
942
  if (part) {
873
943
  part.stats.parsing.end = now;
874
944
  }
945
+ // See if part loading should be disabled/enabled based on buffer and playback position.
946
+ if (frag.sn !== 'initSegment') {
947
+ const levelDetails = this.getLevelDetails();
948
+ const loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
949
+ const shouldLoadParts =
950
+ loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
951
+ if (shouldLoadParts !== this.loadingParts) {
952
+ this.log(
953
+ `LL-Part loading ${
954
+ shouldLoadParts ? 'ON' : 'OFF'
955
+ } after parsing segment ending @${frag.end.toFixed(2)}`,
956
+ );
957
+ this.loadingParts = shouldLoadParts;
958
+ }
959
+ }
960
+
875
961
  this.updateLevelTiming(frag, part, level, chunkMeta.partial);
876
962
  }
877
963
 
964
+ private shouldLoadParts(
965
+ details: LevelDetails | undefined,
966
+ bufferEnd: number,
967
+ ): boolean {
968
+ if (this.config.lowLatencyMode) {
969
+ if (!details) {
970
+ return this.loadingParts;
971
+ }
972
+ if (details?.partList) {
973
+ // Buffer must be ahead of first part + duration of parts after last segment
974
+ // and playback must be at or past segment adjacent to part list
975
+ const firstPart = details.partList[0];
976
+ const safePartStart =
977
+ firstPart.end + (details.fragmentHint?.duration || 0);
978
+ if (
979
+ bufferEnd >= safePartStart &&
980
+ this.lastCurrentTime > firstPart.start - firstPart.fragment.duration
981
+ ) {
982
+ return true;
983
+ }
984
+ }
985
+ }
986
+ return false;
987
+ }
988
+
878
989
  protected getCurrentContext(
879
990
  chunkMeta: ChunkMetadata,
880
991
  ): { frag: Fragment; part: Part | null; level: Level } | null {
@@ -1056,7 +1167,8 @@ export default class BaseStreamController
1056
1167
  // find fragment index, contiguous with end of buffer position
1057
1168
  const { config } = this;
1058
1169
  const start = fragments[0].start;
1059
- let frag;
1170
+ const canLoadParts = config.lowLatencyMode && !!levelDetails.partList;
1171
+ let frag: Fragment | null = null;
1060
1172
 
1061
1173
  if (levelDetails.live) {
1062
1174
  const initialLiveManifestSize = config.initialLiveManifestSize;
@@ -1076,6 +1188,10 @@ export default class BaseStreamController
1076
1188
  this.startPosition === -1) ||
1077
1189
  pos < start
1078
1190
  ) {
1191
+ if (canLoadParts && !this.loadingParts) {
1192
+ this.log(`LL-Part loading ON for initial live fragment`);
1193
+ this.loadingParts = true;
1194
+ }
1079
1195
  frag = this.getInitialLiveFragment(levelDetails, fragments);
1080
1196
  this.startPosition = this.nextLoadPosition = frag
1081
1197
  ? this.hls.liveSyncPosition || frag.start
@@ -1088,7 +1204,7 @@ export default class BaseStreamController
1088
1204
 
1089
1205
  // If we haven't run into any special cases already, just load the fragment most closely matching the requested position
1090
1206
  if (!frag) {
1091
- const end = config.lowLatencyMode
1207
+ const end = this.loadingParts
1092
1208
  ? levelDetails.partEnd
1093
1209
  : levelDetails.fragmentEnd;
1094
1210
  frag = this.getFragmentAtPosition(pos, end, levelDetails);
@@ -1271,7 +1387,7 @@ export default class BaseStreamController
1271
1387
  const partList = levelDetails.partList;
1272
1388
 
1273
1389
  const loadingParts = !!(
1274
- config.lowLatencyMode &&
1390
+ this.loadingParts &&
1275
1391
  partList?.length &&
1276
1392
  fragmentHint
1277
1393
  );
@@ -1550,7 +1666,7 @@ export default class BaseStreamController
1550
1666
  errorAction.resolved = true;
1551
1667
  }
1552
1668
  } else {
1553
- logger.warn(
1669
+ this.warn(
1554
1670
  `${data.details} reached or exceeded max retry (${retryCount})`,
1555
1671
  );
1556
1672
  return;
@@ -1,5 +1,5 @@
1
1
  import { Events } from '../events';
2
- import { logger } from '../utils/logger';
2
+ import { Logger } from '../utils/logger';
3
3
  import { ErrorDetails, ErrorTypes } from '../errors';
4
4
  import { BufferHelper } from '../utils/buffer-helper';
5
5
  import {
@@ -42,7 +42,7 @@ interface BufferedChangeEvent extends Event {
42
42
  readonly removedRanges?: TimeRanges;
43
43
  }
44
44
 
45
- export default class BufferController implements ComponentAPI {
45
+ export default class BufferController extends Logger implements ComponentAPI {
46
46
  // The level details used to determine duration, target-duration and live
47
47
  private details: LevelDetails | null = null;
48
48
  // cache the self generated object url to detect hijack of video tag
@@ -82,17 +82,10 @@ export default class BufferController implements ComponentAPI {
82
82
  public pendingTracks: TrackSet = {};
83
83
  public sourceBuffer!: SourceBuffers;
84
84
 
85
- protected log: (msg: any) => void;
86
- protected warn: (msg: any, obj?: any) => void;
87
- protected error: (msg: any, obj?: any) => void;
88
-
89
85
  constructor(hls: Hls) {
86
+ super('buffer-controller', hls.logger);
90
87
  this.hls = hls;
91
- const logPrefix = '[buffer-controller]';
92
88
  this.appendSource = hls.config.preferManagedMediaSource;
93
- this.log = logger.log.bind(logger, logPrefix);
94
- this.warn = logger.warn.bind(logger, logPrefix);
95
- this.error = logger.error.bind(logger, logPrefix);
96
89
  this._initSourceBuffer();
97
90
  this.registerListeners();
98
91
  }
@@ -110,6 +103,12 @@ export default class BufferController implements ComponentAPI {
110
103
  this.lastMpegAudioChunk = null;
111
104
  // @ts-ignore
112
105
  this.hls = null;
106
+ // @ts-ignore
107
+ this._onMediaSourceOpen = this._onMediaSourceClose = null;
108
+ // @ts-ignore
109
+ this._onMediaSourceEnded = null;
110
+ // @ts-ignore
111
+ this._onStartStreaming = this._onEndStreaming = null;
113
112
  }
114
113
 
115
114
  protected registerListeners() {
@@ -296,6 +295,7 @@ export default class BufferController implements ComponentAPI {
296
295
  this.resetBuffer(type);
297
296
  });
298
297
  this._initSourceBuffer();
298
+ this.hls.resumeBuffering();
299
299
  }
300
300
 
301
301
  private resetBuffer(type: SourceBufferName) {
@@ -1004,7 +1004,7 @@ export default class BufferController implements ComponentAPI {
1004
1004
  private _onMediaEmptied = () => {
1005
1005
  const { mediaSrc, _objectUrl } = this;
1006
1006
  if (mediaSrc !== _objectUrl) {
1007
- logger.error(
1007
+ this.error(
1008
1008
  `Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`,
1009
1009
  );
1010
1010
  }
@@ -12,7 +12,6 @@ import type {
12
12
  LevelsUpdatedData,
13
13
  } from '../types/events';
14
14
  import StreamController from './stream-controller';
15
- import { logger } from '../utils/logger';
16
15
  import type { ComponentAPI } from '../types/component-api';
17
16
  import type Hls from '../hls';
18
17
 
@@ -152,7 +151,7 @@ class CapLevelController implements ComponentAPI {
152
151
  const hls = this.hls;
153
152
  const maxLevel = this.getMaxLevel(levels.length - 1);
154
153
  if (maxLevel !== this.autoLevelCapping) {
155
- logger.log(
154
+ hls.logger.log(
156
155
  `Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`,
157
156
  );
158
157
  }
@@ -5,9 +5,9 @@ import { CmcdObjectType } from '@svta/common-media-library/cmcd/CmcdObjectType';
5
5
  import { CmcdStreamingFormat } from '@svta/common-media-library/cmcd/CmcdStreamingFormat';
6
6
  import { appendCmcdHeaders } from '@svta/common-media-library/cmcd/appendCmcdHeaders';
7
7
  import { appendCmcdQuery } from '@svta/common-media-library/cmcd/appendCmcdQuery';
8
+ import type { CmcdEncodeOptions } from '@svta/common-media-library/cmcd/CmcdEncodeOptions';
8
9
  import { uuid } from '@svta/common-media-library/utils/uuid';
9
10
  import { BufferHelper } from '../utils/buffer-helper';
10
- import { logger } from '../utils/logger';
11
11
  import type { ComponentAPI } from '../types/component-api';
12
12
  import type { Fragment } from '../loader/fragment';
13
13
  import type { BufferCreatedData, MediaAttachedData } from '../types/events';
@@ -165,7 +165,7 @@ export default class CMCDController implements ComponentAPI {
165
165
  data.su = this.buffering;
166
166
  }
167
167
 
168
- // TODO: Implement rtp, nrr, nor, dl
168
+ // TODO: Implement rtp, nrr, dl
169
169
 
170
170
  const { includeKeys } = this;
171
171
  if (includeKeys) {
@@ -175,14 +175,18 @@ export default class CMCDController implements ComponentAPI {
175
175
  }, {});
176
176
  }
177
177
 
178
+ const options: CmcdEncodeOptions = {
179
+ baseUrl: context.url,
180
+ };
181
+
178
182
  if (this.useHeaders) {
179
183
  if (!context.headers) {
180
184
  context.headers = {};
181
185
  }
182
186
 
183
- appendCmcdHeaders(context.headers, data);
187
+ appendCmcdHeaders(context.headers, data, options);
184
188
  } else {
185
- context.url = appendCmcdQuery(context.url, data);
189
+ context.url = appendCmcdQuery(context.url, data, options);
186
190
  }
187
191
  }
188
192
 
@@ -196,7 +200,7 @@ export default class CMCDController implements ComponentAPI {
196
200
  su: !this.initialized,
197
201
  });
198
202
  } catch (error) {
199
- logger.warn('Could not generate manifest CMCD data.', error);
203
+ this.hls.logger.warn('Could not generate manifest CMCD data.', error);
200
204
  }
201
205
  };
202
206
 
@@ -223,12 +227,29 @@ export default class CMCDController implements ComponentAPI {
223
227
  data.bl = this.getBufferLength(ot);
224
228
  }
225
229
 
230
+ const next = this.getNextFrag(fragment);
231
+ if (next) {
232
+ if (next.url && next.url !== fragment.url) {
233
+ data.nor = next.url;
234
+ }
235
+ }
236
+
226
237
  this.apply(context, data);
227
238
  } catch (error) {
228
- logger.warn('Could not generate segment CMCD data.', error);
239
+ this.hls.logger.warn('Could not generate segment CMCD data.', error);
229
240
  }
230
241
  };
231
242
 
243
+ private getNextFrag(fragment: Fragment): Fragment | undefined {
244
+ const levelDetails = this.hls.levels[fragment.level]?.details;
245
+ if (levelDetails) {
246
+ const index = (fragment.sn as number) - levelDetails.startSN;
247
+ return levelDetails.fragments[index + 1];
248
+ }
249
+
250
+ return undefined;
251
+ }
252
+
232
253
  /**
233
254
  * The CMCD object type.
234
255
  */
@@ -3,7 +3,7 @@ import { Level } from '../types/level';
3
3
  import { reassignFragmentLevelIndexes } from '../utils/level-helper';
4
4
  import { AttrList } from '../utils/attr-list';
5
5
  import { ErrorActionFlags, NetworkErrorAction } from './error-controller';
6
- import { logger } from '../utils/logger';
6
+ import { Logger } from '../utils/logger';
7
7
  import {
8
8
  PlaylistContextType,
9
9
  type Loader,
@@ -48,9 +48,11 @@ export type UriReplacement = {
48
48
 
49
49
  const PATHWAY_PENALTY_DURATION_MS = 300000;
50
50
 
51
- export default class ContentSteeringController implements NetworkComponentAPI {
51
+ export default class ContentSteeringController
52
+ extends Logger
53
+ implements NetworkComponentAPI
54
+ {
52
55
  private readonly hls: Hls;
53
- private log: (msg: any) => void;
54
56
  private loader: Loader<LoaderContext> | null = null;
55
57
  private uri: string | null = null;
56
58
  private pathwayId: string = '.';
@@ -66,8 +68,8 @@ export default class ContentSteeringController implements NetworkComponentAPI {
66
68
  private penalizedPathways: { [pathwayId: string]: number } = {};
67
69
 
68
70
  constructor(hls: Hls) {
71
+ super('content-steering', hls.logger);
69
72
  this.hls = hls;
70
- this.log = logger.log.bind(logger, `[content-steering]:`);
71
73
  this.registerListeners();
72
74
  }
73
75
 
@@ -203,7 +205,7 @@ export default class ContentSteeringController implements NetworkComponentAPI {
203
205
  errorAction.resolved = this.pathwayId !== errorPathway;
204
206
  }
205
207
  if (!errorAction.resolved) {
206
- logger.warn(
208
+ this.warn(
207
209
  `Could not resolve ${data.details} ("${
208
210
  data.error.message
209
211
  }") with content-steering for Pathway: ${errorPathway} levels: ${
@@ -442,7 +444,7 @@ export default class ContentSteeringController implements NetworkComponentAPI {
442
444
  ) => {
443
445
  this.log(`Loaded steering manifest: "${url}"`);
444
446
  const steeringData = response.data as SteeringManifest;
445
- if (steeringData.VERSION !== 1) {
447
+ if (steeringData?.VERSION !== 1) {
446
448
  this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
447
449
  return;
448
450
  }
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { Events } from '../events';
7
7
  import { ErrorTypes, ErrorDetails } from '../errors';
8
- import { logger } from '../utils/logger';
8
+ import { Logger } from '../utils/logger';
9
9
  import {
10
10
  getKeySystemsForConfig,
11
11
  getSupportedMediaKeySystemConfigurations,
@@ -41,9 +41,6 @@ import type {
41
41
  LoaderConfiguration,
42
42
  LoaderContext,
43
43
  } from '../types/loader';
44
-
45
- const LOGGER_PREFIX = '[eme]';
46
-
47
44
  interface KeySystemAccessPromises {
48
45
  keySystemAccess: Promise<MediaKeySystemAccess>;
49
46
  mediaKeys?: Promise<MediaKeys>;
@@ -68,7 +65,7 @@ export interface MediaKeySessionContext {
68
65
  * @class
69
66
  * @constructor
70
67
  */
71
- class EMEController implements ComponentAPI {
68
+ class EMEController extends Logger implements ComponentAPI {
72
69
  public static CDMCleanupPromise: Promise<void> | void;
73
70
 
74
71
  private readonly hls: Hls;
@@ -90,15 +87,9 @@ class EMEController implements ComponentAPI {
90
87
  private setMediaKeysQueue: Promise<void>[] = EMEController.CDMCleanupPromise
91
88
  ? [EMEController.CDMCleanupPromise]
92
89
  : [];
93
- private onMediaEncrypted = this._onMediaEncrypted.bind(this);
94
- private onWaitingForKey = this._onWaitingForKey.bind(this);
95
-
96
- private debug: (msg: any) => void = logger.debug.bind(logger, LOGGER_PREFIX);
97
- private log: (msg: any) => void = logger.log.bind(logger, LOGGER_PREFIX);
98
- private warn: (msg: any) => void = logger.warn.bind(logger, LOGGER_PREFIX);
99
- private error: (msg: any) => void = logger.error.bind(logger, LOGGER_PREFIX);
100
90
 
101
91
  constructor(hls: Hls) {
92
+ super('eme', hls.logger);
102
93
  this.hls = hls;
103
94
  this.config = hls.config;
104
95
  this.registerListeners();
@@ -113,13 +104,9 @@ class EMEController implements ComponentAPI {
113
104
  config.licenseXhrSetup = config.licenseResponseCallback = undefined;
114
105
  config.drmSystems = config.drmSystemOptions = {};
115
106
  // @ts-ignore
116
- this.hls =
117
- this.onMediaEncrypted =
118
- this.onWaitingForKey =
119
- this.keyIdToKeySessionPromise =
120
- null as any;
107
+ this.hls = this.config = this.keyIdToKeySessionPromise = null;
121
108
  // @ts-ignore
122
- this.config = null;
109
+ this.onMediaEncrypted = this.onWaitingForKey = null;
123
110
  }
124
111
 
125
112
  private registerListeners() {
@@ -523,7 +510,7 @@ class EMEController implements ComponentAPI {
523
510
  return this.attemptKeySystemAccess(keySystemsToAttempt);
524
511
  }
525
512
 
526
- private _onMediaEncrypted(event: MediaEncryptedEvent) {
513
+ private onMediaEncrypted = (event: MediaEncryptedEvent) => {
527
514
  const { initDataType, initData } = event;
528
515
  this.debug(`"${event.type}" event: init data type: "${initDataType}"`);
529
516
 
@@ -639,11 +626,11 @@ class EMEController implements ComponentAPI {
639
626
  );
640
627
  }
641
628
  keySessionContextPromise.catch((error) => this.handleError(error));
642
- }
629
+ };
643
630
 
644
- private _onWaitingForKey(event: Event) {
631
+ private onWaitingForKey = (event: Event) => {
645
632
  this.log(`"${event.type}" event`);
646
- }
633
+ };
647
634
 
648
635
  private attemptSetMediaKeys(
649
636
  keySystem: KeySystems,
@@ -8,7 +8,7 @@ import {
8
8
  } from '../utils/error-helper';
9
9
  import { findFragmentByPTS } from './fragment-finders';
10
10
  import { HdcpLevel, HdcpLevels } from '../types/level';
11
- import { logger } from '../utils/logger';
11
+ import { Logger } from '../utils/logger';
12
12
  import type Hls from '../hls';
13
13
  import type { RetryConfig } from '../config';
14
14
  import type { NetworkComponentAPI } from '../types/component-api';
@@ -50,19 +50,17 @@ type PenalizedRendition = {
50
50
 
51
51
  type PenalizedRenditions = { [key: number]: PenalizedRendition };
52
52
 
53
- export default class ErrorController implements NetworkComponentAPI {
53
+ export default class ErrorController
54
+ extends Logger
55
+ implements NetworkComponentAPI
56
+ {
54
57
  private readonly hls: Hls;
55
58
  private playlistError: number = 0;
56
59
  private penalizedRenditions: PenalizedRenditions = {};
57
- private log: (msg: any) => void;
58
- private warn: (msg: any) => void;
59
- private error: (msg: any) => void;
60
60
 
61
61
  constructor(hls: Hls) {
62
+ super('error-controller', hls.logger);
62
63
  this.hls = hls;
63
- this.log = logger.log.bind(logger, `[info]:`);
64
- this.warn = logger.warn.bind(logger, `[warning]:`);
65
- this.error = logger.error.bind(logger, `[error]:`);
66
64
  this.registerListeners();
67
65
  }
68
66