hls.js 1.5.7-0.canary.10042 → 1.5.7

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 (72) hide show
  1. package/README.md +1 -2
  2. package/dist/hls-demo.js +0 -10
  3. package/dist/hls-demo.js.map +1 -1
  4. package/dist/hls.js +1283 -2293
  5. package/dist/hls.js.d.ts +84 -97
  6. package/dist/hls.js.map +1 -1
  7. package/dist/hls.light.js +1030 -1435
  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 +809 -1209
  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 +1039 -2030
  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 +22 -22
  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 +8 -20
  25. package/src/controller/base-stream-controller.ts +36 -157
  26. package/src/controller/buffer-controller.ts +99 -226
  27. package/src/controller/buffer-operation-queue.ts +19 -16
  28. package/src/controller/cap-level-controller.ts +2 -2
  29. package/src/controller/cmcd-controller.ts +6 -27
  30. package/src/controller/content-steering-controller.ts +6 -8
  31. package/src/controller/eme-controller.ts +22 -9
  32. package/src/controller/error-controller.ts +8 -6
  33. package/src/controller/fps-controller.ts +3 -2
  34. package/src/controller/fragment-tracker.ts +11 -15
  35. package/src/controller/gap-controller.ts +16 -43
  36. package/src/controller/latency-controller.ts +11 -9
  37. package/src/controller/level-controller.ts +18 -12
  38. package/src/controller/stream-controller.ts +31 -36
  39. package/src/controller/subtitle-stream-controller.ts +40 -28
  40. package/src/controller/subtitle-track-controller.ts +3 -5
  41. package/src/controller/timeline-controller.ts +30 -23
  42. package/src/crypt/aes-crypto.ts +2 -21
  43. package/src/crypt/decrypter.ts +18 -32
  44. package/src/crypt/fast-aes-key.ts +5 -24
  45. package/src/demux/audio/adts.ts +4 -9
  46. package/src/demux/sample-aes.ts +0 -2
  47. package/src/demux/transmuxer-interface.ts +12 -4
  48. package/src/demux/transmuxer-worker.ts +4 -4
  49. package/src/demux/transmuxer.ts +3 -16
  50. package/src/demux/tsdemuxer.ts +37 -71
  51. package/src/demux/video/avc-video-parser.ts +119 -208
  52. package/src/demux/video/base-video-parser.ts +2 -134
  53. package/src/demux/video/exp-golomb.ts +208 -0
  54. package/src/events.ts +0 -7
  55. package/src/hls.ts +37 -49
  56. package/src/loader/fragment-loader.ts +2 -9
  57. package/src/loader/key-loader.ts +0 -2
  58. package/src/loader/level-key.ts +9 -10
  59. package/src/loader/playlist-loader.ts +5 -4
  60. package/src/remux/mp4-generator.ts +1 -196
  61. package/src/remux/mp4-remuxer.ts +7 -23
  62. package/src/task-loop.ts +2 -5
  63. package/src/types/component-api.ts +0 -2
  64. package/src/types/demuxer.ts +0 -3
  65. package/src/types/events.ts +0 -4
  66. package/src/utils/buffer-helper.ts +31 -12
  67. package/src/utils/codecs.ts +5 -34
  68. package/src/utils/logger.ts +24 -54
  69. package/src/utils/mp4-tools.ts +2 -4
  70. package/src/crypt/decrypter-aes-mode.ts +0 -4
  71. package/src/demux/video/hevc-video-parser.ts +0 -746
  72. package/src/utils/encryption-methods-util.ts +0 -21
@@ -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 {
@@ -7,12 +7,7 @@ import {
7
7
  pickMostCompleteCodecName,
8
8
  } from '../utils/codecs';
9
9
  import { getMediaSource } from '../utils/mediasource-helper';
10
- import {
11
- ElementaryStreamTypes,
12
- type Part,
13
- type Fragment,
14
- } from '../loader/fragment';
15
- import { PlaylistLevelType } from '../types/loader';
10
+ import { ElementaryStreamTypes } from '../loader/fragment';
16
11
  import type { TrackSet } from '../types/track';
17
12
  import BufferOperationQueue from './buffer-operation-queue';
18
13
  import {
@@ -36,7 +31,6 @@ import type {
36
31
  import type { ComponentAPI } from '../types/component-api';
37
32
  import type { ChunkMetadata } from '../types/transmuxer';
38
33
  import type Hls from '../hls';
39
- import type { FragmentTracker } from './fragment-tracker';
40
34
  import type { LevelDetails } from '../loader/level-details';
41
35
  import type { HlsConfig } from '../config';
42
36
 
@@ -48,7 +42,7 @@ interface BufferedChangeEvent extends Event {
48
42
  readonly removedRanges?: TimeRanges;
49
43
  }
50
44
 
51
- export default class BufferController extends Logger implements ComponentAPI {
45
+ export default class BufferController implements ComponentAPI {
52
46
  // The level details used to determine duration, target-duration and live
53
47
  private details: LevelDetails | null = null;
54
48
  // cache the self generated object url to detect hijack of video tag
@@ -59,7 +53,6 @@ export default class BufferController extends Logger implements ComponentAPI {
59
53
  private listeners!: SourceBufferListeners;
60
54
 
61
55
  private hls: Hls;
62
- private fragmentTracker: FragmentTracker;
63
56
 
64
57
  // The number of BUFFER_CODEC events received before any sourceBuffers are created
65
58
  public bufferCodecEventsExpected: number = 0;
@@ -76,14 +69,6 @@ export default class BufferController extends Logger implements ComponentAPI {
76
69
  // Last MP3 audio chunk appended
77
70
  private lastMpegAudioChunk: ChunkMetadata | null = null;
78
71
 
79
- // Audio fragment blocked from appending until corresponding video appends or context changes
80
- private blockedAudioAppend: {
81
- op: BufferOperation;
82
- frag: Fragment | Part;
83
- } | null = null;
84
- // Keep track of video append position for unblocking audio
85
- private lastVideoAppendEnd: number = 0;
86
-
87
72
  private appendSource: boolean;
88
73
 
89
74
  // counters
@@ -97,11 +82,20 @@ export default class BufferController extends Logger implements ComponentAPI {
97
82
  public pendingTracks: TrackSet = {};
98
83
  public sourceBuffer!: SourceBuffers;
99
84
 
100
- constructor(hls: Hls, fragmentTracker: FragmentTracker) {
101
- super('buffer-controller', hls.logger);
85
+ protected log: (msg: any) => void;
86
+ protected warn: (msg: any, obj?: any) => void;
87
+ protected error: (msg: any, obj?: any) => void;
88
+
89
+ constructor(hls: Hls) {
102
90
  this.hls = hls;
103
- this.fragmentTracker = fragmentTracker;
104
- this.appendSource = hls.config.preferManagedMediaSource;
91
+ const logPrefix = '[buffer-controller]';
92
+ this.appendSource =
93
+ hls.config.preferManagedMediaSource &&
94
+ typeof self !== 'undefined' &&
95
+ (self as any).ManagedMediaSource;
96
+ this.log = logger.log.bind(logger, logPrefix);
97
+ this.warn = logger.warn.bind(logger, logPrefix);
98
+ this.error = logger.error.bind(logger, logPrefix);
105
99
  this._initSourceBuffer();
106
100
  this.registerListeners();
107
101
  }
@@ -118,13 +112,7 @@ export default class BufferController extends Logger implements ComponentAPI {
118
112
  this.details = null;
119
113
  this.lastMpegAudioChunk = null;
120
114
  // @ts-ignore
121
- this.hls = this.fragmentTracker = null;
122
- // @ts-ignore
123
- this._onMediaSourceOpen = this._onMediaSourceClose = null;
124
- // @ts-ignore
125
- this._onMediaSourceEnded = null;
126
- // @ts-ignore
127
- this._onStartStreaming = this._onEndStreaming = null;
115
+ this.hls = null;
128
116
  }
129
117
 
130
118
  protected registerListeners() {
@@ -173,8 +161,6 @@ export default class BufferController extends Logger implements ComponentAPI {
173
161
  audiovideo: 0,
174
162
  };
175
163
  this.lastMpegAudioChunk = null;
176
- this.blockedAudioAppend = null;
177
- this.lastVideoAppendEnd = 0;
178
164
  }
179
165
 
180
166
  private onManifestLoading() {
@@ -211,8 +197,10 @@ export default class BufferController extends Logger implements ComponentAPI {
211
197
  ms.addEventListener('sourceopen', this._onMediaSourceOpen);
212
198
  ms.addEventListener('sourceended', this._onMediaSourceEnded);
213
199
  ms.addEventListener('sourceclose', this._onMediaSourceClose);
214
- ms.addEventListener('startstreaming', this._onStartStreaming);
215
- ms.addEventListener('endstreaming', this._onEndStreaming);
200
+ if (this.appendSource) {
201
+ ms.addEventListener('startstreaming', this._onStartStreaming);
202
+ ms.addEventListener('endstreaming', this._onEndStreaming);
203
+ }
216
204
 
217
205
  // cache the locally generated object url
218
206
  const objectUrl = (this._objectUrl = self.URL.createObjectURL(ms));
@@ -271,8 +259,13 @@ export default class BufferController extends Logger implements ComponentAPI {
271
259
  mediaSource.removeEventListener('sourceopen', this._onMediaSourceOpen);
272
260
  mediaSource.removeEventListener('sourceended', this._onMediaSourceEnded);
273
261
  mediaSource.removeEventListener('sourceclose', this._onMediaSourceClose);
274
- mediaSource.removeEventListener('startstreaming', this._onStartStreaming);
275
- mediaSource.removeEventListener('endstreaming', this._onEndStreaming);
262
+ if (this.appendSource) {
263
+ mediaSource.removeEventListener(
264
+ 'startstreaming',
265
+ this._onStartStreaming,
266
+ );
267
+ mediaSource.removeEventListener('endstreaming', this._onEndStreaming);
268
+ }
276
269
 
277
270
  // Detach properly the MediaSource from the HTMLMediaElement as
278
271
  // suggested in https://github.com/w3c/media-source/issues/53.
@@ -313,7 +306,6 @@ export default class BufferController extends Logger implements ComponentAPI {
313
306
  this.resetBuffer(type);
314
307
  });
315
308
  this._initSourceBuffer();
316
- this.hls.resumeBuffering();
317
309
  }
318
310
 
319
311
  private resetBuffer(type: SourceBufferName) {
@@ -339,11 +331,11 @@ export default class BufferController extends Logger implements ComponentAPI {
339
331
  ) {
340
332
  const sourceBufferCount = this.getSourceBufferTypes().length;
341
333
  const trackNames = Object.keys(data);
342
- trackNames.forEach((trackName: SourceBufferName) => {
334
+ trackNames.forEach((trackName) => {
343
335
  if (sourceBufferCount) {
344
336
  // check if SourceBuffer codec needs to change
345
337
  const track = this.tracks[trackName];
346
- if (track && typeof track.buffer?.changeType === 'function') {
338
+ if (track && typeof track.buffer.changeType === 'function') {
347
339
  const { id, codec, levelCodec, container, metadata } =
348
340
  data[trackName];
349
341
  const currentCodecFull = pickMostCompleteCodecName(
@@ -363,7 +355,7 @@ export default class BufferController extends Logger implements ComponentAPI {
363
355
  if (trackName.slice(0, 5) === 'audio') {
364
356
  trackCodec = getCodecCompatibleName(
365
357
  trackCodec,
366
- this.hls.config.preferManagedMediaSource,
358
+ this.appendSource,
367
359
  );
368
360
  }
369
361
  const mimeType = `${container};codecs=${trackCodec}`;
@@ -407,7 +399,7 @@ export default class BufferController extends Logger implements ComponentAPI {
407
399
  }
408
400
  }
409
401
 
410
- protected appendChangeType(type: SourceBufferName, mimeType: string) {
402
+ protected appendChangeType(type, mimeType) {
411
403
  const { operationQueue } = this;
412
404
  const operation: BufferOperation = {
413
405
  execute: () => {
@@ -428,52 +420,14 @@ export default class BufferController extends Logger implements ComponentAPI {
428
420
  operationQueue.append(operation, type, !!this.pendingTracks[type]);
429
421
  }
430
422
 
431
- private blockAudio(partOrFrag: Fragment | Part) {
432
- const pStart = partOrFrag.start;
433
- const pTime = pStart + partOrFrag.duration * 0.05;
434
- const atGap =
435
- this.fragmentTracker.getAppendedFrag(pStart, PlaylistLevelType.MAIN)
436
- ?.gap === true;
437
- if (atGap) {
438
- return;
439
- }
440
- const op: BufferOperation = {
441
- execute: () => {
442
- if (
443
- this.lastVideoAppendEnd > pTime ||
444
- (this.sourceBuffer.video &&
445
- BufferHelper.isBuffered(this.sourceBuffer.video, pTime)) ||
446
- this.fragmentTracker.getAppendedFrag(pTime, PlaylistLevelType.MAIN)
447
- ?.gap === true
448
- ) {
449
- this.blockedAudioAppend = null;
450
- this.operationQueue.shiftAndExecuteNext('audio');
451
- }
452
- },
453
- onStart: () => {},
454
- onComplete: () => {},
455
- onError: () => {},
456
- };
457
- this.blockedAudioAppend = { op, frag: partOrFrag };
458
- this.operationQueue.append(op, 'audio', true);
459
- }
460
-
461
- private unblockAudio() {
462
- const blockedAudioAppend = this.blockedAudioAppend;
463
- if (blockedAudioAppend) {
464
- this.blockedAudioAppend = null;
465
- this.operationQueue.unblockAudio(blockedAudioAppend.op);
466
- }
467
- }
468
-
469
423
  protected onBufferAppending(
470
424
  event: Events.BUFFER_APPENDING,
471
425
  eventData: BufferAppendingData,
472
426
  ) {
473
- const { operationQueue, tracks } = this;
474
- const { data, type, parent, frag, part, chunkMeta } = eventData;
427
+ const { hls, operationQueue, tracks } = this;
428
+ const { data, type, frag, part, chunkMeta } = eventData;
475
429
  const chunkStats = chunkMeta.buffering[type];
476
- const sn = frag.sn;
430
+
477
431
  const bufferAppendingStart = self.performance.now();
478
432
  chunkStats.start = bufferAppendingStart;
479
433
  const fragBuffering = frag.stats.buffering;
@@ -500,44 +454,7 @@ export default class BufferController extends Logger implements ComponentAPI {
500
454
  this.lastMpegAudioChunk = chunkMeta;
501
455
  }
502
456
 
503
- // Block audio append until overlapping video append
504
- const videoSb = this.sourceBuffer.video;
505
- if (videoSb && sn !== 'initSegment') {
506
- const partOrFrag = part || frag;
507
- const blockedAudioAppend = this.blockedAudioAppend;
508
- if (type === 'audio' && parent !== 'main' && !this.blockedAudioAppend) {
509
- const pStart = partOrFrag.start;
510
- const pTime = pStart + partOrFrag.duration * 0.05;
511
- const vbuffered = videoSb.buffered;
512
- const vappending = this.operationQueue.current('video');
513
- if (!vbuffered.length && !vappending) {
514
- // wait for video before appending audio
515
- this.blockAudio(partOrFrag);
516
- } else if (
517
- !vappending &&
518
- !BufferHelper.isBuffered(videoSb, pTime) &&
519
- this.lastVideoAppendEnd < pTime
520
- ) {
521
- // audio is ahead of video
522
- this.blockAudio(partOrFrag);
523
- }
524
- } else if (type === 'video') {
525
- const videoAppendEnd = partOrFrag.end;
526
- if (blockedAudioAppend) {
527
- const audioStart = blockedAudioAppend.frag.start;
528
- if (
529
- videoAppendEnd > audioStart ||
530
- videoAppendEnd < this.lastVideoAppendEnd ||
531
- BufferHelper.isBuffered(videoSb, audioStart)
532
- ) {
533
- this.unblockAudio();
534
- }
535
- }
536
- this.lastVideoAppendEnd = videoAppendEnd;
537
- }
538
- }
539
-
540
- const fragStart = (part || frag).start;
457
+ const fragStart = frag.start;
541
458
  const operation: BufferOperation = {
542
459
  execute: () => {
543
460
  chunkStats.executeStart = self.performance.now();
@@ -547,7 +464,7 @@ export default class BufferController extends Logger implements ComponentAPI {
547
464
  const delta = fragStart - sb.timestampOffset;
548
465
  if (Math.abs(delta) >= 0.1) {
549
466
  this.log(
550
- `Updating audio SourceBuffer timestampOffset to ${fragStart} (delta: ${delta}) sn: ${sn})`,
467
+ `Updating audio SourceBuffer timestampOffset to ${fragStart} (delta: ${delta}) sn: ${frag.sn})`,
551
468
  );
552
469
  sb.timestampOffset = fragStart;
553
470
  }
@@ -616,27 +533,30 @@ export default class BufferController extends Logger implements ComponentAPI {
616
533
  browser is able to evict some data from sourcebuffer. Retrying can help recover.
617
534
  */
618
535
  this.warn(
619
- `Failed ${appendErrorCount}/${this.hls.config.appendErrorMaxRetry} times to append segment in "${type}" sourceBuffer`,
536
+ `Failed ${appendErrorCount}/${hls.config.appendErrorMaxRetry} times to append segment in "${type}" sourceBuffer`,
620
537
  );
621
- if (appendErrorCount >= this.hls.config.appendErrorMaxRetry) {
538
+ if (appendErrorCount >= hls.config.appendErrorMaxRetry) {
622
539
  event.fatal = true;
623
540
  }
624
541
  }
625
- this.hls.trigger(Events.ERROR, event);
542
+ hls.trigger(Events.ERROR, event);
626
543
  },
627
544
  };
628
545
  operationQueue.append(operation, type, !!this.pendingTracks[type]);
629
546
  }
630
547
 
631
- private getFlushOp(
632
- type: SourceBufferName,
633
- start: number,
634
- end: number,
635
- ): BufferOperation {
636
- return {
637
- execute: () => {
638
- this.removeExecutor(type, start, end);
639
- },
548
+ protected onBufferFlushing(
549
+ event: Events.BUFFER_FLUSHING,
550
+ data: BufferFlushingData,
551
+ ) {
552
+ const { operationQueue } = this;
553
+ const flushOperation = (type: SourceBufferName): BufferOperation => ({
554
+ execute: this.removeExecutor.bind(
555
+ this,
556
+ type,
557
+ data.startOffset,
558
+ data.endOffset,
559
+ ),
640
560
  onStart: () => {
641
561
  // logger.debug(`[buffer-controller]: Started flushing ${data.startOffset} -> ${data.endOffset} for ${type} Source Buffer`);
642
562
  },
@@ -647,26 +567,13 @@ export default class BufferController extends Logger implements ComponentAPI {
647
567
  onError: (error: Error) => {
648
568
  this.warn(`Failed to remove from ${type} SourceBuffer`, error);
649
569
  },
650
- };
651
- }
570
+ });
652
571
 
653
- protected onBufferFlushing(
654
- event: Events.BUFFER_FLUSHING,
655
- data: BufferFlushingData,
656
- ) {
657
- const { operationQueue } = this;
658
- const { type, startOffset, endOffset } = data;
659
- if (type) {
660
- operationQueue.append(
661
- this.getFlushOp(type, startOffset, endOffset),
662
- type,
663
- );
572
+ if (data.type) {
573
+ operationQueue.append(flushOperation(data.type), data.type);
664
574
  } else {
665
- this.getSourceBufferTypes().forEach((sbType: SourceBufferName) => {
666
- operationQueue.append(
667
- this.getFlushOp(sbType, startOffset, endOffset),
668
- sbType,
669
- );
575
+ this.getSourceBufferTypes().forEach((type: SourceBufferName) => {
576
+ operationQueue.append(flushOperation(type), type);
670
577
  });
671
578
  }
672
579
  }
@@ -719,9 +626,6 @@ export default class BufferController extends Logger implements ComponentAPI {
719
626
  // on BUFFER_EOS mark matching sourcebuffer(s) as ended and trigger checkEos()
720
627
  // an undefined data.type will mark all buffers as EOS.
721
628
  protected onBufferEos(event: Events.BUFFER_EOS, data: BufferEOSData) {
722
- if (data.type === 'video') {
723
- this.unblockAudio();
724
- }
725
629
  const ended = this.getSourceBufferTypes().reduce((acc, type) => {
726
630
  const sb = this.sourceBuffer[type];
727
631
  if (sb && (!data.type || data.type === type)) {
@@ -767,14 +671,11 @@ export default class BufferController extends Logger implements ComponentAPI {
767
671
  return;
768
672
  }
769
673
  this.details = details;
770
- const durationAndRange = this.getDurationAndRange();
771
- if (!durationAndRange) {
772
- return;
773
- }
674
+
774
675
  if (this.getSourceBufferTypes().length) {
775
- this.blockBuffers(() => this.updateMediaSource(durationAndRange));
676
+ this.blockBuffers(this.updateMediaElementDuration.bind(this));
776
677
  } else {
777
- this.updateMediaSource(durationAndRange);
678
+ this.updateMediaElementDuration();
778
679
  }
779
680
  }
780
681
 
@@ -926,18 +827,14 @@ export default class BufferController extends Logger implements ComponentAPI {
926
827
  * 'liveDurationInfinity` is set to `true`
927
828
  * More details: https://github.com/video-dev/hls.js/issues/355
928
829
  */
929
- private getDurationAndRange(): {
930
- duration: number;
931
- start?: number;
932
- end?: number;
933
- } | null {
830
+ private updateMediaElementDuration() {
934
831
  if (
935
832
  !this.details ||
936
833
  !this.media ||
937
834
  !this.mediaSource ||
938
835
  this.mediaSource.readyState !== 'open'
939
836
  ) {
940
- return null;
837
+ return;
941
838
  }
942
839
  const { details, hls, media, mediaSource } = this;
943
840
  const levelDuration = details.fragments[0].start + details.totalduration;
@@ -949,47 +846,31 @@ export default class BufferController extends Logger implements ComponentAPI {
949
846
  if (details.live && hls.config.liveDurationInfinity) {
950
847
  // Override duration to Infinity
951
848
  mediaSource.duration = Infinity;
952
- const len = details.fragments.length;
953
- if (len && details.live && !!mediaSource.setLiveSeekableRange) {
954
- const start = Math.max(0, details.fragments[0].start);
955
- const end = Math.max(start, start + details.totalduration);
956
- return { duration: Infinity, start, end };
957
- }
958
- return { duration: Infinity };
849
+ this.updateSeekableRange(details);
959
850
  } else if (
960
851
  (levelDuration > msDuration && levelDuration > mediaDuration) ||
961
852
  !Number.isFinite(mediaDuration)
962
853
  ) {
963
- return { duration: levelDuration };
854
+ // levelDuration was the last value we set.
855
+ // not using mediaSource.duration as the browser may tweak this value
856
+ // only update Media Source duration if its value increase, this is to avoid
857
+ // flushing already buffered portion when switching between quality level
858
+ this.log(`Updating Media Source duration to ${levelDuration.toFixed(3)}`);
859
+ mediaSource.duration = levelDuration;
964
860
  }
965
- return null;
966
861
  }
967
862
 
968
- private updateMediaSource({
969
- duration,
970
- start,
971
- end,
972
- }: {
973
- duration: number;
974
- start?: number;
975
- end?: number;
976
- }) {
977
- if (
978
- !this.media ||
979
- !this.mediaSource ||
980
- this.mediaSource.readyState !== 'open'
981
- ) {
982
- return;
983
- }
984
- if (Number.isFinite(duration)) {
985
- this.log(`Updating Media Source duration to ${duration.toFixed(3)}`);
986
- }
987
- this.mediaSource.duration = duration;
988
- if (start !== undefined && end !== undefined) {
863
+ updateSeekableRange(levelDetails) {
864
+ const mediaSource = this.mediaSource;
865
+ const fragments = levelDetails.fragments;
866
+ const len = fragments.length;
867
+ if (len && levelDetails.live && mediaSource?.setLiveSeekableRange) {
868
+ const start = Math.max(0, fragments[0].start);
869
+ const end = Math.max(start, start + levelDetails.totalduration);
989
870
  this.log(
990
- `Media Source duration is set to ${this.mediaSource.duration}. Setting seekable range to ${start}-${end}.`,
871
+ `Media Source duration is set to ${mediaSource.duration}. Setting seekable range to ${start}-${end}.`,
991
872
  );
992
- this.mediaSource.setLiveSeekableRange(start, end);
873
+ mediaSource.setLiveSeekableRange(start, end);
993
874
  }
994
875
  }
995
876
 
@@ -1049,10 +930,7 @@ export default class BufferController extends Logger implements ComponentAPI {
1049
930
  let codec = track.levelCodec || track.codec;
1050
931
  if (codec) {
1051
932
  if (trackName.slice(0, 5) === 'audio') {
1052
- codec = getCodecCompatibleName(
1053
- codec,
1054
- this.hls.config.preferManagedMediaSource,
1055
- );
933
+ codec = getCodecCompatibleName(codec, this.appendSource);
1056
934
  }
1057
935
  }
1058
936
  const mimeType = `${track.container};codecs=${codec}`;
@@ -1065,19 +943,21 @@ export default class BufferController extends Logger implements ComponentAPI {
1065
943
  this.addBufferListener(sbName, 'updateend', this._onSBUpdateEnd);
1066
944
  this.addBufferListener(sbName, 'error', this._onSBUpdateError);
1067
945
  // ManagedSourceBuffer bufferedchange event
1068
- this.addBufferListener(
1069
- sbName,
1070
- 'bufferedchange',
1071
- (type: SourceBufferName, event: BufferedChangeEvent) => {
1072
- // If media was ejected check for a change. Added ranges are redundant with changes on 'updateend' event.
1073
- const removedRanges = event.removedRanges;
1074
- if (removedRanges?.length) {
1075
- this.hls.trigger(Events.BUFFER_FLUSHED, {
1076
- type: trackName as SourceBufferName,
1077
- });
1078
- }
1079
- },
1080
- );
946
+ if (this.appendSource) {
947
+ this.addBufferListener(
948
+ sbName,
949
+ 'bufferedchange',
950
+ (type: SourceBufferName, event: BufferedChangeEvent) => {
951
+ // If media was ejected check for a change. Added ranges are redundant with changes on 'updateend' event.
952
+ const removedRanges = event.removedRanges;
953
+ if (removedRanges?.length) {
954
+ this.hls.trigger(Events.BUFFER_FLUSHED, {
955
+ type: trackName as SourceBufferName,
956
+ });
957
+ }
958
+ },
959
+ );
960
+ }
1081
961
 
1082
962
  this.tracks[trackName] = {
1083
963
  buffer: sb,
@@ -1108,10 +988,7 @@ export default class BufferController extends Logger implements ComponentAPI {
1108
988
  this.log('Media source opened');
1109
989
  if (media) {
1110
990
  media.removeEventListener('emptied', this._onMediaEmptied);
1111
- const durationAndRange = this.getDurationAndRange();
1112
- if (durationAndRange) {
1113
- this.updateMediaSource(durationAndRange);
1114
- }
991
+ this.updateMediaElementDuration();
1115
992
  this.hls.trigger(Events.MEDIA_ATTACHED, {
1116
993
  media,
1117
994
  mediaSource: mediaSource as MediaSource,
@@ -1136,7 +1013,7 @@ export default class BufferController extends Logger implements ComponentAPI {
1136
1013
  private _onMediaEmptied = () => {
1137
1014
  const { mediaSrc, _objectUrl } = this;
1138
1015
  if (mediaSrc !== _objectUrl) {
1139
- this.error(
1016
+ logger.error(
1140
1017
  `Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`,
1141
1018
  );
1142
1019
  }
@@ -1232,7 +1109,7 @@ export default class BufferController extends Logger implements ComponentAPI {
1232
1109
  }
1233
1110
  return;
1234
1111
  }
1235
- sb.ending = false;
1112
+
1236
1113
  sb.ended = false;
1237
1114
  sb.appendBuffer(data);
1238
1115
  }
@@ -1255,14 +1132,10 @@ export default class BufferController extends Logger implements ComponentAPI {
1255
1132
  const blockingOperations = buffers.map((type) =>
1256
1133
  operationQueue.appendBlocker(type as SourceBufferName),
1257
1134
  );
1258
- const audioBlocked = buffers.length > 1 && !!this.blockedAudioAppend;
1259
- if (audioBlocked) {
1260
- this.unblockAudio();
1261
- }
1262
- Promise.all(blockingOperations).then((result) => {
1135
+ Promise.all(blockingOperations).then(() => {
1263
1136
  // logger.debug(`[buffer-controller]: Blocking operation resolved; unblocking ${buffers} SourceBuffer`);
1264
1137
  onUnblocked();
1265
- buffers.forEach((type, i) => {
1138
+ buffers.forEach((type) => {
1266
1139
  const sb = this.sourceBuffer[type];
1267
1140
  // Only cycle the queue if the SB is not updating. There's a bug in Chrome which sets the SB updating flag to
1268
1141
  // 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
 
@@ -151,13 +152,12 @@ class CapLevelController implements ComponentAPI {
151
152
  const hls = this.hls;
152
153
  const maxLevel = this.getMaxLevel(levels.length - 1);
153
154
  if (maxLevel !== this.autoLevelCapping) {
154
- hls.logger.log(
155
+ logger.log(
155
156
  `Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`,
156
157
  );
157
158
  }
158
159
  hls.autoLevelCapping = maxLevel;
159
160
  if (
160
- hls.autoLevelEnabled &&
161
161
  hls.autoLevelCapping > this.autoLevelCapping &&
162
162
  this.streamController
163
163
  ) {