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.
- package/README.md +3 -4
- package/dist/hls-demo.js +38 -41
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +2194 -3476
- package/dist/hls.js.d.ts +85 -108
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +3137 -3784
- 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 +1256 -1928
- 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 +4182 -5487
- 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 +36 -36
- 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 +10 -27
- package/src/controller/base-stream-controller.ts +38 -160
- package/src/controller/buffer-controller.ts +92 -230
- package/src/controller/buffer-operation-queue.ts +19 -16
- package/src/controller/cap-level-controller.ts +2 -3
- package/src/controller/cmcd-controller.ts +14 -51
- package/src/controller/content-steering-controller.ts +15 -29
- package/src/controller/eme-controller.ts +23 -10
- package/src/controller/error-controller.ts +8 -6
- package/src/controller/fps-controller.ts +3 -8
- package/src/controller/fragment-tracker.ts +11 -15
- package/src/controller/gap-controller.ts +16 -43
- package/src/controller/id3-track-controller.ts +7 -7
- package/src/controller/latency-controller.ts +11 -9
- package/src/controller/level-controller.ts +19 -37
- package/src/controller/stream-controller.ts +32 -37
- package/src/controller/subtitle-stream-controller.ts +40 -28
- package/src/controller/subtitle-track-controller.ts +3 -5
- package/src/controller/timeline-controller.ts +21 -19
- package/src/crypt/aes-crypto.ts +2 -21
- package/src/crypt/decrypter.ts +16 -32
- package/src/crypt/fast-aes-key.ts +5 -28
- package/src/demux/audio/aacdemuxer.ts +2 -2
- package/src/demux/audio/ac3-demuxer.ts +3 -4
- package/src/demux/audio/adts.ts +4 -9
- package/src/demux/audio/base-audio-demuxer.ts +14 -16
- package/src/demux/audio/mp3demuxer.ts +3 -4
- package/src/demux/audio/mpegaudio.ts +1 -1
- package/src/demux/id3.ts +411 -0
- package/src/demux/mp4demuxer.ts +7 -7
- 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 +38 -75
- package/src/demux/video/avc-video-parser.ts +119 -208
- package/src/demux/video/base-video-parser.ts +18 -147
- package/src/demux/video/exp-golomb.ts +208 -0
- package/src/events.ts +1 -8
- package/src/exports-named.ts +1 -1
- package/src/hls.ts +38 -61
- package/src/loader/fragment-loader.ts +3 -10
- package/src/loader/key-loader.ts +1 -3
- 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 +8 -24
- package/src/task-loop.ts +2 -5
- package/src/types/component-api.ts +1 -3
- package/src/types/demuxer.ts +0 -4
- package/src/types/events.ts +0 -4
- package/src/types/remuxer.ts +1 -1
- package/src/utils/buffer-helper.ts +31 -12
- package/src/utils/cea-608-parser.ts +3 -1
- package/src/utils/codecs.ts +5 -34
- package/src/utils/fetch-loader.ts +1 -1
- package/src/utils/imsc1-ttml-parser.ts +1 -1
- package/src/utils/keysystem-util.ts +6 -1
- package/src/utils/logger.ts +23 -58
- package/src/utils/mp4-tools.ts +3 -5
- package/src/utils/webvtt-parser.ts +1 -1
- package/src/crypt/decrypter-aes-mode.ts +0 -4
- package/src/demux/video/hevc-video-parser.ts +0 -749
- package/src/utils/encryption-methods-util.ts +0 -21
- package/src/utils/utf8-utils.ts +0 -18
@@ -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 {
|
@@ -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
|
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
|
-
|
104
|
-
|
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
|
-
|
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 =
|
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
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
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
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
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
|
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
|
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
|
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,
|
430
|
+
const { hls, operationQueue, tracks } = this;
|
431
|
+
const { data, type, frag, part, chunkMeta } = eventData;
|
490
432
|
const chunkStats = chunkMeta.buffering[type];
|
491
|
-
|
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
|
-
|
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}/${
|
539
|
+
`Failed ${appendErrorCount}/${hls.config.appendErrorMaxRetry} times to append segment in "${type}" sourceBuffer`,
|
635
540
|
);
|
636
|
-
if (appendErrorCount >=
|
541
|
+
if (appendErrorCount >= hls.config.appendErrorMaxRetry) {
|
637
542
|
event.fatal = true;
|
638
543
|
}
|
639
544
|
}
|
640
|
-
|
545
|
+
hls.trigger(Events.ERROR, event);
|
641
546
|
},
|
642
547
|
};
|
643
548
|
operationQueue.append(operation, type, !!this.pendingTracks[type]);
|
644
549
|
}
|
645
550
|
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
execute: (
|
653
|
-
this
|
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
|
-
|
669
|
-
|
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((
|
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
|
-
|
786
|
-
if (!durationAndRange) {
|
787
|
-
return;
|
788
|
-
}
|
677
|
+
|
789
678
|
if (this.getSourceBufferTypes().length) {
|
790
|
-
this.blockBuffers(
|
679
|
+
this.blockBuffers(this.updateMediaElementDuration.bind(this));
|
791
680
|
} else {
|
792
|
-
this.
|
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
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
984
|
-
|
985
|
-
|
986
|
-
|
987
|
-
|
988
|
-
|
989
|
-
|
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 ${
|
874
|
+
`Media Source duration is set to ${mediaSource.duration}. Setting seekable range to ${start}-${end}.`,
|
1006
875
|
);
|
1007
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
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
|
|
@@ -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
|
-
|
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
|
) {
|