hls.js 1.5.11 → 1.5.12-0.canary.10340
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 +4 -3
- package/dist/hls-demo.js +41 -38
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +3477 -2195
- package/dist/hls.js.d.ts +108 -85
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +2401 -1754
- 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 +2148 -1476
- 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 +2863 -1558
- 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 +3 -2
- package/src/controller/abr-controller.ts +24 -20
- package/src/controller/audio-stream-controller.ts +68 -74
- package/src/controller/audio-track-controller.ts +1 -1
- package/src/controller/base-playlist-controller.ts +27 -10
- package/src/controller/base-stream-controller.ts +160 -38
- package/src/controller/buffer-controller.ts +230 -92
- package/src/controller/buffer-operation-queue.ts +16 -19
- package/src/controller/cap-level-controller.ts +3 -2
- package/src/controller/cmcd-controller.ts +51 -14
- package/src/controller/content-steering-controller.ts +29 -15
- package/src/controller/eme-controller.ts +10 -23
- package/src/controller/error-controller.ts +6 -8
- package/src/controller/fps-controller.ts +8 -3
- package/src/controller/fragment-tracker.ts +15 -11
- package/src/controller/gap-controller.ts +43 -16
- package/src/controller/id3-track-controller.ts +7 -7
- package/src/controller/latency-controller.ts +9 -11
- package/src/controller/level-controller.ts +37 -19
- package/src/controller/stream-controller.ts +37 -32
- package/src/controller/subtitle-stream-controller.ts +28 -40
- package/src/controller/subtitle-track-controller.ts +5 -3
- package/src/controller/timeline-controller.ts +19 -21
- package/src/crypt/aes-crypto.ts +21 -2
- package/src/crypt/decrypter-aes-mode.ts +4 -0
- package/src/crypt/decrypter.ts +32 -16
- package/src/crypt/fast-aes-key.ts +28 -5
- package/src/demux/audio/aacdemuxer.ts +2 -2
- package/src/demux/audio/ac3-demuxer.ts +4 -3
- package/src/demux/audio/adts.ts +9 -4
- package/src/demux/audio/base-audio-demuxer.ts +16 -14
- package/src/demux/audio/mp3demuxer.ts +4 -3
- package/src/demux/audio/mpegaudio.ts +1 -1
- package/src/demux/mp4demuxer.ts +7 -7
- package/src/demux/sample-aes.ts +2 -0
- package/src/demux/transmuxer-interface.ts +4 -12
- package/src/demux/transmuxer-worker.ts +4 -4
- package/src/demux/transmuxer.ts +16 -3
- package/src/demux/tsdemuxer.ts +75 -38
- package/src/demux/video/avc-video-parser.ts +208 -119
- package/src/demux/video/base-video-parser.ts +147 -18
- package/src/demux/video/exp-golomb.ts +0 -208
- package/src/demux/video/hevc-video-parser.ts +749 -0
- package/src/events.ts +8 -1
- package/src/exports-named.ts +1 -1
- package/src/hls.ts +61 -38
- package/src/loader/fragment-loader.ts +10 -3
- package/src/loader/key-loader.ts +3 -1
- package/src/loader/level-key.ts +10 -9
- package/src/loader/playlist-loader.ts +4 -5
- package/src/remux/mp4-generator.ts +196 -1
- package/src/remux/mp4-remuxer.ts +24 -8
- package/src/task-loop.ts +5 -2
- package/src/types/component-api.ts +3 -1
- package/src/types/demuxer.ts +4 -0
- package/src/types/events.ts +4 -0
- package/src/types/remuxer.ts +1 -1
- package/src/utils/buffer-helper.ts +12 -31
- package/src/utils/cea-608-parser.ts +1 -3
- package/src/utils/codecs.ts +34 -5
- package/src/utils/encryption-methods-util.ts +21 -0
- package/src/utils/fetch-loader.ts +1 -1
- package/src/utils/imsc1-ttml-parser.ts +1 -1
- package/src/utils/keysystem-util.ts +1 -6
- package/src/utils/logger.ts +58 -23
- package/src/utils/mp4-tools.ts +5 -3
- package/src/utils/utf8-utils.ts +18 -0
- package/src/utils/webvtt-parser.ts +1 -1
- package/src/demux/id3.ts +0 -411
@@ -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,7 +10,12 @@ import {
|
|
10
10
|
getMediaSource,
|
11
11
|
isManagedMediaSource,
|
12
12
|
} from '../utils/mediasource-helper';
|
13
|
-
import {
|
13
|
+
import {
|
14
|
+
ElementaryStreamTypes,
|
15
|
+
type Part,
|
16
|
+
type Fragment,
|
17
|
+
} from '../loader/fragment';
|
18
|
+
import { PlaylistLevelType } from '../types/loader';
|
14
19
|
import type { TrackSet } from '../types/track';
|
15
20
|
import BufferOperationQueue from './buffer-operation-queue';
|
16
21
|
import {
|
@@ -34,6 +39,7 @@ import type {
|
|
34
39
|
import type { ComponentAPI } from '../types/component-api';
|
35
40
|
import type { ChunkMetadata } from '../types/transmuxer';
|
36
41
|
import type Hls from '../hls';
|
42
|
+
import type { FragmentTracker } from './fragment-tracker';
|
37
43
|
import type { LevelDetails } from '../loader/level-details';
|
38
44
|
import type { HlsConfig } from '../config';
|
39
45
|
|
@@ -45,7 +51,7 @@ interface BufferedChangeEvent extends Event {
|
|
45
51
|
readonly removedRanges?: TimeRanges;
|
46
52
|
}
|
47
53
|
|
48
|
-
export default class BufferController implements ComponentAPI {
|
54
|
+
export default class BufferController extends Logger implements ComponentAPI {
|
49
55
|
// The level details used to determine duration, target-duration and live
|
50
56
|
private details: LevelDetails | null = null;
|
51
57
|
// cache the self generated object url to detect hijack of video tag
|
@@ -56,6 +62,7 @@ export default class BufferController implements ComponentAPI {
|
|
56
62
|
private listeners!: SourceBufferListeners;
|
57
63
|
|
58
64
|
private hls: Hls;
|
65
|
+
private fragmentTracker: FragmentTracker;
|
59
66
|
|
60
67
|
// The number of BUFFER_CODEC events received before any sourceBuffers are created
|
61
68
|
public bufferCodecEventsExpected: number = 0;
|
@@ -72,6 +79,14 @@ export default class BufferController implements ComponentAPI {
|
|
72
79
|
// Last MP3 audio chunk appended
|
73
80
|
private lastMpegAudioChunk: ChunkMetadata | null = null;
|
74
81
|
|
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
|
+
|
75
90
|
private appendSource: boolean;
|
76
91
|
|
77
92
|
// counters
|
@@ -85,19 +100,14 @@ export default class BufferController implements ComponentAPI {
|
|
85
100
|
public pendingTracks: TrackSet = {};
|
86
101
|
public sourceBuffer!: SourceBuffers;
|
87
102
|
|
88
|
-
|
89
|
-
|
90
|
-
protected error: (msg: any, obj?: any) => void;
|
91
|
-
|
92
|
-
constructor(hls: Hls) {
|
103
|
+
constructor(hls: Hls, fragmentTracker: FragmentTracker) {
|
104
|
+
super('buffer-controller', hls.logger);
|
93
105
|
this.hls = hls;
|
94
|
-
|
106
|
+
this.fragmentTracker = fragmentTracker;
|
95
107
|
this.appendSource = isManagedMediaSource(
|
96
108
|
getMediaSource(hls.config.preferManagedMediaSource),
|
97
109
|
);
|
98
|
-
|
99
|
-
this.warn = logger.warn.bind(logger, logPrefix);
|
100
|
-
this.error = logger.error.bind(logger, logPrefix);
|
110
|
+
|
101
111
|
this._initSourceBuffer();
|
102
112
|
this.registerListeners();
|
103
113
|
}
|
@@ -114,7 +124,13 @@ export default class BufferController implements ComponentAPI {
|
|
114
124
|
this.details = null;
|
115
125
|
this.lastMpegAudioChunk = null;
|
116
126
|
// @ts-ignore
|
117
|
-
this.hls = null;
|
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;
|
118
134
|
}
|
119
135
|
|
120
136
|
protected registerListeners() {
|
@@ -163,6 +179,8 @@ export default class BufferController implements ComponentAPI {
|
|
163
179
|
audiovideo: 0,
|
164
180
|
};
|
165
181
|
this.lastMpegAudioChunk = null;
|
182
|
+
this.blockedAudioAppend = null;
|
183
|
+
this.lastVideoAppendEnd = 0;
|
166
184
|
}
|
167
185
|
|
168
186
|
private onManifestLoading() {
|
@@ -270,37 +288,38 @@ export default class BufferController implements ComponentAPI {
|
|
270
288
|
mediaSource.removeEventListener('endstreaming', this._onEndStreaming);
|
271
289
|
}
|
272
290
|
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
media.removeEventListener('emptied', this._onMediaEmptied);
|
277
|
-
if (_objectUrl) {
|
278
|
-
self.URL.revokeObjectURL(_objectUrl);
|
279
|
-
}
|
291
|
+
this.mediaSource = null;
|
292
|
+
this._objectUrl = null;
|
293
|
+
}
|
280
294
|
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
}
|
288
|
-
media.load();
|
289
|
-
} else {
|
290
|
-
this.warn(
|
291
|
-
'media|source.src was changed by a third party - skip cleanup',
|
292
|
-
);
|
293
|
-
}
|
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);
|
294
301
|
}
|
295
302
|
|
296
|
-
|
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);
|
309
|
+
}
|
310
|
+
media.load();
|
311
|
+
} else {
|
312
|
+
this.warn(
|
313
|
+
'media|source.src was changed by a third party - skip cleanup',
|
314
|
+
);
|
315
|
+
}
|
297
316
|
this.media = null;
|
298
|
-
this._objectUrl = null;
|
299
|
-
this.bufferCodecEventsExpected = this._bufferCodecEventsTotal;
|
300
|
-
this.pendingTracks = {};
|
301
|
-
this.tracks = {};
|
302
317
|
}
|
303
318
|
|
319
|
+
this.bufferCodecEventsExpected = this._bufferCodecEventsTotal;
|
320
|
+
this.pendingTracks = {};
|
321
|
+
this.tracks = {};
|
322
|
+
|
304
323
|
this.hls.trigger(Events.MEDIA_DETACHED, undefined);
|
305
324
|
}
|
306
325
|
|
@@ -309,6 +328,7 @@ export default class BufferController implements ComponentAPI {
|
|
309
328
|
this.resetBuffer(type);
|
310
329
|
});
|
311
330
|
this._initSourceBuffer();
|
331
|
+
this.hls.resumeBuffering();
|
312
332
|
}
|
313
333
|
|
314
334
|
private resetBuffer(type: SourceBufferName) {
|
@@ -334,11 +354,11 @@ export default class BufferController implements ComponentAPI {
|
|
334
354
|
) {
|
335
355
|
const sourceBufferCount = this.getSourceBufferTypes().length;
|
336
356
|
const trackNames = Object.keys(data);
|
337
|
-
trackNames.forEach((trackName) => {
|
357
|
+
trackNames.forEach((trackName: SourceBufferName) => {
|
338
358
|
if (sourceBufferCount) {
|
339
359
|
// check if SourceBuffer codec needs to change
|
340
360
|
const track = this.tracks[trackName];
|
341
|
-
if (track && typeof track.buffer
|
361
|
+
if (track && typeof track.buffer?.changeType === 'function') {
|
342
362
|
const { id, codec, levelCodec, container, metadata } =
|
343
363
|
data[trackName];
|
344
364
|
const currentCodecFull = pickMostCompleteCodecName(
|
@@ -402,7 +422,7 @@ export default class BufferController implements ComponentAPI {
|
|
402
422
|
}
|
403
423
|
}
|
404
424
|
|
405
|
-
protected appendChangeType(type, mimeType) {
|
425
|
+
protected appendChangeType(type: SourceBufferName, mimeType: string) {
|
406
426
|
const { operationQueue } = this;
|
407
427
|
const operation: BufferOperation = {
|
408
428
|
execute: () => {
|
@@ -423,14 +443,52 @@ export default class BufferController implements ComponentAPI {
|
|
423
443
|
operationQueue.append(operation, type, !!this.pendingTracks[type]);
|
424
444
|
}
|
425
445
|
|
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
|
+
|
426
484
|
protected onBufferAppending(
|
427
485
|
event: Events.BUFFER_APPENDING,
|
428
486
|
eventData: BufferAppendingData,
|
429
487
|
) {
|
430
|
-
const {
|
431
|
-
const { data, type, frag, part, chunkMeta } = eventData;
|
488
|
+
const { operationQueue, tracks } = this;
|
489
|
+
const { data, type, parent, frag, part, chunkMeta } = eventData;
|
432
490
|
const chunkStats = chunkMeta.buffering[type];
|
433
|
-
|
491
|
+
const sn = frag.sn;
|
434
492
|
const bufferAppendingStart = self.performance.now();
|
435
493
|
chunkStats.start = bufferAppendingStart;
|
436
494
|
const fragBuffering = frag.stats.buffering;
|
@@ -457,7 +515,44 @@ export default class BufferController implements ComponentAPI {
|
|
457
515
|
this.lastMpegAudioChunk = chunkMeta;
|
458
516
|
}
|
459
517
|
|
460
|
-
|
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;
|
461
556
|
const operation: BufferOperation = {
|
462
557
|
execute: () => {
|
463
558
|
chunkStats.executeStart = self.performance.now();
|
@@ -467,7 +562,7 @@ export default class BufferController implements ComponentAPI {
|
|
467
562
|
const delta = fragStart - sb.timestampOffset;
|
468
563
|
if (Math.abs(delta) >= 0.1) {
|
469
564
|
this.log(
|
470
|
-
`Updating audio SourceBuffer timestampOffset to ${fragStart} (delta: ${delta}) sn: ${
|
565
|
+
`Updating audio SourceBuffer timestampOffset to ${fragStart} (delta: ${delta}) sn: ${sn})`,
|
471
566
|
);
|
472
567
|
sb.timestampOffset = fragStart;
|
473
568
|
}
|
@@ -536,30 +631,27 @@ export default class BufferController implements ComponentAPI {
|
|
536
631
|
browser is able to evict some data from sourcebuffer. Retrying can help recover.
|
537
632
|
*/
|
538
633
|
this.warn(
|
539
|
-
`Failed ${appendErrorCount}/${hls.config.appendErrorMaxRetry} times to append segment in "${type}" sourceBuffer`,
|
634
|
+
`Failed ${appendErrorCount}/${this.hls.config.appendErrorMaxRetry} times to append segment in "${type}" sourceBuffer`,
|
540
635
|
);
|
541
|
-
if (appendErrorCount >= hls.config.appendErrorMaxRetry) {
|
636
|
+
if (appendErrorCount >= this.hls.config.appendErrorMaxRetry) {
|
542
637
|
event.fatal = true;
|
543
638
|
}
|
544
639
|
}
|
545
|
-
hls.trigger(Events.ERROR, event);
|
640
|
+
this.hls.trigger(Events.ERROR, event);
|
546
641
|
},
|
547
642
|
};
|
548
643
|
operationQueue.append(operation, type, !!this.pendingTracks[type]);
|
549
644
|
}
|
550
645
|
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
execute:
|
558
|
-
this,
|
559
|
-
|
560
|
-
data.startOffset,
|
561
|
-
data.endOffset,
|
562
|
-
),
|
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
|
+
},
|
563
655
|
onStart: () => {
|
564
656
|
// logger.debug(`[buffer-controller]: Started flushing ${data.startOffset} -> ${data.endOffset} for ${type} Source Buffer`);
|
565
657
|
},
|
@@ -570,13 +662,26 @@ export default class BufferController implements ComponentAPI {
|
|
570
662
|
onError: (error: Error) => {
|
571
663
|
this.warn(`Failed to remove from ${type} SourceBuffer`, error);
|
572
664
|
},
|
573
|
-
}
|
665
|
+
};
|
666
|
+
}
|
574
667
|
|
575
|
-
|
576
|
-
|
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
|
+
);
|
577
679
|
} else {
|
578
|
-
this.getSourceBufferTypes().forEach((
|
579
|
-
operationQueue.append(
|
680
|
+
this.getSourceBufferTypes().forEach((sbType: SourceBufferName) => {
|
681
|
+
operationQueue.append(
|
682
|
+
this.getFlushOp(sbType, startOffset, endOffset),
|
683
|
+
sbType,
|
684
|
+
);
|
580
685
|
});
|
581
686
|
}
|
582
687
|
}
|
@@ -629,6 +734,9 @@ export default class BufferController implements ComponentAPI {
|
|
629
734
|
// on BUFFER_EOS mark matching sourcebuffer(s) as ended and trigger checkEos()
|
630
735
|
// an undefined data.type will mark all buffers as EOS.
|
631
736
|
protected onBufferEos(event: Events.BUFFER_EOS, data: BufferEOSData) {
|
737
|
+
if (data.type === 'video') {
|
738
|
+
this.unblockAudio();
|
739
|
+
}
|
632
740
|
const ended = this.getSourceBufferTypes().reduce((acc, type) => {
|
633
741
|
const sb = this.sourceBuffer[type];
|
634
742
|
if (sb && (!data.type || data.type === type)) {
|
@@ -674,11 +782,14 @@ export default class BufferController implements ComponentAPI {
|
|
674
782
|
return;
|
675
783
|
}
|
676
784
|
this.details = details;
|
677
|
-
|
785
|
+
const durationAndRange = this.getDurationAndRange();
|
786
|
+
if (!durationAndRange) {
|
787
|
+
return;
|
788
|
+
}
|
678
789
|
if (this.getSourceBufferTypes().length) {
|
679
|
-
this.blockBuffers(this.
|
790
|
+
this.blockBuffers(() => this.updateMediaSource(durationAndRange));
|
680
791
|
} else {
|
681
|
-
this.
|
792
|
+
this.updateMediaSource(durationAndRange);
|
682
793
|
}
|
683
794
|
}
|
684
795
|
|
@@ -830,14 +941,18 @@ export default class BufferController implements ComponentAPI {
|
|
830
941
|
* 'liveDurationInfinity` is set to `true`
|
831
942
|
* More details: https://github.com/video-dev/hls.js/issues/355
|
832
943
|
*/
|
833
|
-
private
|
944
|
+
private getDurationAndRange(): {
|
945
|
+
duration: number;
|
946
|
+
start?: number;
|
947
|
+
end?: number;
|
948
|
+
} | null {
|
834
949
|
if (
|
835
950
|
!this.details ||
|
836
951
|
!this.media ||
|
837
952
|
!this.mediaSource ||
|
838
953
|
this.mediaSource.readyState !== 'open'
|
839
954
|
) {
|
840
|
-
return;
|
955
|
+
return null;
|
841
956
|
}
|
842
957
|
const { details, hls, media, mediaSource } = this;
|
843
958
|
const levelDuration = details.fragments[0].start + details.totalduration;
|
@@ -849,31 +964,47 @@ export default class BufferController implements ComponentAPI {
|
|
849
964
|
if (details.live && hls.config.liveDurationInfinity) {
|
850
965
|
// Override duration to Infinity
|
851
966
|
mediaSource.duration = Infinity;
|
852
|
-
|
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 };
|
853
974
|
} else if (
|
854
975
|
(levelDuration > msDuration && levelDuration > mediaDuration) ||
|
855
976
|
!Number.isFinite(mediaDuration)
|
856
977
|
) {
|
857
|
-
|
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;
|
978
|
+
return { duration: levelDuration };
|
863
979
|
}
|
980
|
+
return null;
|
864
981
|
}
|
865
982
|
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
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) {
|
873
1004
|
this.log(
|
874
|
-
`Media Source duration is set to ${mediaSource.duration}. Setting seekable range to ${start}-${end}.`,
|
1005
|
+
`Media Source duration is set to ${this.mediaSource.duration}. Setting seekable range to ${start}-${end}.`,
|
875
1006
|
);
|
876
|
-
mediaSource.setLiveSeekableRange(start, end);
|
1007
|
+
this.mediaSource.setLiveSeekableRange(start, end);
|
877
1008
|
}
|
878
1009
|
}
|
879
1010
|
|
@@ -994,7 +1125,10 @@ export default class BufferController implements ComponentAPI {
|
|
994
1125
|
this.log('Media source opened');
|
995
1126
|
if (media) {
|
996
1127
|
media.removeEventListener('emptied', this._onMediaEmptied);
|
997
|
-
this.
|
1128
|
+
const durationAndRange = this.getDurationAndRange();
|
1129
|
+
if (durationAndRange) {
|
1130
|
+
this.updateMediaSource(durationAndRange);
|
1131
|
+
}
|
998
1132
|
this.hls.trigger(Events.MEDIA_ATTACHED, {
|
999
1133
|
media,
|
1000
1134
|
mediaSource: mediaSource as MediaSource,
|
@@ -1019,7 +1153,7 @@ export default class BufferController implements ComponentAPI {
|
|
1019
1153
|
private _onMediaEmptied = () => {
|
1020
1154
|
const { mediaSrc, _objectUrl } = this;
|
1021
1155
|
if (mediaSrc !== _objectUrl) {
|
1022
|
-
|
1156
|
+
this.error(
|
1023
1157
|
`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`,
|
1024
1158
|
);
|
1025
1159
|
}
|
@@ -1115,7 +1249,7 @@ export default class BufferController implements ComponentAPI {
|
|
1115
1249
|
}
|
1116
1250
|
return;
|
1117
1251
|
}
|
1118
|
-
|
1252
|
+
sb.ending = false;
|
1119
1253
|
sb.ended = false;
|
1120
1254
|
sb.appendBuffer(data);
|
1121
1255
|
}
|
@@ -1138,10 +1272,14 @@ export default class BufferController implements ComponentAPI {
|
|
1138
1272
|
const blockingOperations = buffers.map((type) =>
|
1139
1273
|
operationQueue.appendBlocker(type as SourceBufferName),
|
1140
1274
|
);
|
1141
|
-
|
1275
|
+
const audioBlocked = buffers.length > 1 && !!this.blockedAudioAppend;
|
1276
|
+
if (audioBlocked) {
|
1277
|
+
this.unblockAudio();
|
1278
|
+
}
|
1279
|
+
Promise.all(blockingOperations).then((result) => {
|
1142
1280
|
// logger.debug(`[buffer-controller]: Blocking operation resolved; unblocking ${buffers} SourceBuffer`);
|
1143
1281
|
onUnblocked();
|
1144
|
-
buffers.forEach((type) => {
|
1282
|
+
buffers.forEach((type, i) => {
|
1145
1283
|
const sb = this.sourceBuffer[type];
|
1146
1284
|
// Only cycle the queue if the SB is not updating. There's a bug in Chrome which sets the SB updating flag to
|
1147
1285
|
// true when changing the MediaSource duration (https://bugs.chromium.org/p/chromium/issues/detail?id=959359&can=2&q=mediasource%20duration)
|
@@ -30,26 +30,23 @@ export default class BufferOperationQueue {
|
|
30
30
|
}
|
31
31
|
}
|
32
32
|
|
33
|
-
public
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
execute = resolve;
|
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);
|
43
42
|
});
|
44
|
-
|
45
|
-
execute,
|
46
|
-
onStart: () => {},
|
47
|
-
onComplete: () => {},
|
48
|
-
onError: () => {},
|
49
|
-
};
|
43
|
+
}
|
50
44
|
|
51
|
-
|
52
|
-
|
45
|
+
unblockAudio(op: BufferOperation) {
|
46
|
+
const queue = this.queues.audio;
|
47
|
+
if (queue[0] === op) {
|
48
|
+
this.shiftAndExecuteNext('audio');
|
49
|
+
}
|
53
50
|
}
|
54
51
|
|
55
52
|
public executeNext(type: SourceBufferName) {
|
@@ -80,7 +77,7 @@ export default class BufferOperationQueue {
|
|
80
77
|
this.executeNext(type);
|
81
78
|
}
|
82
79
|
|
83
|
-
public current(type: SourceBufferName) {
|
80
|
+
public current(type: SourceBufferName): BufferOperation {
|
84
81
|
return this.queues[type][0];
|
85
82
|
}
|
86
83
|
}
|
@@ -12,7 +12,6 @@ import type {
|
|
12
12
|
LevelsUpdatedData,
|
13
13
|
} from '../types/events';
|
14
14
|
import StreamController from './stream-controller';
|
15
|
-
import { logger } from '../utils/logger';
|
16
15
|
import type { ComponentAPI } from '../types/component-api';
|
17
16
|
import type Hls from '../hls';
|
18
17
|
|
@@ -139,6 +138,7 @@ class CapLevelController implements ComponentAPI {
|
|
139
138
|
|
140
139
|
protected onMediaDetaching() {
|
141
140
|
this.stopCapping();
|
141
|
+
this.media = null;
|
142
142
|
}
|
143
143
|
|
144
144
|
detectPlayerSize() {
|
@@ -152,12 +152,13 @@ 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
|
-
logger.log(
|
155
|
+
hls.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 &&
|
161
162
|
hls.autoLevelCapping > this.autoLevelCapping &&
|
162
163
|
this.streamController
|
163
164
|
) {
|