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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/README.md +4 -3
  2. package/dist/hls-demo.js +41 -38
  3. package/dist/hls-demo.js.map +1 -1
  4. package/dist/hls.js +4211 -2666
  5. package/dist/hls.js.d.ts +179 -110
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +2841 -1921
  8. package/dist/hls.light.js.map +1 -1
  9. package/dist/hls.light.min.js +1 -1
  10. package/dist/hls.light.min.js.map +1 -1
  11. package/dist/hls.light.mjs +2569 -1639
  12. package/dist/hls.light.mjs.map +1 -1
  13. package/dist/hls.min.js +1 -1
  14. package/dist/hls.min.js.map +1 -1
  15. package/dist/hls.mjs +3572 -2017
  16. package/dist/hls.mjs.map +1 -1
  17. package/dist/hls.worker.js +1 -1
  18. package/dist/hls.worker.js.map +1 -1
  19. package/package.json +38 -38
  20. package/src/config.ts +5 -2
  21. package/src/controller/abr-controller.ts +39 -25
  22. package/src/controller/audio-stream-controller.ts +156 -136
  23. package/src/controller/audio-track-controller.ts +1 -1
  24. package/src/controller/base-playlist-controller.ts +27 -10
  25. package/src/controller/base-stream-controller.ts +234 -89
  26. package/src/controller/buffer-controller.ts +250 -97
  27. package/src/controller/buffer-operation-queue.ts +16 -19
  28. package/src/controller/cap-level-controller.ts +3 -2
  29. package/src/controller/cmcd-controller.ts +51 -14
  30. package/src/controller/content-steering-controller.ts +29 -15
  31. package/src/controller/eme-controller.ts +10 -23
  32. package/src/controller/error-controller.ts +28 -22
  33. package/src/controller/fps-controller.ts +8 -3
  34. package/src/controller/fragment-finders.ts +44 -16
  35. package/src/controller/fragment-tracker.ts +58 -25
  36. package/src/controller/gap-controller.ts +43 -16
  37. package/src/controller/id3-track-controller.ts +45 -35
  38. package/src/controller/latency-controller.ts +18 -13
  39. package/src/controller/level-controller.ts +37 -19
  40. package/src/controller/stream-controller.ts +100 -83
  41. package/src/controller/subtitle-stream-controller.ts +35 -47
  42. package/src/controller/subtitle-track-controller.ts +5 -3
  43. package/src/controller/timeline-controller.ts +20 -22
  44. package/src/crypt/aes-crypto.ts +21 -2
  45. package/src/crypt/decrypter-aes-mode.ts +4 -0
  46. package/src/crypt/decrypter.ts +32 -16
  47. package/src/crypt/fast-aes-key.ts +28 -5
  48. package/src/demux/audio/aacdemuxer.ts +2 -2
  49. package/src/demux/audio/ac3-demuxer.ts +4 -3
  50. package/src/demux/audio/adts.ts +9 -4
  51. package/src/demux/audio/base-audio-demuxer.ts +16 -14
  52. package/src/demux/audio/mp3demuxer.ts +4 -3
  53. package/src/demux/audio/mpegaudio.ts +1 -1
  54. package/src/demux/mp4demuxer.ts +7 -7
  55. package/src/demux/sample-aes.ts +2 -0
  56. package/src/demux/transmuxer-interface.ts +8 -16
  57. package/src/demux/transmuxer-worker.ts +4 -4
  58. package/src/demux/transmuxer.ts +16 -3
  59. package/src/demux/tsdemuxer.ts +75 -38
  60. package/src/demux/video/avc-video-parser.ts +210 -121
  61. package/src/demux/video/base-video-parser.ts +135 -2
  62. package/src/demux/video/exp-golomb.ts +0 -208
  63. package/src/demux/video/hevc-video-parser.ts +749 -0
  64. package/src/events.ts +8 -1
  65. package/src/exports-named.ts +1 -1
  66. package/src/hls.ts +84 -47
  67. package/src/loader/date-range.ts +71 -5
  68. package/src/loader/fragment-loader.ts +23 -21
  69. package/src/loader/fragment.ts +8 -4
  70. package/src/loader/key-loader.ts +3 -1
  71. package/src/loader/level-details.ts +6 -6
  72. package/src/loader/level-key.ts +10 -9
  73. package/src/loader/m3u8-parser.ts +138 -144
  74. package/src/loader/playlist-loader.ts +5 -7
  75. package/src/remux/mp4-generator.ts +196 -1
  76. package/src/remux/mp4-remuxer.ts +32 -62
  77. package/src/remux/passthrough-remuxer.ts +1 -1
  78. package/src/task-loop.ts +5 -2
  79. package/src/types/component-api.ts +3 -1
  80. package/src/types/demuxer.ts +3 -0
  81. package/src/types/events.ts +19 -6
  82. package/src/types/fragment-tracker.ts +2 -2
  83. package/src/types/media-playlist.ts +9 -1
  84. package/src/types/remuxer.ts +1 -1
  85. package/src/utils/attr-list.ts +96 -9
  86. package/src/utils/buffer-helper.ts +12 -31
  87. package/src/utils/cea-608-parser.ts +1 -3
  88. package/src/utils/codecs.ts +34 -5
  89. package/src/utils/encryption-methods-util.ts +21 -0
  90. package/src/utils/fetch-loader.ts +1 -1
  91. package/src/utils/hash.ts +10 -0
  92. package/src/utils/hdr.ts +4 -7
  93. package/src/utils/imsc1-ttml-parser.ts +1 -1
  94. package/src/utils/keysystem-util.ts +1 -6
  95. package/src/utils/level-helper.ts +71 -44
  96. package/src/utils/logger.ts +58 -23
  97. package/src/utils/mp4-tools.ts +5 -3
  98. package/src/utils/rendition-helper.ts +100 -74
  99. package/src/utils/utf8-utils.ts +18 -0
  100. package/src/utils/variable-substitution.ts +0 -19
  101. package/src/utils/webvtt-parser.ts +2 -12
  102. package/src/demux/id3.ts +0 -411
  103. package/src/types/general.ts +0 -6
@@ -35,19 +35,13 @@ export class BufferHelper {
35
35
  * Return true if `media`'s buffered include `position`
36
36
  */
37
37
  static isBuffered(media: Bufferable, position: number): boolean {
38
- try {
39
- if (media) {
40
- const buffered = BufferHelper.getBuffered(media);
41
- for (let i = 0; i < buffered.length; i++) {
42
- if (position >= buffered.start(i) && position <= buffered.end(i)) {
43
- return true;
44
- }
38
+ if (media) {
39
+ const buffered = BufferHelper.getBuffered(media);
40
+ for (let i = buffered.length; i--; ) {
41
+ if (position >= buffered.start(i) && position <= buffered.end(i)) {
42
+ return true;
45
43
  }
46
44
  }
47
- } catch (error) {
48
- // this is to catch
49
- // InvalidStateError: Failed to read the 'buffered' property from 'SourceBuffer':
50
- // This SourceBuffer has been removed from the parent media source
51
45
  }
52
46
  return false;
53
47
  }
@@ -57,21 +51,15 @@ export class BufferHelper {
57
51
  pos: number,
58
52
  maxHoleDuration: number,
59
53
  ): BufferInfo {
60
- try {
61
- if (media) {
62
- const vbuffered = BufferHelper.getBuffered(media);
54
+ if (media) {
55
+ const vbuffered = BufferHelper.getBuffered(media);
56
+ if (vbuffered.length) {
63
57
  const buffered: BufferTimeRange[] = [];
64
- let i: number;
65
- for (i = 0; i < vbuffered.length; i++) {
58
+ for (let i = 0; i < vbuffered.length; i++) {
66
59
  buffered.push({ start: vbuffered.start(i), end: vbuffered.end(i) });
67
60
  }
68
-
69
- return this.bufferedInfo(buffered, pos, maxHoleDuration);
61
+ return BufferHelper.bufferedInfo(buffered, pos, maxHoleDuration);
70
62
  }
71
- } catch (error) {
72
- // this is to catch
73
- // InvalidStateError: Failed to read the 'buffered' property from 'SourceBuffer':
74
- // This SourceBuffer has been removed from the parent media source
75
63
  }
76
64
  return { len: 0, start: pos, end: pos, nextStart: undefined };
77
65
  }
@@ -88,14 +76,7 @@ export class BufferHelper {
88
76
  } {
89
77
  pos = Math.max(0, pos);
90
78
  // sort on buffer.start/smaller end (IE does not always return sorted buffered range)
91
- buffered.sort(function (a, b) {
92
- const diff = a.start - b.start;
93
- if (diff) {
94
- return diff;
95
- } else {
96
- return b.end - a.end;
97
- }
98
- });
79
+ buffered.sort((a, b) => a.start - b.start || b.end - a.end);
99
80
 
100
81
  let buffered2: BufferTimeRange[] = [];
101
82
  if (maxHoleDuration) {
@@ -164,7 +145,7 @@ export class BufferHelper {
164
145
  */
165
146
  static getBuffered(media: Bufferable): TimeRanges {
166
147
  try {
167
- return media.buffered;
148
+ return media.buffered || noopBuffered;
168
149
  } catch (e) {
169
150
  logger.log('failed to get media.buffered', e);
170
151
  return noopBuffered;
@@ -1331,9 +1331,7 @@ class Cea608Parser {
1331
1331
  if (charCodes) {
1332
1332
  this.logger.log(
1333
1333
  VerboseLevel.DEBUG,
1334
- () =>
1335
- 'Char codes = ' +
1336
- numArrayToHexArray(charCodes as number[]).join(','),
1334
+ () => 'Char codes = ' + numArrayToHexArray(charCodes).join(','),
1337
1335
  );
1338
1336
  }
1339
1337
  return charCodes;
@@ -147,12 +147,15 @@ function getCodecCompatibleNameLower(
147
147
  return CODEC_COMPATIBLE_NAMES[lowerCaseCodec]!;
148
148
  }
149
149
 
150
- // Idealy fLaC and Opus would be first (spec-compliant) but
151
- // some browsers will report that fLaC is supported then fail.
152
- // see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
153
150
  const codecsToCheck = {
151
+ // Idealy fLaC and Opus would be first (spec-compliant) but
152
+ // some browsers will report that fLaC is supported then fail.
153
+ // see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
154
154
  flac: ['flac', 'fLaC', 'FLAC'],
155
155
  opus: ['opus', 'Opus'],
156
+ // Replace audio codec info if browser does not support mp4a.40.34,
157
+ // and demuxer can fallback to 'audio/mpeg' or 'audio/mp4;codecs="mp3"'
158
+ 'mp4a.40.34': ['mp3'],
156
159
  }[lowerCaseCodec];
157
160
 
158
161
  for (let i = 0; i < codecsToCheck.length; i++) {
@@ -165,13 +168,18 @@ function getCodecCompatibleNameLower(
165
168
  ) {
166
169
  CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
167
170
  return codecsToCheck[i];
171
+ } else if (
172
+ codecsToCheck[i] === 'mp3' &&
173
+ getMediaSource(preferManagedMediaSource)?.isTypeSupported('audio/mpeg')
174
+ ) {
175
+ return '';
168
176
  }
169
177
  }
170
178
 
171
179
  return lowerCaseCodec;
172
180
  }
173
181
 
174
- const AUDIO_CODEC_REGEXP = /flac|opus/i;
182
+ const AUDIO_CODEC_REGEXP = /flac|opus|mp4a\.40\.34/i;
175
183
  export function getCodecCompatibleName(
176
184
  codec: string,
177
185
  preferManagedMediaSource = true,
@@ -185,7 +193,7 @@ export function getCodecCompatibleName(
185
193
  }
186
194
 
187
195
  export function pickMostCompleteCodecName(
188
- parsedCodec: string,
196
+ parsedCodec: string | undefined,
189
197
  levelCodec: string | undefined,
190
198
  ): string | undefined {
191
199
  // Parsing of mp4a codecs strings in mp4-tools from media is incomplete as of d8c6c7a
@@ -213,3 +221,24 @@ export function convertAVC1ToAVCOTI(codec: string) {
213
221
  }
214
222
  return codecs.join(',');
215
223
  }
224
+
225
+ export interface TypeSupported {
226
+ mpeg: boolean;
227
+ mp3: boolean;
228
+ ac3: boolean;
229
+ }
230
+
231
+ export function getM2TSSupportedAudioTypes(
232
+ preferManagedMediaSource: boolean,
233
+ ): TypeSupported {
234
+ const MediaSource = getMediaSource(preferManagedMediaSource) || {
235
+ isTypeSupported: () => false,
236
+ };
237
+ return {
238
+ mpeg: MediaSource.isTypeSupported('audio/mpeg'),
239
+ mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
240
+ ac3: __USE_M2TS_ADVANCED_CODECS__
241
+ ? MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
242
+ : false,
243
+ };
244
+ }
@@ -0,0 +1,21 @@
1
+ import { DecrypterAesMode } from '../crypt/decrypter-aes-mode';
2
+
3
+ export function isFullSegmentEncryption(method: string): boolean {
4
+ return (
5
+ method === 'AES-128' || method === 'AES-256' || method === 'AES-256-CTR'
6
+ );
7
+ }
8
+
9
+ export function getAesModeFromFullSegmentMethod(
10
+ method: string,
11
+ ): DecrypterAesMode {
12
+ switch (method) {
13
+ case 'AES-128':
14
+ case 'AES-256':
15
+ return DecrypterAesMode.cbc;
16
+ case 'AES-256-CTR':
17
+ return DecrypterAesMode.ctr;
18
+ default:
19
+ throw new Error(`invalid full segment method ${method}`);
20
+ }
21
+ }
@@ -1,4 +1,4 @@
1
- import {
1
+ import type {
2
2
  LoaderCallbacks,
3
3
  LoaderContext,
4
4
  Loader,
@@ -0,0 +1,10 @@
1
+ // From https://github.com/darkskyapp/string-hash
2
+ export function hash(text: string) {
3
+ let hash = 5381;
4
+ let i = text.length;
5
+ while (i) {
6
+ hash = (hash * 33) ^ text.charCodeAt(--i);
7
+ }
8
+
9
+ return (hash >>> 0).toString();
10
+ }
package/src/utils/hdr.ts CHANGED
@@ -49,16 +49,13 @@ export function getVideoSelectionOptions(
49
49
  if (videoPreference) {
50
50
  allowedVideoRanges =
51
51
  videoPreference.allowedVideoRanges || VideoRangeValues.slice(0);
52
+ const allowAutoPreferHDR =
53
+ allowedVideoRanges.join('') !== 'SDR' && !videoPreference.videoCodec;
52
54
  preferHDR =
53
55
  videoPreference.preferHDR !== undefined
54
56
  ? videoPreference.preferHDR
55
- : isHdrSupported();
56
-
57
- if (preferHDR) {
58
- allowedVideoRanges = allowedVideoRanges.filter(
59
- (range: VideoRange) => range !== 'SDR',
60
- );
61
- } else {
57
+ : allowAutoPreferHDR && isHdrSupported();
58
+ if (!preferHDR) {
62
59
  allowedVideoRanges = ['SDR'];
63
60
  }
64
61
  }
@@ -1,7 +1,7 @@
1
1
  import { findBox } from './mp4-tools';
2
2
  import { parseTimeStamp } from './vttparser';
3
3
  import VTTCue from './vttcue';
4
- import { utf8ArrayToStr } from '../demux/id3';
4
+ import { utf8ArrayToStr } from '@svta/common-media-library/utils/utf8ArrayToStr';
5
5
  import {
6
6
  RationalTimestamp,
7
7
  toTimescaleFromScale,
@@ -1,4 +1,5 @@
1
1
  import { base64Decode } from './numeric-encoding-utils';
2
+ import { strToUtf8array } from './utf8-utils';
2
3
 
3
4
  function getKeyIdBytes(str: string): Uint8Array {
4
5
  const keyIdbytes = strToUtf8array(str).subarray(0, 16);
@@ -40,9 +41,3 @@ export function convertDataUriToArrayBytes(uri: string): Uint8Array | null {
40
41
  }
41
42
  return keydata;
42
43
  }
43
-
44
- export function strToUtf8array(str: string): Uint8Array {
45
- return Uint8Array.from(unescape(encodeURIComponent(str)), (c) =>
46
- c.charCodeAt(0),
47
- );
48
- }
@@ -3,16 +3,17 @@
3
3
  */
4
4
 
5
5
  import { logger } from './logger';
6
- import { Fragment, Part } from '../loader/fragment';
7
- import { LevelDetails } from '../loader/level-details';
8
- import type { Level } from '../types/level';
9
6
  import { DateRange } from '../loader/date-range';
7
+ import { assignProgramDateTime, mapDateRanges } from '../loader/m3u8-parser';
8
+ import type { Fragment, MediaFragment, Part } from '../loader/fragment';
9
+ import type { LevelDetails } from '../loader/level-details';
10
+ import type { Level } from '../types/level';
10
11
 
11
12
  type FragmentIntersection = (oldFrag: Fragment, newFrag: Fragment) => void;
12
13
  type PartIntersection = (oldPart: Part, newPart: Part) => void;
13
14
 
14
15
  export function updatePTS(
15
- fragments: Fragment[],
16
+ fragments: MediaFragment[],
16
17
  fromIdx: number,
17
18
  toIdx: number,
18
19
  ): void {
@@ -21,7 +22,7 @@ export function updatePTS(
21
22
  updateFromToPTS(fragFrom, fragTo);
22
23
  }
23
24
 
24
- function updateFromToPTS(fragFrom: Fragment, fragTo: Fragment) {
25
+ function updateFromToPTS(fragFrom: MediaFragment, fragTo: MediaFragment) {
25
26
  const fragToPTS = fragTo.startPTS as number;
26
27
  // if we know startPTS[toIdx]
27
28
  if (Number.isFinite(fragToPTS)) {
@@ -55,7 +56,7 @@ function updateFromToPTS(fragFrom: Fragment, fragTo: Fragment) {
55
56
 
56
57
  export function updateFragPTSDTS(
57
58
  details: LevelDetails | undefined,
58
- frag: Fragment,
59
+ frag: MediaFragment,
59
60
  startPTS: number,
60
61
  endPTS: number,
61
62
  startDTS: number,
@@ -82,11 +83,11 @@ export function updateFragPTSDTS(
82
83
 
83
84
  maxStartPTS = Math.max(startPTS, fragStartPts);
84
85
  startPTS = Math.min(startPTS, fragStartPts);
85
- startDTS = Math.min(startDTS, frag.startDTS);
86
+ startDTS = Math.min(startDTS, frag.startDTS as number);
86
87
 
87
88
  minEndPTS = Math.min(endPTS, fragEndPts);
88
89
  endPTS = Math.max(endPTS, fragEndPts);
89
- endDTS = Math.max(endDTS, frag.endDTS);
90
+ endDTS = Math.max(endDTS, frag.endDTS as number);
90
91
  }
91
92
 
92
93
  const drift = startPTS - frag.start;
@@ -101,7 +102,7 @@ export function updateFragPTSDTS(
101
102
  frag.minEndPTS = minEndPTS;
102
103
  frag.endDTS = endDTS;
103
104
 
104
- const sn = frag.sn as number; // 'initSegment'
105
+ const sn = frag.sn;
105
106
  // exit if sn out of range
106
107
  if (!details || sn < details.startSN || sn > details.endSN) {
107
108
  return 0;
@@ -196,10 +197,10 @@ export function mergeDetails(
196
197
  },
197
198
  );
198
199
 
200
+ const fragmentsToCheck = newDetails.fragmentHint
201
+ ? newDetails.fragments.concat(newDetails.fragmentHint)
202
+ : newDetails.fragments;
199
203
  if (currentInitSegment) {
200
- const fragmentsToCheck = newDetails.fragmentHint
201
- ? newDetails.fragments.concat(newDetails.fragmentHint)
202
- : newDetails.fragments;
203
204
  fragmentsToCheck.forEach((frag) => {
204
205
  if (
205
206
  frag &&
@@ -220,14 +221,30 @@ export function mergeDetails(
220
221
  for (let i = newDetails.skippedSegments; i--; ) {
221
222
  newDetails.fragments.shift();
222
223
  }
223
- newDetails.startSN = newDetails.fragments[0].sn as number;
224
+ newDetails.startSN = newDetails.fragments[0].sn;
224
225
  newDetails.startCC = newDetails.fragments[0].cc;
225
- } else if (newDetails.canSkipDateRanges) {
226
- newDetails.dateRanges = mergeDateRanges(
227
- oldDetails.dateRanges,
228
- newDetails.dateRanges,
229
- newDetails.recentlyRemovedDateranges,
226
+ } else {
227
+ if (newDetails.canSkipDateRanges) {
228
+ newDetails.dateRanges = mergeDateRanges(
229
+ oldDetails.dateRanges,
230
+ newDetails,
231
+ );
232
+ }
233
+ const programDateTimes = oldDetails.fragments.filter(
234
+ (frag) => frag.rawProgramDateTime,
230
235
  );
236
+ if (oldDetails.hasProgramDateTime && !newDetails.hasProgramDateTime) {
237
+ for (let i = 1; i < fragmentsToCheck.length; i++) {
238
+ if (fragmentsToCheck[i].programDateTime === null) {
239
+ assignProgramDateTime(
240
+ fragmentsToCheck[i],
241
+ fragmentsToCheck[i - 1],
242
+ programDateTimes,
243
+ );
244
+ }
245
+ }
246
+ }
247
+ mapDateRanges(programDateTimes, newDetails);
231
248
  }
232
249
  }
233
250
 
@@ -293,27 +310,38 @@ export function mergeDetails(
293
310
 
294
311
  function mergeDateRanges(
295
312
  oldDateRanges: Record<string, DateRange>,
296
- deltaDateRanges: Record<string, DateRange>,
297
- recentlyRemovedDateranges: string[] | undefined,
313
+ newDetails: LevelDetails,
298
314
  ): Record<string, DateRange> {
315
+ const { dateRanges: deltaDateRanges, recentlyRemovedDateranges } = newDetails;
299
316
  const dateRanges = Object.assign({}, oldDateRanges);
300
317
  if (recentlyRemovedDateranges) {
301
318
  recentlyRemovedDateranges.forEach((id) => {
302
319
  delete dateRanges[id];
303
320
  });
304
321
  }
305
- Object.keys(deltaDateRanges).forEach((id) => {
306
- const dateRange = new DateRange(deltaDateRanges[id].attr, dateRanges[id]);
307
- if (dateRange.isValid) {
308
- dateRanges[id] = dateRange;
309
- } else {
310
- logger.warn(
311
- `Ignoring invalid Playlist Delta Update DATERANGE tag: "${JSON.stringify(
312
- deltaDateRanges[id].attr,
313
- )}"`,
322
+ const mergeIds = Object.keys(dateRanges);
323
+ const mergeCount = mergeIds.length;
324
+ if (mergeCount) {
325
+ Object.keys(deltaDateRanges).forEach((id) => {
326
+ const mergedDateRange = dateRanges[id];
327
+ const dateRange = new DateRange(
328
+ deltaDateRanges[id].attr,
329
+ mergedDateRange,
314
330
  );
315
- }
316
- });
331
+ if (dateRange.isValid) {
332
+ dateRanges[id] = dateRange;
333
+ if (!mergedDateRange) {
334
+ dateRange.tagOrder += mergeCount;
335
+ }
336
+ } else {
337
+ logger.warn(
338
+ `Ignoring invalid Playlist Delta Update DATERANGE tag: "${JSON.stringify(
339
+ deltaDateRanges[id].attr,
340
+ )}"`,
341
+ );
342
+ }
343
+ });
344
+ }
317
345
  return dateRanges;
318
346
  }
319
347
 
@@ -366,7 +394,7 @@ export function mapFragmentIntersection(
366
394
  for (let i = start; i <= end; i++) {
367
395
  const oldFrag = oldFrags[delta + i];
368
396
  let newFrag = newFrags[i];
369
- if (skippedSegments && !newFrag && i < skippedSegments) {
397
+ if (skippedSegments && !newFrag && oldFrag) {
370
398
  // Fill in skipped segments in delta playlist
371
399
  newFrag = newDetails.fragments[i] = oldFrag;
372
400
  }
@@ -433,38 +461,37 @@ export function computeReloadInterval(
433
461
  }
434
462
 
435
463
  export function getFragmentWithSN(
436
- level: Level,
464
+ details: LevelDetails | undefined,
437
465
  sn: number,
438
466
  fragCurrent: Fragment | null,
439
- ): Fragment | null {
440
- if (!level?.details) {
467
+ ): MediaFragment | null {
468
+ if (!details) {
441
469
  return null;
442
470
  }
443
- const levelDetails = level.details;
444
- let fragment: Fragment | undefined =
445
- levelDetails.fragments[sn - levelDetails.startSN];
471
+ let fragment: MediaFragment | undefined =
472
+ details.fragments[sn - details.startSN];
446
473
  if (fragment) {
447
474
  return fragment;
448
475
  }
449
- fragment = levelDetails.fragmentHint;
476
+ fragment = details.fragmentHint;
450
477
  if (fragment && fragment.sn === sn) {
451
478
  return fragment;
452
479
  }
453
- if (sn < levelDetails.startSN && fragCurrent && fragCurrent.sn === sn) {
454
- return fragCurrent;
480
+ if (sn < details.startSN && fragCurrent && fragCurrent.sn === sn) {
481
+ return fragCurrent as MediaFragment;
455
482
  }
456
483
  return null;
457
484
  }
458
485
 
459
486
  export function getPartWith(
460
- level: Level,
487
+ details: LevelDetails | undefined,
461
488
  sn: number,
462
489
  partIndex: number,
463
490
  ): Part | null {
464
- if (!level?.details) {
491
+ if (!details) {
465
492
  return null;
466
493
  }
467
- return findPart(level.details?.partList, sn, partIndex);
494
+ return findPart(details.partList, sn, partIndex);
468
495
  }
469
496
 
470
497
  export function findPart(
@@ -11,6 +11,25 @@ export interface ILogger {
11
11
  error: ILogFunction;
12
12
  }
13
13
 
14
+ export class Logger implements ILogger {
15
+ trace: ILogFunction;
16
+ debug: ILogFunction;
17
+ log: ILogFunction;
18
+ warn: ILogFunction;
19
+ info: ILogFunction;
20
+ error: ILogFunction;
21
+
22
+ constructor(label: string, logger: ILogger) {
23
+ const lb = `[${label}]:`;
24
+ this.trace = noop;
25
+ this.debug = logger.debug.bind(null, lb);
26
+ this.log = logger.log.bind(null, lb);
27
+ this.warn = logger.warn.bind(null, lb);
28
+ this.info = logger.info.bind(null, lb);
29
+ this.error = logger.error.bind(null, lb);
30
+ }
31
+ }
32
+
14
33
  const noop: ILogFunction = function () {};
15
34
 
16
35
  const fakeLogger: ILogger = {
@@ -22,7 +41,9 @@ const fakeLogger: ILogger = {
22
41
  error: noop,
23
42
  };
24
43
 
25
- let exportedLogger: ILogger = fakeLogger;
44
+ function createLogger() {
45
+ return Object.assign({}, fakeLogger);
46
+ }
26
47
 
27
48
  // let lastCallTime;
28
49
  // function formatMsgWithTimeInfo(type, msg) {
@@ -33,33 +54,37 @@ let exportedLogger: ILogger = fakeLogger;
33
54
  // return msg;
34
55
  // }
35
56
 
36
- function consolePrintFn(type: string): ILogFunction {
57
+ function consolePrintFn(type: string, id: string | undefined): ILogFunction {
37
58
  const func: ILogFunction = self.console[type];
38
- if (func) {
39
- return func.bind(self.console, `[${type}] >`);
40
- }
41
- return noop;
59
+ return func
60
+ ? func.bind(self.console, `${id ? '[' + id + '] ' : ''}[${type}] >`)
61
+ : noop;
42
62
  }
43
63
 
44
- function exportLoggerFunctions(
45
- debugConfig: boolean | ILogger,
46
- ...functions: string[]
47
- ): void {
48
- functions.forEach(function (type) {
49
- exportedLogger[type] = debugConfig[type]
50
- ? debugConfig[type].bind(debugConfig)
51
- : consolePrintFn(type);
52
- });
64
+ function getLoggerFn(
65
+ key: string,
66
+ debugConfig: boolean | Partial<ILogger>,
67
+ id?: string,
68
+ ): ILogFunction {
69
+ return debugConfig[key]
70
+ ? debugConfig[key].bind(debugConfig)
71
+ : consolePrintFn(key, id);
53
72
  }
54
73
 
55
- export function enableLogs(debugConfig: boolean | ILogger, id: string): void {
74
+ const exportedLogger: ILogger = createLogger();
75
+
76
+ export function enableLogs(
77
+ debugConfig: boolean | ILogger,
78
+ context: string,
79
+ id?: string | undefined,
80
+ ): ILogger {
56
81
  // check that console is available
82
+ const newLogger = createLogger();
57
83
  if (
58
84
  (typeof console === 'object' && debugConfig === true) ||
59
85
  typeof debugConfig === 'object'
60
86
  ) {
61
- exportLoggerFunctions(
62
- debugConfig,
87
+ const keys: (keyof ILogger)[] = [
63
88
  // Remove out from list here to hard-disable a log-level
64
89
  // 'trace',
65
90
  'debug',
@@ -67,19 +92,29 @@ export function enableLogs(debugConfig: boolean | ILogger, id: string): void {
67
92
  'info',
68
93
  'warn',
69
94
  'error',
70
- );
95
+ ];
96
+ keys.forEach((key) => {
97
+ newLogger[key] = getLoggerFn(key, debugConfig, id);
98
+ });
71
99
  // Some browsers don't allow to use bind on console object anyway
72
100
  // fallback to default if needed
73
101
  try {
74
- exportedLogger.log(
75
- `Debug logs enabled for "${id}" in hls.js version ${__VERSION__}`,
102
+ newLogger.log(
103
+ `Debug logs enabled for "${context}" in hls.js version ${__VERSION__}`,
76
104
  );
77
105
  } catch (e) {
78
- exportedLogger = fakeLogger;
106
+ /* log fn threw an exception. All logger methods are no-ops. */
107
+ return createLogger();
79
108
  }
109
+ // global exported logger uses the same functions as new logger without `id`
110
+ keys.forEach((key) => {
111
+ exportedLogger[key] = getLoggerFn(key, debugConfig);
112
+ });
80
113
  } else {
81
- exportedLogger = fakeLogger;
114
+ // Reset global exported logger
115
+ Object.assign(exportedLogger, newLogger);
82
116
  }
117
+ return newLogger;
83
118
  }
84
119
 
85
120
  export const logger: ILogger = exportedLogger;
@@ -1,6 +1,6 @@
1
1
  import { ElementaryStreamTypes } from '../loader/fragment';
2
2
  import { sliceUint8 } from './typed-array';
3
- import { utf8ArrayToStr } from '../demux/id3';
3
+ import { utf8ArrayToStr } from '@svta/common-media-library/utils/utf8ArrayToStr';
4
4
  import { logger } from '../utils/logger';
5
5
  import Hex from './hex';
6
6
  import type { PassthroughTrack, UserdataSample } from '../types/demuxer';
@@ -327,7 +327,7 @@ function parseStsd(stsd: Uint8Array): { codec: string; encrypted: boolean } {
327
327
  case 'mp4a': {
328
328
  const codecBox = findBox(sampleEntries, [fourCC])[0];
329
329
  const esdsBox = findBox(codecBox.subarray(28), ['esds'])[0];
330
- if (esdsBox && esdsBox.length > 12) {
330
+ if (esdsBox && esdsBox.length > 7) {
331
331
  let i = 4;
332
332
  // ES Descriptor tag
333
333
  if (esdsBox[i++] !== 0x03) {
@@ -478,7 +478,9 @@ function parseStsd(stsd: Uint8Array): { codec: string; encrypted: boolean } {
478
478
 
479
479
  function skipBERInteger(bytes: Uint8Array, i: number): number {
480
480
  const limit = i + 5;
481
- while (bytes[i++] & 0x80 && i < limit) {}
481
+ while (bytes[i++] & 0x80 && i < limit) {
482
+ /* do nothing */
483
+ }
482
484
  return i;
483
485
  }
484
486