hls.js 1.5.10-0.canary.10328 → 1.5.10

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 (88) 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 +2194 -3476
  5. package/dist/hls.js.d.ts +85 -108
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +3137 -3784
  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 +1256 -1928
  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 +4182 -5487
  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 +36 -36
  20. package/src/config.ts +2 -3
  21. package/src/controller/abr-controller.ts +20 -24
  22. package/src/controller/audio-stream-controller.ts +74 -68
  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 +38 -160
  26. package/src/controller/buffer-controller.ts +92 -230
  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 +8 -6
  33. package/src/controller/fps-controller.ts +3 -8
  34. package/src/controller/fragment-tracker.ts +11 -15
  35. package/src/controller/gap-controller.ts +16 -43
  36. package/src/controller/id3-track-controller.ts +7 -7
  37. package/src/controller/latency-controller.ts +11 -9
  38. package/src/controller/level-controller.ts +19 -37
  39. package/src/controller/stream-controller.ts +32 -37
  40. package/src/controller/subtitle-stream-controller.ts +40 -28
  41. package/src/controller/subtitle-track-controller.ts +3 -5
  42. package/src/controller/timeline-controller.ts +21 -19
  43. package/src/crypt/aes-crypto.ts +2 -21
  44. package/src/crypt/decrypter.ts +16 -32
  45. package/src/crypt/fast-aes-key.ts +5 -28
  46. package/src/demux/audio/aacdemuxer.ts +2 -2
  47. package/src/demux/audio/ac3-demuxer.ts +3 -4
  48. package/src/demux/audio/adts.ts +4 -9
  49. package/src/demux/audio/base-audio-demuxer.ts +14 -16
  50. package/src/demux/audio/mp3demuxer.ts +3 -4
  51. package/src/demux/audio/mpegaudio.ts +1 -1
  52. package/src/demux/id3.ts +411 -0
  53. package/src/demux/mp4demuxer.ts +7 -7
  54. package/src/demux/sample-aes.ts +0 -2
  55. package/src/demux/transmuxer-interface.ts +12 -4
  56. package/src/demux/transmuxer-worker.ts +4 -4
  57. package/src/demux/transmuxer.ts +3 -16
  58. package/src/demux/tsdemuxer.ts +38 -75
  59. package/src/demux/video/avc-video-parser.ts +119 -208
  60. package/src/demux/video/base-video-parser.ts +18 -147
  61. package/src/demux/video/exp-golomb.ts +208 -0
  62. package/src/events.ts +1 -8
  63. package/src/exports-named.ts +1 -1
  64. package/src/hls.ts +38 -61
  65. package/src/loader/fragment-loader.ts +3 -10
  66. package/src/loader/key-loader.ts +1 -3
  67. package/src/loader/level-key.ts +9 -10
  68. package/src/loader/playlist-loader.ts +5 -4
  69. package/src/remux/mp4-generator.ts +1 -196
  70. package/src/remux/mp4-remuxer.ts +8 -24
  71. package/src/task-loop.ts +2 -5
  72. package/src/types/component-api.ts +1 -3
  73. package/src/types/demuxer.ts +0 -4
  74. package/src/types/events.ts +0 -4
  75. package/src/types/remuxer.ts +1 -1
  76. package/src/utils/buffer-helper.ts +31 -12
  77. package/src/utils/cea-608-parser.ts +3 -1
  78. package/src/utils/codecs.ts +5 -34
  79. package/src/utils/fetch-loader.ts +1 -1
  80. package/src/utils/imsc1-ttml-parser.ts +1 -1
  81. package/src/utils/keysystem-util.ts +6 -1
  82. package/src/utils/logger.ts +23 -58
  83. package/src/utils/mp4-tools.ts +3 -5
  84. package/src/utils/webvtt-parser.ts +1 -1
  85. package/src/crypt/decrypter-aes-mode.ts +0 -4
  86. package/src/demux/video/hevc-video-parser.ts +0 -749
  87. package/src/utils/encryption-methods-util.ts +0 -21
  88. package/src/utils/utf8-utils.ts +0 -18
@@ -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() {
@@ -179,8 +163,6 @@ export default class BufferController extends Logger implements ComponentAPI {
179
163
  audiovideo: 0,
180
164
  };
181
165
  this.lastMpegAudioChunk = null;
182
- this.blockedAudioAppend = null;
183
- this.lastVideoAppendEnd = 0;
184
166
  }
185
167
 
186
168
  private onManifestLoading() {
@@ -288,38 +270,37 @@ export default class BufferController extends Logger implements ComponentAPI {
288
270
  mediaSource.removeEventListener('endstreaming', this._onEndStreaming);
289
271
  }
290
272
 
291
- this.mediaSource = null;
292
- this._objectUrl = null;
293
- }
294
-
295
- // Detach properly the MediaSource from the HTMLMediaElement as
296
- // suggested in https://github.com/w3c/media-source/issues/53.
297
- if (media) {
298
- media.removeEventListener('emptied', this._onMediaEmptied);
299
- if (_objectUrl) {
300
- self.URL.revokeObjectURL(_objectUrl);
301
- }
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
+ }
302
280
 
303
- // clean up video tag src only if it's our own url. some external libraries might
304
- // hijack the video tag and change its 'src' without destroying the Hls instance first
305
- if (this.mediaSrc === _objectUrl) {
306
- media.removeAttribute('src');
307
- if (this.appendSource) {
308
- 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
+ );
309
293
  }
310
- media.load();
311
- } else {
312
- this.warn(
313
- 'media|source.src was changed by a third party - skip cleanup',
314
- );
315
294
  }
295
+
296
+ this.mediaSource = null;
316
297
  this.media = null;
298
+ this._objectUrl = null;
299
+ this.bufferCodecEventsExpected = this._bufferCodecEventsTotal;
300
+ this.pendingTracks = {};
301
+ this.tracks = {};
317
302
  }
318
303
 
319
- this.bufferCodecEventsExpected = this._bufferCodecEventsTotal;
320
- this.pendingTracks = {};
321
- this.tracks = {};
322
-
323
304
  this.hls.trigger(Events.MEDIA_DETACHED, undefined);
324
305
  }
325
306
 
@@ -328,7 +309,6 @@ export default class BufferController extends Logger implements ComponentAPI {
328
309
  this.resetBuffer(type);
329
310
  });
330
311
  this._initSourceBuffer();
331
- this.hls.resumeBuffering();
332
312
  }
333
313
 
334
314
  private resetBuffer(type: SourceBufferName) {
@@ -354,11 +334,11 @@ export default class BufferController extends Logger implements ComponentAPI {
354
334
  ) {
355
335
  const sourceBufferCount = this.getSourceBufferTypes().length;
356
336
  const trackNames = Object.keys(data);
357
- trackNames.forEach((trackName: SourceBufferName) => {
337
+ trackNames.forEach((trackName) => {
358
338
  if (sourceBufferCount) {
359
339
  // check if SourceBuffer codec needs to change
360
340
  const track = this.tracks[trackName];
361
- if (track && typeof track.buffer?.changeType === 'function') {
341
+ if (track && typeof track.buffer.changeType === 'function') {
362
342
  const { id, codec, levelCodec, container, metadata } =
363
343
  data[trackName];
364
344
  const currentCodecFull = pickMostCompleteCodecName(
@@ -422,7 +402,7 @@ export default class BufferController extends Logger implements ComponentAPI {
422
402
  }
423
403
  }
424
404
 
425
- protected appendChangeType(type: SourceBufferName, mimeType: string) {
405
+ protected appendChangeType(type, mimeType) {
426
406
  const { operationQueue } = this;
427
407
  const operation: BufferOperation = {
428
408
  execute: () => {
@@ -443,52 +423,14 @@ export default class BufferController extends Logger implements ComponentAPI {
443
423
  operationQueue.append(operation, type, !!this.pendingTracks[type]);
444
424
  }
445
425
 
446
- private blockAudio(partOrFrag: Fragment | Part) {
447
- const pStart = partOrFrag.start;
448
- const pTime = pStart + partOrFrag.duration * 0.05;
449
- const atGap =
450
- this.fragmentTracker.getAppendedFrag(pStart, PlaylistLevelType.MAIN)
451
- ?.gap === true;
452
- if (atGap) {
453
- return;
454
- }
455
- const op: BufferOperation = {
456
- execute: () => {
457
- if (
458
- this.lastVideoAppendEnd > pTime ||
459
- (this.sourceBuffer.video &&
460
- BufferHelper.isBuffered(this.sourceBuffer.video, pTime)) ||
461
- this.fragmentTracker.getAppendedFrag(pTime, PlaylistLevelType.MAIN)
462
- ?.gap === true
463
- ) {
464
- this.blockedAudioAppend = null;
465
- this.operationQueue.shiftAndExecuteNext('audio');
466
- }
467
- },
468
- onStart: () => {},
469
- onComplete: () => {},
470
- onError: () => {},
471
- };
472
- this.blockedAudioAppend = { op, frag: partOrFrag };
473
- this.operationQueue.append(op, 'audio', true);
474
- }
475
-
476
- private unblockAudio() {
477
- const blockedAudioAppend = this.blockedAudioAppend;
478
- if (blockedAudioAppend) {
479
- this.blockedAudioAppend = null;
480
- this.operationQueue.unblockAudio(blockedAudioAppend.op);
481
- }
482
- }
483
-
484
426
  protected onBufferAppending(
485
427
  event: Events.BUFFER_APPENDING,
486
428
  eventData: BufferAppendingData,
487
429
  ) {
488
- const { operationQueue, tracks } = this;
489
- const { data, type, parent, frag, part, chunkMeta } = eventData;
430
+ const { hls, operationQueue, tracks } = this;
431
+ const { data, type, frag, part, chunkMeta } = eventData;
490
432
  const chunkStats = chunkMeta.buffering[type];
491
- const sn = frag.sn;
433
+
492
434
  const bufferAppendingStart = self.performance.now();
493
435
  chunkStats.start = bufferAppendingStart;
494
436
  const fragBuffering = frag.stats.buffering;
@@ -515,44 +457,7 @@ export default class BufferController extends Logger implements ComponentAPI {
515
457
  this.lastMpegAudioChunk = chunkMeta;
516
458
  }
517
459
 
518
- // Block audio append until overlapping video append
519
- const videoSb = this.sourceBuffer.video;
520
- if (videoSb && sn !== 'initSegment') {
521
- const partOrFrag = part || frag;
522
- const blockedAudioAppend = this.blockedAudioAppend;
523
- if (type === 'audio' && parent !== 'main' && !this.blockedAudioAppend) {
524
- const pStart = partOrFrag.start;
525
- const pTime = pStart + partOrFrag.duration * 0.05;
526
- const vbuffered = videoSb.buffered;
527
- const vappending = this.operationQueue.current('video');
528
- if (!vbuffered.length && !vappending) {
529
- // wait for video before appending audio
530
- this.blockAudio(partOrFrag);
531
- } else if (
532
- !vappending &&
533
- !BufferHelper.isBuffered(videoSb, pTime) &&
534
- this.lastVideoAppendEnd < pTime
535
- ) {
536
- // audio is ahead of video
537
- this.blockAudio(partOrFrag);
538
- }
539
- } else if (type === 'video') {
540
- const videoAppendEnd = partOrFrag.end;
541
- if (blockedAudioAppend) {
542
- const audioStart = blockedAudioAppend.frag.start;
543
- if (
544
- videoAppendEnd > audioStart ||
545
- videoAppendEnd < this.lastVideoAppendEnd ||
546
- BufferHelper.isBuffered(videoSb, audioStart)
547
- ) {
548
- this.unblockAudio();
549
- }
550
- }
551
- this.lastVideoAppendEnd = videoAppendEnd;
552
- }
553
- }
554
-
555
- const fragStart = (part || frag).start;
460
+ const fragStart = frag.start;
556
461
  const operation: BufferOperation = {
557
462
  execute: () => {
558
463
  chunkStats.executeStart = self.performance.now();
@@ -562,7 +467,7 @@ export default class BufferController extends Logger implements ComponentAPI {
562
467
  const delta = fragStart - sb.timestampOffset;
563
468
  if (Math.abs(delta) >= 0.1) {
564
469
  this.log(
565
- `Updating audio SourceBuffer timestampOffset to ${fragStart} (delta: ${delta}) sn: ${sn})`,
470
+ `Updating audio SourceBuffer timestampOffset to ${fragStart} (delta: ${delta}) sn: ${frag.sn})`,
566
471
  );
567
472
  sb.timestampOffset = fragStart;
568
473
  }
@@ -631,27 +536,30 @@ export default class BufferController extends Logger implements ComponentAPI {
631
536
  browser is able to evict some data from sourcebuffer. Retrying can help recover.
632
537
  */
633
538
  this.warn(
634
- `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`,
635
540
  );
636
- if (appendErrorCount >= this.hls.config.appendErrorMaxRetry) {
541
+ if (appendErrorCount >= hls.config.appendErrorMaxRetry) {
637
542
  event.fatal = true;
638
543
  }
639
544
  }
640
- this.hls.trigger(Events.ERROR, event);
545
+ hls.trigger(Events.ERROR, event);
641
546
  },
642
547
  };
643
548
  operationQueue.append(operation, type, !!this.pendingTracks[type]);
644
549
  }
645
550
 
646
- private getFlushOp(
647
- type: SourceBufferName,
648
- start: number,
649
- end: number,
650
- ): BufferOperation {
651
- return {
652
- execute: () => {
653
- this.removeExecutor(type, start, end);
654
- },
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
+ ),
655
563
  onStart: () => {
656
564
  // logger.debug(`[buffer-controller]: Started flushing ${data.startOffset} -> ${data.endOffset} for ${type} Source Buffer`);
657
565
  },
@@ -662,26 +570,13 @@ export default class BufferController extends Logger implements ComponentAPI {
662
570
  onError: (error: Error) => {
663
571
  this.warn(`Failed to remove from ${type} SourceBuffer`, error);
664
572
  },
665
- };
666
- }
573
+ });
667
574
 
668
- protected onBufferFlushing(
669
- event: Events.BUFFER_FLUSHING,
670
- data: BufferFlushingData,
671
- ) {
672
- const { operationQueue } = this;
673
- const { type, startOffset, endOffset } = data;
674
- if (type) {
675
- operationQueue.append(
676
- this.getFlushOp(type, startOffset, endOffset),
677
- type,
678
- );
575
+ if (data.type) {
576
+ operationQueue.append(flushOperation(data.type), data.type);
679
577
  } else {
680
- this.getSourceBufferTypes().forEach((sbType: SourceBufferName) => {
681
- operationQueue.append(
682
- this.getFlushOp(sbType, startOffset, endOffset),
683
- sbType,
684
- );
578
+ this.getSourceBufferTypes().forEach((type: SourceBufferName) => {
579
+ operationQueue.append(flushOperation(type), type);
685
580
  });
686
581
  }
687
582
  }
@@ -734,9 +629,6 @@ export default class BufferController extends Logger implements ComponentAPI {
734
629
  // on BUFFER_EOS mark matching sourcebuffer(s) as ended and trigger checkEos()
735
630
  // an undefined data.type will mark all buffers as EOS.
736
631
  protected onBufferEos(event: Events.BUFFER_EOS, data: BufferEOSData) {
737
- if (data.type === 'video') {
738
- this.unblockAudio();
739
- }
740
632
  const ended = this.getSourceBufferTypes().reduce((acc, type) => {
741
633
  const sb = this.sourceBuffer[type];
742
634
  if (sb && (!data.type || data.type === type)) {
@@ -782,14 +674,11 @@ export default class BufferController extends Logger implements ComponentAPI {
782
674
  return;
783
675
  }
784
676
  this.details = details;
785
- const durationAndRange = this.getDurationAndRange();
786
- if (!durationAndRange) {
787
- return;
788
- }
677
+
789
678
  if (this.getSourceBufferTypes().length) {
790
- this.blockBuffers(() => this.updateMediaSource(durationAndRange));
679
+ this.blockBuffers(this.updateMediaElementDuration.bind(this));
791
680
  } else {
792
- this.updateMediaSource(durationAndRange);
681
+ this.updateMediaElementDuration();
793
682
  }
794
683
  }
795
684
 
@@ -941,18 +830,14 @@ export default class BufferController extends Logger implements ComponentAPI {
941
830
  * 'liveDurationInfinity` is set to `true`
942
831
  * More details: https://github.com/video-dev/hls.js/issues/355
943
832
  */
944
- private getDurationAndRange(): {
945
- duration: number;
946
- start?: number;
947
- end?: number;
948
- } | null {
833
+ private updateMediaElementDuration() {
949
834
  if (
950
835
  !this.details ||
951
836
  !this.media ||
952
837
  !this.mediaSource ||
953
838
  this.mediaSource.readyState !== 'open'
954
839
  ) {
955
- return null;
840
+ return;
956
841
  }
957
842
  const { details, hls, media, mediaSource } = this;
958
843
  const levelDuration = details.fragments[0].start + details.totalduration;
@@ -964,47 +849,31 @@ export default class BufferController extends Logger implements ComponentAPI {
964
849
  if (details.live && hls.config.liveDurationInfinity) {
965
850
  // Override duration to Infinity
966
851
  mediaSource.duration = Infinity;
967
- const len = details.fragments.length;
968
- if (len && details.live && !!mediaSource.setLiveSeekableRange) {
969
- const start = Math.max(0, details.fragments[0].start);
970
- const end = Math.max(start, start + details.totalduration);
971
- return { duration: Infinity, start, end };
972
- }
973
- return { duration: Infinity };
852
+ this.updateSeekableRange(details);
974
853
  } else if (
975
854
  (levelDuration > msDuration && levelDuration > mediaDuration) ||
976
855
  !Number.isFinite(mediaDuration)
977
856
  ) {
978
- 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;
979
863
  }
980
- return null;
981
864
  }
982
865
 
983
- private updateMediaSource({
984
- duration,
985
- start,
986
- end,
987
- }: {
988
- duration: number;
989
- start?: number;
990
- end?: number;
991
- }) {
992
- if (
993
- !this.media ||
994
- !this.mediaSource ||
995
- this.mediaSource.readyState !== 'open'
996
- ) {
997
- return;
998
- }
999
- if (Number.isFinite(duration)) {
1000
- this.log(`Updating Media Source duration to ${duration.toFixed(3)}`);
1001
- }
1002
- this.mediaSource.duration = duration;
1003
- 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);
1004
873
  this.log(
1005
- `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}.`,
1006
875
  );
1007
- this.mediaSource.setLiveSeekableRange(start, end);
876
+ mediaSource.setLiveSeekableRange(start, end);
1008
877
  }
1009
878
  }
1010
879
 
@@ -1125,10 +994,7 @@ export default class BufferController extends Logger implements ComponentAPI {
1125
994
  this.log('Media source opened');
1126
995
  if (media) {
1127
996
  media.removeEventListener('emptied', this._onMediaEmptied);
1128
- const durationAndRange = this.getDurationAndRange();
1129
- if (durationAndRange) {
1130
- this.updateMediaSource(durationAndRange);
1131
- }
997
+ this.updateMediaElementDuration();
1132
998
  this.hls.trigger(Events.MEDIA_ATTACHED, {
1133
999
  media,
1134
1000
  mediaSource: mediaSource as MediaSource,
@@ -1153,7 +1019,7 @@ export default class BufferController extends Logger implements ComponentAPI {
1153
1019
  private _onMediaEmptied = () => {
1154
1020
  const { mediaSrc, _objectUrl } = this;
1155
1021
  if (mediaSrc !== _objectUrl) {
1156
- this.error(
1022
+ logger.error(
1157
1023
  `Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`,
1158
1024
  );
1159
1025
  }
@@ -1249,7 +1115,7 @@ export default class BufferController extends Logger implements ComponentAPI {
1249
1115
  }
1250
1116
  return;
1251
1117
  }
1252
- sb.ending = false;
1118
+
1253
1119
  sb.ended = false;
1254
1120
  sb.appendBuffer(data);
1255
1121
  }
@@ -1272,14 +1138,10 @@ export default class BufferController extends Logger implements ComponentAPI {
1272
1138
  const blockingOperations = buffers.map((type) =>
1273
1139
  operationQueue.appendBlocker(type as SourceBufferName),
1274
1140
  );
1275
- const audioBlocked = buffers.length > 1 && !!this.blockedAudioAppend;
1276
- if (audioBlocked) {
1277
- this.unblockAudio();
1278
- }
1279
- Promise.all(blockingOperations).then((result) => {
1141
+ Promise.all(blockingOperations).then(() => {
1280
1142
  // logger.debug(`[buffer-controller]: Blocking operation resolved; unblocking ${buffers} SourceBuffer`);
1281
1143
  onUnblocked();
1282
- buffers.forEach((type, i) => {
1144
+ buffers.forEach((type) => {
1283
1145
  const sb = this.sourceBuffer[type];
1284
1146
  // Only cycle the queue if the SB is not updating. There's a bug in Chrome which sets the SB updating flag to
1285
1147
  // 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
  ) {