hls.js 1.5.14-0.canary.10517 → 1.5.14

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 (107) hide show
  1. package/README.md +3 -4
  2. package/dist/hls-demo.js +38 -41
  3. package/dist/hls-demo.js.map +1 -1
  4. package/dist/hls.js +2903 -4542
  5. package/dist/hls.js.d.ts +112 -186
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +2284 -3295
  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 +1804 -2817
  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 +4652 -6293
  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 +2 -5
  21. package/src/controller/abr-controller.ts +25 -39
  22. package/src/controller/audio-stream-controller.ts +136 -156
  23. package/src/controller/audio-track-controller.ts +1 -1
  24. package/src/controller/base-playlist-controller.ts +10 -27
  25. package/src/controller/base-stream-controller.ts +107 -263
  26. package/src/controller/buffer-controller.ts +98 -252
  27. package/src/controller/buffer-operation-queue.ts +19 -16
  28. package/src/controller/cap-level-controller.ts +2 -3
  29. package/src/controller/cmcd-controller.ts +14 -51
  30. package/src/controller/content-steering-controller.ts +15 -29
  31. package/src/controller/eme-controller.ts +23 -10
  32. package/src/controller/error-controller.ts +22 -28
  33. package/src/controller/fps-controller.ts +3 -8
  34. package/src/controller/fragment-finders.ts +16 -44
  35. package/src/controller/fragment-tracker.ts +25 -58
  36. package/src/controller/gap-controller.ts +16 -43
  37. package/src/controller/id3-track-controller.ts +35 -45
  38. package/src/controller/latency-controller.ts +13 -18
  39. package/src/controller/level-controller.ts +19 -37
  40. package/src/controller/stream-controller.ts +83 -100
  41. package/src/controller/subtitle-stream-controller.ts +47 -35
  42. package/src/controller/subtitle-track-controller.ts +3 -5
  43. package/src/controller/timeline-controller.ts +22 -20
  44. package/src/crypt/aes-crypto.ts +2 -21
  45. package/src/crypt/decrypter.ts +16 -32
  46. package/src/crypt/fast-aes-key.ts +5 -28
  47. package/src/demux/audio/aacdemuxer.ts +5 -5
  48. package/src/demux/audio/ac3-demuxer.ts +4 -5
  49. package/src/demux/audio/adts.ts +4 -9
  50. package/src/demux/audio/base-audio-demuxer.ts +14 -16
  51. package/src/demux/audio/mp3demuxer.ts +3 -4
  52. package/src/demux/audio/mpegaudio.ts +1 -1
  53. package/src/demux/id3.ts +411 -0
  54. package/src/demux/inject-worker.ts +4 -38
  55. package/src/demux/mp4demuxer.ts +7 -7
  56. package/src/demux/sample-aes.ts +0 -2
  57. package/src/demux/transmuxer-interface.ts +83 -106
  58. package/src/demux/transmuxer-worker.ts +77 -111
  59. package/src/demux/transmuxer.ts +22 -46
  60. package/src/demux/tsdemuxer.ts +62 -122
  61. package/src/demux/video/avc-video-parser.ts +121 -210
  62. package/src/demux/video/base-video-parser.ts +2 -135
  63. package/src/demux/video/exp-golomb.ts +208 -0
  64. package/src/errors.ts +0 -2
  65. package/src/events.ts +1 -8
  66. package/src/exports-named.ts +1 -1
  67. package/src/hls.ts +48 -97
  68. package/src/loader/date-range.ts +5 -71
  69. package/src/loader/fragment-loader.ts +21 -23
  70. package/src/loader/fragment.ts +4 -8
  71. package/src/loader/key-loader.ts +1 -3
  72. package/src/loader/level-details.ts +6 -6
  73. package/src/loader/level-key.ts +9 -10
  74. package/src/loader/m3u8-parser.ts +144 -138
  75. package/src/loader/playlist-loader.ts +7 -5
  76. package/src/remux/mp4-generator.ts +1 -196
  77. package/src/remux/mp4-remuxer.ts +84 -55
  78. package/src/remux/passthrough-remuxer.ts +8 -23
  79. package/src/task-loop.ts +2 -5
  80. package/src/types/component-api.ts +1 -3
  81. package/src/types/demuxer.ts +0 -3
  82. package/src/types/events.ts +6 -19
  83. package/src/types/fragment-tracker.ts +2 -2
  84. package/src/types/general.ts +6 -0
  85. package/src/types/media-playlist.ts +1 -9
  86. package/src/types/remuxer.ts +1 -1
  87. package/src/utils/attr-list.ts +9 -96
  88. package/src/utils/buffer-helper.ts +31 -12
  89. package/src/utils/cea-608-parser.ts +3 -1
  90. package/src/utils/codecs.ts +5 -34
  91. package/src/utils/discontinuities.ts +47 -21
  92. package/src/utils/fetch-loader.ts +1 -1
  93. package/src/utils/hdr.ts +7 -4
  94. package/src/utils/imsc1-ttml-parser.ts +1 -1
  95. package/src/utils/keysystem-util.ts +6 -1
  96. package/src/utils/level-helper.ts +44 -71
  97. package/src/utils/logger.ts +23 -58
  98. package/src/utils/mp4-tools.ts +3 -5
  99. package/src/utils/rendition-helper.ts +74 -100
  100. package/src/utils/variable-substitution.ts +19 -0
  101. package/src/utils/webvtt-parser.ts +12 -2
  102. package/src/crypt/decrypter-aes-mode.ts +0 -4
  103. package/src/demux/video/hevc-video-parser.ts +0 -749
  104. package/src/utils/encryption-methods-util.ts +0 -21
  105. package/src/utils/hash.ts +0 -10
  106. package/src/utils/utf8-utils.ts +0 -18
  107. package/src/version.ts +0 -1
@@ -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 {
@@ -10,12 +10,7 @@ import {
10
10
  getMediaSource,
11
11
  isManagedMediaSource,
12
12
  } from '../utils/mediasource-helper';
13
- import {
14
- ElementaryStreamTypes,
15
- type Part,
16
- type Fragment,
17
- } from '../loader/fragment';
18
- import { PlaylistLevelType } from '../types/loader';
13
+ import { ElementaryStreamTypes } from '../loader/fragment';
19
14
  import type { TrackSet } from '../types/track';
20
15
  import BufferOperationQueue from './buffer-operation-queue';
21
16
  import {
@@ -39,7 +34,6 @@ import type {
39
34
  import type { ComponentAPI } from '../types/component-api';
40
35
  import type { ChunkMetadata } from '../types/transmuxer';
41
36
  import type Hls from '../hls';
42
- import type { FragmentTracker } from './fragment-tracker';
43
37
  import type { LevelDetails } from '../loader/level-details';
44
38
  import type { HlsConfig } from '../config';
45
39
 
@@ -51,7 +45,7 @@ interface BufferedChangeEvent extends Event {
51
45
  readonly removedRanges?: TimeRanges;
52
46
  }
53
47
 
54
- export default class BufferController extends Logger implements ComponentAPI {
48
+ export default class BufferController implements ComponentAPI {
55
49
  // The level details used to determine duration, target-duration and live
56
50
  private details: LevelDetails | null = null;
57
51
  // cache the self generated object url to detect hijack of video tag
@@ -62,7 +56,6 @@ export default class BufferController extends Logger implements ComponentAPI {
62
56
  private listeners!: SourceBufferListeners;
63
57
 
64
58
  private hls: Hls;
65
- private fragmentTracker: FragmentTracker;
66
59
 
67
60
  // The number of BUFFER_CODEC events received before any sourceBuffers are created
68
61
  public bufferCodecEventsExpected: number = 0;
@@ -79,14 +72,6 @@ export default class BufferController extends Logger implements ComponentAPI {
79
72
  // Last MP3 audio chunk appended
80
73
  private lastMpegAudioChunk: ChunkMetadata | null = null;
81
74
 
82
- // Audio fragment blocked from appending until corresponding video appends or context changes
83
- private blockedAudioAppend: {
84
- op: BufferOperation;
85
- frag: Fragment | Part;
86
- } | null = null;
87
- // Keep track of video append position for unblocking audio
88
- private lastVideoAppendEnd: number = 0;
89
-
90
75
  private appendSource: boolean;
91
76
 
92
77
  // counters
@@ -100,14 +85,19 @@ export default class BufferController extends Logger implements ComponentAPI {
100
85
  public pendingTracks: TrackSet = {};
101
86
  public sourceBuffer!: SourceBuffers;
102
87
 
103
- constructor(hls: Hls, fragmentTracker: FragmentTracker) {
104
- super('buffer-controller', hls.logger);
88
+ protected log: (msg: any) => void;
89
+ protected warn: (msg: any, obj?: any) => void;
90
+ protected error: (msg: any, obj?: any) => void;
91
+
92
+ constructor(hls: Hls) {
105
93
  this.hls = hls;
106
- this.fragmentTracker = fragmentTracker;
94
+ const logPrefix = '[buffer-controller]';
107
95
  this.appendSource = isManagedMediaSource(
108
96
  getMediaSource(hls.config.preferManagedMediaSource),
109
97
  );
110
-
98
+ this.log = logger.log.bind(logger, logPrefix);
99
+ this.warn = logger.warn.bind(logger, logPrefix);
100
+ this.error = logger.error.bind(logger, logPrefix);
111
101
  this._initSourceBuffer();
112
102
  this.registerListeners();
113
103
  }
@@ -124,13 +114,7 @@ export default class BufferController extends Logger implements ComponentAPI {
124
114
  this.details = null;
125
115
  this.lastMpegAudioChunk = null;
126
116
  // @ts-ignore
127
- this.hls = this.fragmentTracker = null;
128
- // @ts-ignore
129
- this._onMediaSourceOpen = this._onMediaSourceClose = null;
130
- // @ts-ignore
131
- this._onMediaSourceEnded = null;
132
- // @ts-ignore
133
- this._onStartStreaming = this._onEndStreaming = null;
117
+ this.hls = null;
134
118
  }
135
119
 
136
120
  protected registerListeners() {
@@ -147,7 +131,6 @@ export default class BufferController extends Logger implements ComponentAPI {
147
131
  hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
148
132
  hls.on(Events.FRAG_PARSED, this.onFragParsed, this);
149
133
  hls.on(Events.FRAG_CHANGED, this.onFragChanged, this);
150
- hls.on(Events.ERROR, this.onError, this);
151
134
  }
152
135
 
153
136
  protected unregisterListeners() {
@@ -164,7 +147,6 @@ export default class BufferController extends Logger implements ComponentAPI {
164
147
  hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
165
148
  hls.off(Events.FRAG_PARSED, this.onFragParsed, this);
166
149
  hls.off(Events.FRAG_CHANGED, this.onFragChanged, this);
167
- hls.off(Events.ERROR, this.onError, this);
168
150
  }
169
151
 
170
152
  private _initSourceBuffer() {
@@ -175,10 +157,12 @@ export default class BufferController extends Logger implements ComponentAPI {
175
157
  video: [],
176
158
  audiovideo: [],
177
159
  };
178
- this.resetAppendErrors();
160
+ this.appendErrors = {
161
+ audio: 0,
162
+ video: 0,
163
+ audiovideo: 0,
164
+ };
179
165
  this.lastMpegAudioChunk = null;
180
- this.blockedAudioAppend = null;
181
- this.lastVideoAppendEnd = 0;
182
166
  }
183
167
 
184
168
  private onManifestLoading() {
@@ -286,38 +270,37 @@ export default class BufferController extends Logger implements ComponentAPI {
286
270
  mediaSource.removeEventListener('endstreaming', this._onEndStreaming);
287
271
  }
288
272
 
289
- this.mediaSource = null;
290
- this._objectUrl = null;
291
- }
292
-
293
- // Detach properly the MediaSource from the HTMLMediaElement as
294
- // suggested in https://github.com/w3c/media-source/issues/53.
295
- if (media) {
296
- media.removeEventListener('emptied', this._onMediaEmptied);
297
- if (_objectUrl) {
298
- self.URL.revokeObjectURL(_objectUrl);
299
- }
273
+ // Detach properly the MediaSource from the HTMLMediaElement as
274
+ // suggested in https://github.com/w3c/media-source/issues/53.
275
+ if (media) {
276
+ media.removeEventListener('emptied', this._onMediaEmptied);
277
+ if (_objectUrl) {
278
+ self.URL.revokeObjectURL(_objectUrl);
279
+ }
300
280
 
301
- // clean up video tag src only if it's our own url. some external libraries might
302
- // hijack the video tag and change its 'src' without destroying the Hls instance first
303
- if (this.mediaSrc === _objectUrl) {
304
- media.removeAttribute('src');
305
- if (this.appendSource) {
306
- removeSourceChildren(media);
281
+ // clean up video tag src only if it's our own url. some external libraries might
282
+ // hijack the video tag and change its 'src' without destroying the Hls instance first
283
+ if (this.mediaSrc === _objectUrl) {
284
+ media.removeAttribute('src');
285
+ if (this.appendSource) {
286
+ removeSourceChildren(media);
287
+ }
288
+ media.load();
289
+ } else {
290
+ this.warn(
291
+ 'media|source.src was changed by a third party - skip cleanup',
292
+ );
307
293
  }
308
- media.load();
309
- } else {
310
- this.warn(
311
- 'media|source.src was changed by a third party - skip cleanup',
312
- );
313
294
  }
295
+
296
+ this.mediaSource = null;
314
297
  this.media = null;
298
+ this._objectUrl = null;
299
+ this.bufferCodecEventsExpected = this._bufferCodecEventsTotal;
300
+ this.pendingTracks = {};
301
+ this.tracks = {};
315
302
  }
316
303
 
317
- this.bufferCodecEventsExpected = this._bufferCodecEventsTotal;
318
- this.pendingTracks = {};
319
- this.tracks = {};
320
-
321
304
  this.hls.trigger(Events.MEDIA_DETACHED, undefined);
322
305
  }
323
306
 
@@ -326,7 +309,6 @@ export default class BufferController extends Logger implements ComponentAPI {
326
309
  this.resetBuffer(type);
327
310
  });
328
311
  this._initSourceBuffer();
329
- this.hls.resumeBuffering();
330
312
  }
331
313
 
332
314
  private resetBuffer(type: SourceBufferName) {
@@ -352,11 +334,11 @@ export default class BufferController extends Logger implements ComponentAPI {
352
334
  ) {
353
335
  const sourceBufferCount = this.getSourceBufferTypes().length;
354
336
  const trackNames = Object.keys(data);
355
- trackNames.forEach((trackName: SourceBufferName) => {
337
+ trackNames.forEach((trackName) => {
356
338
  if (sourceBufferCount) {
357
339
  // check if SourceBuffer codec needs to change
358
340
  const track = this.tracks[trackName];
359
- if (track && typeof track.buffer?.changeType === 'function') {
341
+ if (track && typeof track.buffer.changeType === 'function') {
360
342
  const { id, codec, levelCodec, container, metadata } =
361
343
  data[trackName];
362
344
  const currentCodecFull = pickMostCompleteCodecName(
@@ -420,7 +402,7 @@ export default class BufferController extends Logger implements ComponentAPI {
420
402
  }
421
403
  }
422
404
 
423
- protected appendChangeType(type: SourceBufferName, mimeType: string) {
405
+ protected appendChangeType(type, mimeType) {
424
406
  const { operationQueue } = this;
425
407
  const operation: BufferOperation = {
426
408
  execute: () => {
@@ -441,52 +423,14 @@ export default class BufferController extends Logger implements ComponentAPI {
441
423
  operationQueue.append(operation, type, !!this.pendingTracks[type]);
442
424
  }
443
425
 
444
- private blockAudio(partOrFrag: Fragment | Part) {
445
- const pStart = partOrFrag.start;
446
- const pTime = pStart + partOrFrag.duration * 0.05;
447
- const atGap =
448
- this.fragmentTracker.getAppendedFrag(pStart, PlaylistLevelType.MAIN)
449
- ?.gap === true;
450
- if (atGap) {
451
- return;
452
- }
453
- const op: BufferOperation = {
454
- execute: () => {
455
- if (
456
- this.lastVideoAppendEnd > pTime ||
457
- (this.sourceBuffer.video &&
458
- BufferHelper.isBuffered(this.sourceBuffer.video, pTime)) ||
459
- this.fragmentTracker.getAppendedFrag(pTime, PlaylistLevelType.MAIN)
460
- ?.gap === true
461
- ) {
462
- this.blockedAudioAppend = null;
463
- this.operationQueue.shiftAndExecuteNext('audio');
464
- }
465
- },
466
- onStart: () => {},
467
- onComplete: () => {},
468
- onError: () => {},
469
- };
470
- this.blockedAudioAppend = { op, frag: partOrFrag };
471
- this.operationQueue.append(op, 'audio', true);
472
- }
473
-
474
- private unblockAudio() {
475
- const blockedAudioAppend = this.blockedAudioAppend;
476
- if (blockedAudioAppend) {
477
- this.blockedAudioAppend = null;
478
- this.operationQueue.unblockAudio(blockedAudioAppend.op);
479
- }
480
- }
481
-
482
426
  protected onBufferAppending(
483
427
  event: Events.BUFFER_APPENDING,
484
428
  eventData: BufferAppendingData,
485
429
  ) {
486
- const { operationQueue, tracks } = this;
487
- const { data, type, parent, frag, part, chunkMeta } = eventData;
430
+ const { hls, operationQueue, tracks } = this;
431
+ const { data, type, frag, part, chunkMeta } = eventData;
488
432
  const chunkStats = chunkMeta.buffering[type];
489
- const sn = frag.sn;
433
+
490
434
  const bufferAppendingStart = self.performance.now();
491
435
  chunkStats.start = bufferAppendingStart;
492
436
  const fragBuffering = frag.stats.buffering;
@@ -513,44 +457,7 @@ export default class BufferController extends Logger implements ComponentAPI {
513
457
  this.lastMpegAudioChunk = chunkMeta;
514
458
  }
515
459
 
516
- // Block audio append until overlapping video append
517
- const videoSb = this.sourceBuffer.video;
518
- if (videoSb && sn !== 'initSegment') {
519
- const partOrFrag = part || frag;
520
- const blockedAudioAppend = this.blockedAudioAppend;
521
- if (type === 'audio' && parent !== 'main' && !this.blockedAudioAppend) {
522
- const pStart = partOrFrag.start;
523
- const pTime = pStart + partOrFrag.duration * 0.05;
524
- const vbuffered = videoSb.buffered;
525
- const vappending = this.operationQueue.current('video');
526
- if (!vbuffered.length && !vappending) {
527
- // wait for video before appending audio
528
- this.blockAudio(partOrFrag);
529
- } else if (
530
- !vappending &&
531
- !BufferHelper.isBuffered(videoSb, pTime) &&
532
- this.lastVideoAppendEnd < pTime
533
- ) {
534
- // audio is ahead of video
535
- this.blockAudio(partOrFrag);
536
- }
537
- } else if (type === 'video') {
538
- const videoAppendEnd = partOrFrag.end;
539
- if (blockedAudioAppend) {
540
- const audioStart = blockedAudioAppend.frag.start;
541
- if (
542
- videoAppendEnd > audioStart ||
543
- videoAppendEnd < this.lastVideoAppendEnd ||
544
- BufferHelper.isBuffered(videoSb, audioStart)
545
- ) {
546
- this.unblockAudio();
547
- }
548
- }
549
- this.lastVideoAppendEnd = videoAppendEnd;
550
- }
551
- }
552
-
553
- const fragStart = (part || frag).start;
460
+ const fragStart = frag.start;
554
461
  const operation: BufferOperation = {
555
462
  execute: () => {
556
463
  chunkStats.executeStart = self.performance.now();
@@ -560,7 +467,7 @@ export default class BufferController extends Logger implements ComponentAPI {
560
467
  const delta = fragStart - sb.timestampOffset;
561
468
  if (Math.abs(delta) >= 0.1) {
562
469
  this.log(
563
- `Updating audio SourceBuffer timestampOffset to ${fragStart} (delta: ${delta}) sn: ${sn})`,
470
+ `Updating audio SourceBuffer timestampOffset to ${fragStart} (delta: ${delta}) sn: ${frag.sn})`,
564
471
  );
565
472
  sb.timestampOffset = fragStart;
566
473
  }
@@ -629,27 +536,30 @@ export default class BufferController extends Logger implements ComponentAPI {
629
536
  browser is able to evict some data from sourcebuffer. Retrying can help recover.
630
537
  */
631
538
  this.warn(
632
- `Failed ${appendErrorCount}/${this.hls.config.appendErrorMaxRetry} times to append segment in "${type}" sourceBuffer`,
539
+ `Failed ${appendErrorCount}/${hls.config.appendErrorMaxRetry} times to append segment in "${type}" sourceBuffer`,
633
540
  );
634
- if (appendErrorCount >= this.hls.config.appendErrorMaxRetry) {
541
+ if (appendErrorCount >= hls.config.appendErrorMaxRetry) {
635
542
  event.fatal = true;
636
543
  }
637
544
  }
638
- this.hls.trigger(Events.ERROR, event);
545
+ hls.trigger(Events.ERROR, event);
639
546
  },
640
547
  };
641
548
  operationQueue.append(operation, type, !!this.pendingTracks[type]);
642
549
  }
643
550
 
644
- private getFlushOp(
645
- type: SourceBufferName,
646
- start: number,
647
- end: number,
648
- ): BufferOperation {
649
- return {
650
- execute: () => {
651
- this.removeExecutor(type, start, end);
652
- },
551
+ protected onBufferFlushing(
552
+ event: Events.BUFFER_FLUSHING,
553
+ data: BufferFlushingData,
554
+ ) {
555
+ const { operationQueue } = this;
556
+ const flushOperation = (type: SourceBufferName): BufferOperation => ({
557
+ execute: this.removeExecutor.bind(
558
+ this,
559
+ type,
560
+ data.startOffset,
561
+ data.endOffset,
562
+ ),
653
563
  onStart: () => {
654
564
  // logger.debug(`[buffer-controller]: Started flushing ${data.startOffset} -> ${data.endOffset} for ${type} Source Buffer`);
655
565
  },
@@ -660,26 +570,13 @@ export default class BufferController extends Logger implements ComponentAPI {
660
570
  onError: (error: Error) => {
661
571
  this.warn(`Failed to remove from ${type} SourceBuffer`, error);
662
572
  },
663
- };
664
- }
573
+ });
665
574
 
666
- protected onBufferFlushing(
667
- event: Events.BUFFER_FLUSHING,
668
- data: BufferFlushingData,
669
- ) {
670
- const { operationQueue } = this;
671
- const { type, startOffset, endOffset } = data;
672
- if (type) {
673
- operationQueue.append(
674
- this.getFlushOp(type, startOffset, endOffset),
675
- type,
676
- );
575
+ if (data.type) {
576
+ operationQueue.append(flushOperation(data.type), data.type);
677
577
  } else {
678
- this.getSourceBufferTypes().forEach((sbType: SourceBufferName) => {
679
- operationQueue.append(
680
- this.getFlushOp(sbType, startOffset, endOffset),
681
- sbType,
682
- );
578
+ this.getSourceBufferTypes().forEach((type: SourceBufferName) => {
579
+ operationQueue.append(flushOperation(type), type);
683
580
  });
684
581
  }
685
582
  }
@@ -732,9 +629,6 @@ export default class BufferController extends Logger implements ComponentAPI {
732
629
  // on BUFFER_EOS mark matching sourcebuffer(s) as ended and trigger checkEos()
733
630
  // an undefined data.type will mark all buffers as EOS.
734
631
  protected onBufferEos(event: Events.BUFFER_EOS, data: BufferEOSData) {
735
- if (data.type === 'video') {
736
- this.unblockAudio();
737
- }
738
632
  const ended = this.getSourceBufferTypes().reduce((acc, type) => {
739
633
  const sb = this.sourceBuffer[type];
740
634
  if (sb && (!data.type || data.type === type)) {
@@ -780,34 +674,14 @@ export default class BufferController extends Logger implements ComponentAPI {
780
674
  return;
781
675
  }
782
676
  this.details = details;
783
- const durationAndRange = this.getDurationAndRange();
784
- if (!durationAndRange) {
785
- return;
786
- }
677
+
787
678
  if (this.getSourceBufferTypes().length) {
788
- this.blockBuffers(() => this.updateMediaSource(durationAndRange));
679
+ this.blockBuffers(this.updateMediaElementDuration.bind(this));
789
680
  } else {
790
- this.updateMediaSource(durationAndRange);
791
- }
792
- }
793
-
794
- private onError(event: Events.ERROR, data: ErrorData) {
795
- if (data.details === ErrorDetails.BUFFER_APPEND_ERROR && data.frag) {
796
- const nextAutoLevel = data.errorAction?.nextAutoLevel;
797
- if (Number.isFinite(nextAutoLevel) && nextAutoLevel !== data.frag.level) {
798
- this.resetAppendErrors();
799
- }
681
+ this.updateMediaElementDuration();
800
682
  }
801
683
  }
802
684
 
803
- private resetAppendErrors() {
804
- this.appendErrors = {
805
- audio: 0,
806
- video: 0,
807
- audiovideo: 0,
808
- };
809
- }
810
-
811
685
  trimBuffers() {
812
686
  const { hls, details, media } = this;
813
687
  if (!media || details === null) {
@@ -956,18 +830,14 @@ export default class BufferController extends Logger implements ComponentAPI {
956
830
  * 'liveDurationInfinity` is set to `true`
957
831
  * More details: https://github.com/video-dev/hls.js/issues/355
958
832
  */
959
- private getDurationAndRange(): {
960
- duration: number;
961
- start?: number;
962
- end?: number;
963
- } | null {
833
+ private updateMediaElementDuration() {
964
834
  if (
965
835
  !this.details ||
966
836
  !this.media ||
967
837
  !this.mediaSource ||
968
838
  this.mediaSource.readyState !== 'open'
969
839
  ) {
970
- return null;
840
+ return;
971
841
  }
972
842
  const { details, hls, media, mediaSource } = this;
973
843
  const levelDuration = details.fragments[0].start + details.totalduration;
@@ -979,47 +849,31 @@ export default class BufferController extends Logger implements ComponentAPI {
979
849
  if (details.live && hls.config.liveDurationInfinity) {
980
850
  // Override duration to Infinity
981
851
  mediaSource.duration = Infinity;
982
- const len = details.fragments.length;
983
- if (len && details.live && !!mediaSource.setLiveSeekableRange) {
984
- const start = Math.max(0, details.fragments[0].start);
985
- const end = Math.max(start, start + details.totalduration);
986
- return { duration: Infinity, start, end };
987
- }
988
- return { duration: Infinity };
852
+ this.updateSeekableRange(details);
989
853
  } else if (
990
854
  (levelDuration > msDuration && levelDuration > mediaDuration) ||
991
855
  !Number.isFinite(mediaDuration)
992
856
  ) {
993
- return { duration: levelDuration };
857
+ // levelDuration was the last value we set.
858
+ // not using mediaSource.duration as the browser may tweak this value
859
+ // only update Media Source duration if its value increase, this is to avoid
860
+ // flushing already buffered portion when switching between quality level
861
+ this.log(`Updating Media Source duration to ${levelDuration.toFixed(3)}`);
862
+ mediaSource.duration = levelDuration;
994
863
  }
995
- return null;
996
864
  }
997
865
 
998
- private updateMediaSource({
999
- duration,
1000
- start,
1001
- end,
1002
- }: {
1003
- duration: number;
1004
- start?: number;
1005
- end?: number;
1006
- }) {
1007
- if (
1008
- !this.media ||
1009
- !this.mediaSource ||
1010
- this.mediaSource.readyState !== 'open'
1011
- ) {
1012
- return;
1013
- }
1014
- if (Number.isFinite(duration)) {
1015
- this.log(`Updating Media Source duration to ${duration.toFixed(3)}`);
1016
- }
1017
- this.mediaSource.duration = duration;
1018
- if (start !== undefined && end !== undefined) {
866
+ updateSeekableRange(levelDetails) {
867
+ const mediaSource = this.mediaSource;
868
+ const fragments = levelDetails.fragments;
869
+ const len = fragments.length;
870
+ if (len && levelDetails.live && mediaSource?.setLiveSeekableRange) {
871
+ const start = Math.max(0, fragments[0].start);
872
+ const end = Math.max(start, start + levelDetails.totalduration);
1019
873
  this.log(
1020
- `Media Source duration is set to ${this.mediaSource.duration}. Setting seekable range to ${start}-${end}.`,
874
+ `Media Source duration is set to ${mediaSource.duration}. Setting seekable range to ${start}-${end}.`,
1021
875
  );
1022
- this.mediaSource.setLiveSeekableRange(start, end);
876
+ mediaSource.setLiveSeekableRange(start, end);
1023
877
  }
1024
878
  }
1025
879
 
@@ -1140,10 +994,7 @@ export default class BufferController extends Logger implements ComponentAPI {
1140
994
  this.log('Media source opened');
1141
995
  if (media) {
1142
996
  media.removeEventListener('emptied', this._onMediaEmptied);
1143
- const durationAndRange = this.getDurationAndRange();
1144
- if (durationAndRange) {
1145
- this.updateMediaSource(durationAndRange);
1146
- }
997
+ this.updateMediaElementDuration();
1147
998
  this.hls.trigger(Events.MEDIA_ATTACHED, {
1148
999
  media,
1149
1000
  mediaSource: mediaSource as MediaSource,
@@ -1168,15 +1019,14 @@ export default class BufferController extends Logger implements ComponentAPI {
1168
1019
  private _onMediaEmptied = () => {
1169
1020
  const { mediaSrc, _objectUrl } = this;
1170
1021
  if (mediaSrc !== _objectUrl) {
1171
- this.error(
1022
+ logger.error(
1172
1023
  `Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`,
1173
1024
  );
1174
1025
  }
1175
1026
  };
1176
1027
 
1177
1028
  private get mediaSrc(): string | undefined {
1178
- const media =
1179
- (this.media?.firstChild as HTMLSourceElement | null) || this.media;
1029
+ const media = this.media?.querySelector?.('source') || this.media;
1180
1030
  return media?.src;
1181
1031
  }
1182
1032
 
@@ -1264,7 +1114,7 @@ export default class BufferController extends Logger implements ComponentAPI {
1264
1114
  }
1265
1115
  return;
1266
1116
  }
1267
- sb.ending = false;
1117
+
1268
1118
  sb.ended = false;
1269
1119
  sb.appendBuffer(data);
1270
1120
  }
@@ -1287,14 +1137,10 @@ export default class BufferController extends Logger implements ComponentAPI {
1287
1137
  const blockingOperations = buffers.map((type) =>
1288
1138
  operationQueue.appendBlocker(type as SourceBufferName),
1289
1139
  );
1290
- const audioBlocked = buffers.length > 1 && !!this.blockedAudioAppend;
1291
- if (audioBlocked) {
1292
- this.unblockAudio();
1293
- }
1294
- Promise.all(blockingOperations).then((result) => {
1140
+ Promise.all(blockingOperations).then(() => {
1295
1141
  // logger.debug(`[buffer-controller]: Blocking operation resolved; unblocking ${buffers} SourceBuffer`);
1296
1142
  onUnblocked();
1297
- buffers.forEach((type, i) => {
1143
+ buffers.forEach((type) => {
1298
1144
  const sb = this.sourceBuffer[type];
1299
1145
  // Only cycle the queue if the SB is not updating. There's a bug in Chrome which sets the SB updating flag to
1300
1146
  // true when changing the MediaSource duration (https://bugs.chromium.org/p/chromium/issues/detail?id=959359&can=2&q=mediasource%20duration)
@@ -30,23 +30,26 @@ export default class BufferOperationQueue {
30
30
  }
31
31
  }
32
32
 
33
- public appendBlocker(type: SourceBufferName): Promise<void> {
34
- return new Promise((resolve) => {
35
- const operation: BufferOperation = {
36
- execute: resolve,
37
- onStart: () => {},
38
- onComplete: () => {},
39
- onError: () => {},
40
- };
41
- this.append(operation, type);
42
- });
33
+ public insertAbort(operation: BufferOperation, type: SourceBufferName) {
34
+ const queue = this.queues[type];
35
+ queue.unshift(operation);
36
+ this.executeNext(type);
43
37
  }
44
38
 
45
- unblockAudio(op: BufferOperation) {
46
- const queue = this.queues.audio;
47
- if (queue[0] === op) {
48
- this.shiftAndExecuteNext('audio');
49
- }
39
+ public appendBlocker(type: SourceBufferName): Promise<{}> {
40
+ let execute;
41
+ const promise: Promise<{}> = new Promise((resolve) => {
42
+ execute = resolve;
43
+ });
44
+ const operation: BufferOperation = {
45
+ execute,
46
+ onStart: () => {},
47
+ onComplete: () => {},
48
+ onError: () => {},
49
+ };
50
+
51
+ this.append(operation, type);
52
+ return promise;
50
53
  }
51
54
 
52
55
  public executeNext(type: SourceBufferName) {
@@ -77,7 +80,7 @@ export default class BufferOperationQueue {
77
80
  this.executeNext(type);
78
81
  }
79
82
 
80
- public current(type: SourceBufferName): BufferOperation {
83
+ public current(type: SourceBufferName) {
81
84
  return this.queues[type][0];
82
85
  }
83
86
  }
@@ -12,6 +12,7 @@ import type {
12
12
  LevelsUpdatedData,
13
13
  } from '../types/events';
14
14
  import StreamController from './stream-controller';
15
+ import { logger } from '../utils/logger';
15
16
  import type { ComponentAPI } from '../types/component-api';
16
17
  import type Hls from '../hls';
17
18
 
@@ -138,7 +139,6 @@ class CapLevelController implements ComponentAPI {
138
139
 
139
140
  protected onMediaDetaching() {
140
141
  this.stopCapping();
141
- this.media = null;
142
142
  }
143
143
 
144
144
  detectPlayerSize() {
@@ -152,13 +152,12 @@ class CapLevelController implements ComponentAPI {
152
152
  const hls = this.hls;
153
153
  const maxLevel = this.getMaxLevel(levels.length - 1);
154
154
  if (maxLevel !== this.autoLevelCapping) {
155
- hls.logger.log(
155
+ logger.log(
156
156
  `Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`,
157
157
  );
158
158
  }
159
159
  hls.autoLevelCapping = maxLevel;
160
160
  if (
161
- hls.autoLevelEnabled &&
162
161
  hls.autoLevelCapping > this.autoLevelCapping &&
163
162
  this.streamController
164
163
  ) {