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.
- package/README.md +1 -2
- package/dist/hls-demo.js +0 -10
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +1283 -2293
- package/dist/hls.js.d.ts +84 -97
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +1030 -1435
- package/dist/hls.light.js.map +1 -1
- package/dist/hls.light.min.js +1 -1
- package/dist/hls.light.min.js.map +1 -1
- package/dist/hls.light.mjs +809 -1209
- package/dist/hls.light.mjs.map +1 -1
- package/dist/hls.min.js +1 -1
- package/dist/hls.min.js.map +1 -1
- package/dist/hls.mjs +1039 -2030
- package/dist/hls.mjs.map +1 -1
- package/dist/hls.worker.js +1 -1
- package/dist/hls.worker.js.map +1 -1
- package/package.json +22 -22
- package/src/config.ts +2 -3
- package/src/controller/abr-controller.ts +20 -24
- package/src/controller/audio-stream-controller.ts +74 -68
- package/src/controller/audio-track-controller.ts +1 -1
- package/src/controller/base-playlist-controller.ts +8 -20
- package/src/controller/base-stream-controller.ts +36 -157
- package/src/controller/buffer-controller.ts +99 -226
- package/src/controller/buffer-operation-queue.ts +19 -16
- package/src/controller/cap-level-controller.ts +2 -2
- package/src/controller/cmcd-controller.ts +6 -27
- package/src/controller/content-steering-controller.ts +6 -8
- package/src/controller/eme-controller.ts +22 -9
- package/src/controller/error-controller.ts +8 -6
- package/src/controller/fps-controller.ts +3 -2
- package/src/controller/fragment-tracker.ts +11 -15
- package/src/controller/gap-controller.ts +16 -43
- package/src/controller/latency-controller.ts +11 -9
- package/src/controller/level-controller.ts +18 -12
- package/src/controller/stream-controller.ts +31 -36
- package/src/controller/subtitle-stream-controller.ts +40 -28
- package/src/controller/subtitle-track-controller.ts +3 -5
- package/src/controller/timeline-controller.ts +30 -23
- package/src/crypt/aes-crypto.ts +2 -21
- package/src/crypt/decrypter.ts +18 -32
- package/src/crypt/fast-aes-key.ts +5 -24
- package/src/demux/audio/adts.ts +4 -9
- package/src/demux/sample-aes.ts +0 -2
- package/src/demux/transmuxer-interface.ts +12 -4
- package/src/demux/transmuxer-worker.ts +4 -4
- package/src/demux/transmuxer.ts +3 -16
- package/src/demux/tsdemuxer.ts +37 -71
- package/src/demux/video/avc-video-parser.ts +119 -208
- package/src/demux/video/base-video-parser.ts +2 -134
- package/src/demux/video/exp-golomb.ts +208 -0
- package/src/events.ts +0 -7
- package/src/hls.ts +37 -49
- package/src/loader/fragment-loader.ts +2 -9
- package/src/loader/key-loader.ts +0 -2
- package/src/loader/level-key.ts +9 -10
- package/src/loader/playlist-loader.ts +5 -4
- package/src/remux/mp4-generator.ts +1 -196
- package/src/remux/mp4-remuxer.ts +7 -23
- package/src/task-loop.ts +2 -5
- package/src/types/component-api.ts +0 -2
- package/src/types/demuxer.ts +0 -3
- package/src/types/events.ts +0 -4
- package/src/utils/buffer-helper.ts +31 -12
- package/src/utils/codecs.ts +5 -34
- package/src/utils/logger.ts +24 -54
- package/src/utils/mp4-tools.ts +2 -4
- package/src/crypt/decrypter-aes-mode.ts +0 -4
- package/src/demux/video/hevc-video-parser.ts +0 -746
- package/src/utils/encryption-methods-util.ts +0 -21
@@ -1,5 +1,5 @@
|
|
1
1
|
import { Events } from '../events';
|
2
|
-
import {
|
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
|
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
|
-
|
101
|
-
|
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
|
-
|
104
|
-
this.appendSource =
|
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 =
|
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
|
-
|
215
|
-
|
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
|
-
|
275
|
-
|
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
|
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
|
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.
|
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
|
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,
|
427
|
+
const { hls, operationQueue, tracks } = this;
|
428
|
+
const { data, type, frag, part, chunkMeta } = eventData;
|
475
429
|
const chunkStats = chunkMeta.buffering[type];
|
476
|
-
|
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
|
-
|
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}/${
|
536
|
+
`Failed ${appendErrorCount}/${hls.config.appendErrorMaxRetry} times to append segment in "${type}" sourceBuffer`,
|
620
537
|
);
|
621
|
-
if (appendErrorCount >=
|
538
|
+
if (appendErrorCount >= hls.config.appendErrorMaxRetry) {
|
622
539
|
event.fatal = true;
|
623
540
|
}
|
624
541
|
}
|
625
|
-
|
542
|
+
hls.trigger(Events.ERROR, event);
|
626
543
|
},
|
627
544
|
};
|
628
545
|
operationQueue.append(operation, type, !!this.pendingTracks[type]);
|
629
546
|
}
|
630
547
|
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
execute: (
|
638
|
-
this
|
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
|
-
|
654
|
-
|
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((
|
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
|
-
|
771
|
-
if (!durationAndRange) {
|
772
|
-
return;
|
773
|
-
}
|
674
|
+
|
774
675
|
if (this.getSourceBufferTypes().length) {
|
775
|
-
this.blockBuffers(
|
676
|
+
this.blockBuffers(this.updateMediaElementDuration.bind(this));
|
776
677
|
} else {
|
777
|
-
this.
|
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
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
|
973
|
-
|
974
|
-
|
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 ${
|
871
|
+
`Media Source duration is set to ${mediaSource.duration}. Setting seekable range to ${start}-${end}.`,
|
991
872
|
);
|
992
|
-
|
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.
|
1069
|
-
|
1070
|
-
|
1071
|
-
|
1072
|
-
|
1073
|
-
|
1074
|
-
|
1075
|
-
|
1076
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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)
|
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
|
-
|
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
|
) {
|