hls.js 1.5.13 → 1.5.14-0.canary.10417
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 +4211 -2666
- package/dist/hls.js.d.ts +179 -110
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +2841 -1921
- 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 +2569 -1639
- 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 +3572 -2017
- 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 +38 -38
- package/src/config.ts +5 -2
- package/src/controller/abr-controller.ts +39 -25
- package/src/controller/audio-stream-controller.ts +156 -136
- 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 +234 -89
- package/src/controller/buffer-controller.ts +250 -97
- 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 +28 -22
- package/src/controller/fps-controller.ts +8 -3
- package/src/controller/fragment-finders.ts +44 -16
- package/src/controller/fragment-tracker.ts +58 -25
- package/src/controller/gap-controller.ts +43 -16
- package/src/controller/id3-track-controller.ts +45 -35
- package/src/controller/latency-controller.ts +18 -13
- package/src/controller/level-controller.ts +37 -19
- package/src/controller/stream-controller.ts +100 -83
- package/src/controller/subtitle-stream-controller.ts +35 -47
- package/src/controller/subtitle-track-controller.ts +5 -3
- package/src/controller/timeline-controller.ts +20 -22
- 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 +8 -16
- 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 +210 -121
- package/src/demux/video/base-video-parser.ts +135 -2
- 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 +84 -47
- package/src/loader/date-range.ts +71 -5
- package/src/loader/fragment-loader.ts +23 -21
- package/src/loader/fragment.ts +8 -4
- package/src/loader/key-loader.ts +3 -1
- package/src/loader/level-details.ts +6 -6
- package/src/loader/level-key.ts +10 -9
- package/src/loader/m3u8-parser.ts +138 -144
- package/src/loader/playlist-loader.ts +5 -7
- package/src/remux/mp4-generator.ts +196 -1
- package/src/remux/mp4-remuxer.ts +32 -62
- package/src/remux/passthrough-remuxer.ts +1 -1
- package/src/task-loop.ts +5 -2
- package/src/types/component-api.ts +3 -1
- package/src/types/demuxer.ts +3 -0
- package/src/types/events.ts +19 -6
- package/src/types/fragment-tracker.ts +2 -2
- package/src/types/media-playlist.ts +9 -1
- package/src/types/remuxer.ts +1 -1
- package/src/utils/attr-list.ts +96 -9
- 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/hash.ts +10 -0
- package/src/utils/hdr.ts +4 -7
- package/src/utils/imsc1-ttml-parser.ts +1 -1
- package/src/utils/keysystem-util.ts +1 -6
- package/src/utils/level-helper.ts +71 -44
- package/src/utils/logger.ts +58 -23
- package/src/utils/mp4-tools.ts +5 -3
- package/src/utils/rendition-helper.ts +100 -74
- package/src/utils/utf8-utils.ts +18 -0
- package/src/utils/variable-substitution.ts +0 -19
- package/src/utils/webvtt-parser.ts +2 -12
- package/src/demux/id3.ts +0 -411
- package/src/types/general.ts +0 -6
@@ -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() {
|
@@ -131,6 +147,7 @@ export default class BufferController implements ComponentAPI {
|
|
131
147
|
hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
|
132
148
|
hls.on(Events.FRAG_PARSED, this.onFragParsed, this);
|
133
149
|
hls.on(Events.FRAG_CHANGED, this.onFragChanged, this);
|
150
|
+
hls.on(Events.ERROR, this.onError, this);
|
134
151
|
}
|
135
152
|
|
136
153
|
protected unregisterListeners() {
|
@@ -147,6 +164,7 @@ export default class BufferController implements ComponentAPI {
|
|
147
164
|
hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
|
148
165
|
hls.off(Events.FRAG_PARSED, this.onFragParsed, this);
|
149
166
|
hls.off(Events.FRAG_CHANGED, this.onFragChanged, this);
|
167
|
+
hls.off(Events.ERROR, this.onError, this);
|
150
168
|
}
|
151
169
|
|
152
170
|
private _initSourceBuffer() {
|
@@ -157,12 +175,10 @@ export default class BufferController implements ComponentAPI {
|
|
157
175
|
video: [],
|
158
176
|
audiovideo: [],
|
159
177
|
};
|
160
|
-
this.
|
161
|
-
audio: 0,
|
162
|
-
video: 0,
|
163
|
-
audiovideo: 0,
|
164
|
-
};
|
178
|
+
this.resetAppendErrors();
|
165
179
|
this.lastMpegAudioChunk = null;
|
180
|
+
this.blockedAudioAppend = null;
|
181
|
+
this.lastVideoAppendEnd = 0;
|
166
182
|
}
|
167
183
|
|
168
184
|
private onManifestLoading() {
|
@@ -270,37 +286,38 @@ export default class BufferController implements ComponentAPI {
|
|
270
286
|
mediaSource.removeEventListener('endstreaming', this._onEndStreaming);
|
271
287
|
}
|
272
288
|
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
media.removeEventListener('emptied', this._onMediaEmptied);
|
277
|
-
if (_objectUrl) {
|
278
|
-
self.URL.revokeObjectURL(_objectUrl);
|
279
|
-
}
|
289
|
+
this.mediaSource = null;
|
290
|
+
this._objectUrl = null;
|
291
|
+
}
|
280
292
|
|
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
|
-
}
|
293
|
+
// Detach properly the MediaSource from the HTMLMediaElement as
|
294
|
+
// suggested in https://github.com/w3c/media-source/issues/53.
|
295
|
+
if (media) {
|
296
|
+
media.removeEventListener('emptied', this._onMediaEmptied);
|
297
|
+
if (_objectUrl) {
|
298
|
+
self.URL.revokeObjectURL(_objectUrl);
|
294
299
|
}
|
295
300
|
|
296
|
-
|
301
|
+
// clean up video tag src only if it's our own url. some external libraries might
|
302
|
+
// hijack the video tag and change its 'src' without destroying the Hls instance first
|
303
|
+
if (this.mediaSrc === _objectUrl) {
|
304
|
+
media.removeAttribute('src');
|
305
|
+
if (this.appendSource) {
|
306
|
+
removeSourceChildren(media);
|
307
|
+
}
|
308
|
+
media.load();
|
309
|
+
} else {
|
310
|
+
this.warn(
|
311
|
+
'media|source.src was changed by a third party - skip cleanup',
|
312
|
+
);
|
313
|
+
}
|
297
314
|
this.media = null;
|
298
|
-
this._objectUrl = null;
|
299
|
-
this.bufferCodecEventsExpected = this._bufferCodecEventsTotal;
|
300
|
-
this.pendingTracks = {};
|
301
|
-
this.tracks = {};
|
302
315
|
}
|
303
316
|
|
317
|
+
this.bufferCodecEventsExpected = this._bufferCodecEventsTotal;
|
318
|
+
this.pendingTracks = {};
|
319
|
+
this.tracks = {};
|
320
|
+
|
304
321
|
this.hls.trigger(Events.MEDIA_DETACHED, undefined);
|
305
322
|
}
|
306
323
|
|
@@ -309,6 +326,7 @@ export default class BufferController implements ComponentAPI {
|
|
309
326
|
this.resetBuffer(type);
|
310
327
|
});
|
311
328
|
this._initSourceBuffer();
|
329
|
+
this.hls.resumeBuffering();
|
312
330
|
}
|
313
331
|
|
314
332
|
private resetBuffer(type: SourceBufferName) {
|
@@ -334,11 +352,11 @@ export default class BufferController implements ComponentAPI {
|
|
334
352
|
) {
|
335
353
|
const sourceBufferCount = this.getSourceBufferTypes().length;
|
336
354
|
const trackNames = Object.keys(data);
|
337
|
-
trackNames.forEach((trackName) => {
|
355
|
+
trackNames.forEach((trackName: SourceBufferName) => {
|
338
356
|
if (sourceBufferCount) {
|
339
357
|
// check if SourceBuffer codec needs to change
|
340
358
|
const track = this.tracks[trackName];
|
341
|
-
if (track && typeof track.buffer
|
359
|
+
if (track && typeof track.buffer?.changeType === 'function') {
|
342
360
|
const { id, codec, levelCodec, container, metadata } =
|
343
361
|
data[trackName];
|
344
362
|
const currentCodecFull = pickMostCompleteCodecName(
|
@@ -402,7 +420,7 @@ export default class BufferController implements ComponentAPI {
|
|
402
420
|
}
|
403
421
|
}
|
404
422
|
|
405
|
-
protected appendChangeType(type, mimeType) {
|
423
|
+
protected appendChangeType(type: SourceBufferName, mimeType: string) {
|
406
424
|
const { operationQueue } = this;
|
407
425
|
const operation: BufferOperation = {
|
408
426
|
execute: () => {
|
@@ -423,14 +441,52 @@ export default class BufferController implements ComponentAPI {
|
|
423
441
|
operationQueue.append(operation, type, !!this.pendingTracks[type]);
|
424
442
|
}
|
425
443
|
|
444
|
+
private blockAudio(partOrFrag: Fragment | Part) {
|
445
|
+
const pStart = partOrFrag.start;
|
446
|
+
const pTime = pStart + partOrFrag.duration * 0.05;
|
447
|
+
const atGap =
|
448
|
+
this.fragmentTracker.getAppendedFrag(pStart, PlaylistLevelType.MAIN)
|
449
|
+
?.gap === true;
|
450
|
+
if (atGap) {
|
451
|
+
return;
|
452
|
+
}
|
453
|
+
const op: BufferOperation = {
|
454
|
+
execute: () => {
|
455
|
+
if (
|
456
|
+
this.lastVideoAppendEnd > pTime ||
|
457
|
+
(this.sourceBuffer.video &&
|
458
|
+
BufferHelper.isBuffered(this.sourceBuffer.video, pTime)) ||
|
459
|
+
this.fragmentTracker.getAppendedFrag(pTime, PlaylistLevelType.MAIN)
|
460
|
+
?.gap === true
|
461
|
+
) {
|
462
|
+
this.blockedAudioAppend = null;
|
463
|
+
this.operationQueue.shiftAndExecuteNext('audio');
|
464
|
+
}
|
465
|
+
},
|
466
|
+
onStart: () => {},
|
467
|
+
onComplete: () => {},
|
468
|
+
onError: () => {},
|
469
|
+
};
|
470
|
+
this.blockedAudioAppend = { op, frag: partOrFrag };
|
471
|
+
this.operationQueue.append(op, 'audio', true);
|
472
|
+
}
|
473
|
+
|
474
|
+
private unblockAudio() {
|
475
|
+
const blockedAudioAppend = this.blockedAudioAppend;
|
476
|
+
if (blockedAudioAppend) {
|
477
|
+
this.blockedAudioAppend = null;
|
478
|
+
this.operationQueue.unblockAudio(blockedAudioAppend.op);
|
479
|
+
}
|
480
|
+
}
|
481
|
+
|
426
482
|
protected onBufferAppending(
|
427
483
|
event: Events.BUFFER_APPENDING,
|
428
484
|
eventData: BufferAppendingData,
|
429
485
|
) {
|
430
|
-
const {
|
431
|
-
const { data, type, frag, part, chunkMeta } = eventData;
|
486
|
+
const { operationQueue, tracks } = this;
|
487
|
+
const { data, type, parent, frag, part, chunkMeta } = eventData;
|
432
488
|
const chunkStats = chunkMeta.buffering[type];
|
433
|
-
|
489
|
+
const sn = frag.sn;
|
434
490
|
const bufferAppendingStart = self.performance.now();
|
435
491
|
chunkStats.start = bufferAppendingStart;
|
436
492
|
const fragBuffering = frag.stats.buffering;
|
@@ -457,7 +513,44 @@ export default class BufferController implements ComponentAPI {
|
|
457
513
|
this.lastMpegAudioChunk = chunkMeta;
|
458
514
|
}
|
459
515
|
|
460
|
-
|
516
|
+
// Block audio append until overlapping video append
|
517
|
+
const videoSb = this.sourceBuffer.video;
|
518
|
+
if (videoSb && sn !== 'initSegment') {
|
519
|
+
const partOrFrag = part || frag;
|
520
|
+
const blockedAudioAppend = this.blockedAudioAppend;
|
521
|
+
if (type === 'audio' && parent !== 'main' && !this.blockedAudioAppend) {
|
522
|
+
const pStart = partOrFrag.start;
|
523
|
+
const pTime = pStart + partOrFrag.duration * 0.05;
|
524
|
+
const vbuffered = videoSb.buffered;
|
525
|
+
const vappending = this.operationQueue.current('video');
|
526
|
+
if (!vbuffered.length && !vappending) {
|
527
|
+
// wait for video before appending audio
|
528
|
+
this.blockAudio(partOrFrag);
|
529
|
+
} else if (
|
530
|
+
!vappending &&
|
531
|
+
!BufferHelper.isBuffered(videoSb, pTime) &&
|
532
|
+
this.lastVideoAppendEnd < pTime
|
533
|
+
) {
|
534
|
+
// audio is ahead of video
|
535
|
+
this.blockAudio(partOrFrag);
|
536
|
+
}
|
537
|
+
} else if (type === 'video') {
|
538
|
+
const videoAppendEnd = partOrFrag.end;
|
539
|
+
if (blockedAudioAppend) {
|
540
|
+
const audioStart = blockedAudioAppend.frag.start;
|
541
|
+
if (
|
542
|
+
videoAppendEnd > audioStart ||
|
543
|
+
videoAppendEnd < this.lastVideoAppendEnd ||
|
544
|
+
BufferHelper.isBuffered(videoSb, audioStart)
|
545
|
+
) {
|
546
|
+
this.unblockAudio();
|
547
|
+
}
|
548
|
+
}
|
549
|
+
this.lastVideoAppendEnd = videoAppendEnd;
|
550
|
+
}
|
551
|
+
}
|
552
|
+
|
553
|
+
const fragStart = (part || frag).start;
|
461
554
|
const operation: BufferOperation = {
|
462
555
|
execute: () => {
|
463
556
|
chunkStats.executeStart = self.performance.now();
|
@@ -467,7 +560,7 @@ export default class BufferController implements ComponentAPI {
|
|
467
560
|
const delta = fragStart - sb.timestampOffset;
|
468
561
|
if (Math.abs(delta) >= 0.1) {
|
469
562
|
this.log(
|
470
|
-
`Updating audio SourceBuffer timestampOffset to ${fragStart} (delta: ${delta}) sn: ${
|
563
|
+
`Updating audio SourceBuffer timestampOffset to ${fragStart} (delta: ${delta}) sn: ${sn})`,
|
471
564
|
);
|
472
565
|
sb.timestampOffset = fragStart;
|
473
566
|
}
|
@@ -536,30 +629,27 @@ export default class BufferController implements ComponentAPI {
|
|
536
629
|
browser is able to evict some data from sourcebuffer. Retrying can help recover.
|
537
630
|
*/
|
538
631
|
this.warn(
|
539
|
-
`Failed ${appendErrorCount}/${hls.config.appendErrorMaxRetry} times to append segment in "${type}" sourceBuffer`,
|
632
|
+
`Failed ${appendErrorCount}/${this.hls.config.appendErrorMaxRetry} times to append segment in "${type}" sourceBuffer`,
|
540
633
|
);
|
541
|
-
if (appendErrorCount >= hls.config.appendErrorMaxRetry) {
|
634
|
+
if (appendErrorCount >= this.hls.config.appendErrorMaxRetry) {
|
542
635
|
event.fatal = true;
|
543
636
|
}
|
544
637
|
}
|
545
|
-
hls.trigger(Events.ERROR, event);
|
638
|
+
this.hls.trigger(Events.ERROR, event);
|
546
639
|
},
|
547
640
|
};
|
548
641
|
operationQueue.append(operation, type, !!this.pendingTracks[type]);
|
549
642
|
}
|
550
643
|
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
execute:
|
558
|
-
this,
|
559
|
-
|
560
|
-
data.startOffset,
|
561
|
-
data.endOffset,
|
562
|
-
),
|
644
|
+
private getFlushOp(
|
645
|
+
type: SourceBufferName,
|
646
|
+
start: number,
|
647
|
+
end: number,
|
648
|
+
): BufferOperation {
|
649
|
+
return {
|
650
|
+
execute: () => {
|
651
|
+
this.removeExecutor(type, start, end);
|
652
|
+
},
|
563
653
|
onStart: () => {
|
564
654
|
// logger.debug(`[buffer-controller]: Started flushing ${data.startOffset} -> ${data.endOffset} for ${type} Source Buffer`);
|
565
655
|
},
|
@@ -570,13 +660,26 @@ export default class BufferController implements ComponentAPI {
|
|
570
660
|
onError: (error: Error) => {
|
571
661
|
this.warn(`Failed to remove from ${type} SourceBuffer`, error);
|
572
662
|
},
|
573
|
-
}
|
663
|
+
};
|
664
|
+
}
|
574
665
|
|
575
|
-
|
576
|
-
|
666
|
+
protected onBufferFlushing(
|
667
|
+
event: Events.BUFFER_FLUSHING,
|
668
|
+
data: BufferFlushingData,
|
669
|
+
) {
|
670
|
+
const { operationQueue } = this;
|
671
|
+
const { type, startOffset, endOffset } = data;
|
672
|
+
if (type) {
|
673
|
+
operationQueue.append(
|
674
|
+
this.getFlushOp(type, startOffset, endOffset),
|
675
|
+
type,
|
676
|
+
);
|
577
677
|
} else {
|
578
|
-
this.getSourceBufferTypes().forEach((
|
579
|
-
operationQueue.append(
|
678
|
+
this.getSourceBufferTypes().forEach((sbType: SourceBufferName) => {
|
679
|
+
operationQueue.append(
|
680
|
+
this.getFlushOp(sbType, startOffset, endOffset),
|
681
|
+
sbType,
|
682
|
+
);
|
580
683
|
});
|
581
684
|
}
|
582
685
|
}
|
@@ -629,6 +732,9 @@ export default class BufferController implements ComponentAPI {
|
|
629
732
|
// on BUFFER_EOS mark matching sourcebuffer(s) as ended and trigger checkEos()
|
630
733
|
// an undefined data.type will mark all buffers as EOS.
|
631
734
|
protected onBufferEos(event: Events.BUFFER_EOS, data: BufferEOSData) {
|
735
|
+
if (data.type === 'video') {
|
736
|
+
this.unblockAudio();
|
737
|
+
}
|
632
738
|
const ended = this.getSourceBufferTypes().reduce((acc, type) => {
|
633
739
|
const sb = this.sourceBuffer[type];
|
634
740
|
if (sb && (!data.type || data.type === type)) {
|
@@ -674,14 +780,34 @@ export default class BufferController implements ComponentAPI {
|
|
674
780
|
return;
|
675
781
|
}
|
676
782
|
this.details = details;
|
677
|
-
|
783
|
+
const durationAndRange = this.getDurationAndRange();
|
784
|
+
if (!durationAndRange) {
|
785
|
+
return;
|
786
|
+
}
|
678
787
|
if (this.getSourceBufferTypes().length) {
|
679
|
-
this.blockBuffers(this.
|
788
|
+
this.blockBuffers(() => this.updateMediaSource(durationAndRange));
|
680
789
|
} else {
|
681
|
-
this.
|
790
|
+
this.updateMediaSource(durationAndRange);
|
791
|
+
}
|
792
|
+
}
|
793
|
+
|
794
|
+
private onError(event: Events.ERROR, data: ErrorData) {
|
795
|
+
if (data.details === ErrorDetails.BUFFER_APPEND_ERROR && data.frag) {
|
796
|
+
const nextAutoLevel = data.errorAction?.nextAutoLevel;
|
797
|
+
if (Number.isFinite(nextAutoLevel) && nextAutoLevel !== data.frag.level) {
|
798
|
+
this.resetAppendErrors();
|
799
|
+
}
|
682
800
|
}
|
683
801
|
}
|
684
802
|
|
803
|
+
private resetAppendErrors() {
|
804
|
+
this.appendErrors = {
|
805
|
+
audio: 0,
|
806
|
+
video: 0,
|
807
|
+
audiovideo: 0,
|
808
|
+
};
|
809
|
+
}
|
810
|
+
|
685
811
|
trimBuffers() {
|
686
812
|
const { hls, details, media } = this;
|
687
813
|
if (!media || details === null) {
|
@@ -830,14 +956,18 @@ export default class BufferController implements ComponentAPI {
|
|
830
956
|
* 'liveDurationInfinity` is set to `true`
|
831
957
|
* More details: https://github.com/video-dev/hls.js/issues/355
|
832
958
|
*/
|
833
|
-
private
|
959
|
+
private getDurationAndRange(): {
|
960
|
+
duration: number;
|
961
|
+
start?: number;
|
962
|
+
end?: number;
|
963
|
+
} | null {
|
834
964
|
if (
|
835
965
|
!this.details ||
|
836
966
|
!this.media ||
|
837
967
|
!this.mediaSource ||
|
838
968
|
this.mediaSource.readyState !== 'open'
|
839
969
|
) {
|
840
|
-
return;
|
970
|
+
return null;
|
841
971
|
}
|
842
972
|
const { details, hls, media, mediaSource } = this;
|
843
973
|
const levelDuration = details.fragments[0].start + details.totalduration;
|
@@ -849,31 +979,47 @@ export default class BufferController implements ComponentAPI {
|
|
849
979
|
if (details.live && hls.config.liveDurationInfinity) {
|
850
980
|
// Override duration to Infinity
|
851
981
|
mediaSource.duration = Infinity;
|
852
|
-
|
982
|
+
const len = details.fragments.length;
|
983
|
+
if (len && details.live && !!mediaSource.setLiveSeekableRange) {
|
984
|
+
const start = Math.max(0, details.fragments[0].start);
|
985
|
+
const end = Math.max(start, start + details.totalduration);
|
986
|
+
return { duration: Infinity, start, end };
|
987
|
+
}
|
988
|
+
return { duration: Infinity };
|
853
989
|
} else if (
|
854
990
|
(levelDuration > msDuration && levelDuration > mediaDuration) ||
|
855
991
|
!Number.isFinite(mediaDuration)
|
856
992
|
) {
|
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;
|
993
|
+
return { duration: levelDuration };
|
863
994
|
}
|
995
|
+
return null;
|
864
996
|
}
|
865
997
|
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
998
|
+
private updateMediaSource({
|
999
|
+
duration,
|
1000
|
+
start,
|
1001
|
+
end,
|
1002
|
+
}: {
|
1003
|
+
duration: number;
|
1004
|
+
start?: number;
|
1005
|
+
end?: number;
|
1006
|
+
}) {
|
1007
|
+
if (
|
1008
|
+
!this.media ||
|
1009
|
+
!this.mediaSource ||
|
1010
|
+
this.mediaSource.readyState !== 'open'
|
1011
|
+
) {
|
1012
|
+
return;
|
1013
|
+
}
|
1014
|
+
if (Number.isFinite(duration)) {
|
1015
|
+
this.log(`Updating Media Source duration to ${duration.toFixed(3)}`);
|
1016
|
+
}
|
1017
|
+
this.mediaSource.duration = duration;
|
1018
|
+
if (start !== undefined && end !== undefined) {
|
873
1019
|
this.log(
|
874
|
-
`Media Source duration is set to ${mediaSource.duration}. Setting seekable range to ${start}-${end}.`,
|
1020
|
+
`Media Source duration is set to ${this.mediaSource.duration}. Setting seekable range to ${start}-${end}.`,
|
875
1021
|
);
|
876
|
-
mediaSource.setLiveSeekableRange(start, end);
|
1022
|
+
this.mediaSource.setLiveSeekableRange(start, end);
|
877
1023
|
}
|
878
1024
|
}
|
879
1025
|
|
@@ -994,7 +1140,10 @@ export default class BufferController implements ComponentAPI {
|
|
994
1140
|
this.log('Media source opened');
|
995
1141
|
if (media) {
|
996
1142
|
media.removeEventListener('emptied', this._onMediaEmptied);
|
997
|
-
this.
|
1143
|
+
const durationAndRange = this.getDurationAndRange();
|
1144
|
+
if (durationAndRange) {
|
1145
|
+
this.updateMediaSource(durationAndRange);
|
1146
|
+
}
|
998
1147
|
this.hls.trigger(Events.MEDIA_ATTACHED, {
|
999
1148
|
media,
|
1000
1149
|
mediaSource: mediaSource as MediaSource,
|
@@ -1019,7 +1168,7 @@ export default class BufferController implements ComponentAPI {
|
|
1019
1168
|
private _onMediaEmptied = () => {
|
1020
1169
|
const { mediaSrc, _objectUrl } = this;
|
1021
1170
|
if (mediaSrc !== _objectUrl) {
|
1022
|
-
|
1171
|
+
this.error(
|
1023
1172
|
`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`,
|
1024
1173
|
);
|
1025
1174
|
}
|
@@ -1115,7 +1264,7 @@ export default class BufferController implements ComponentAPI {
|
|
1115
1264
|
}
|
1116
1265
|
return;
|
1117
1266
|
}
|
1118
|
-
|
1267
|
+
sb.ending = false;
|
1119
1268
|
sb.ended = false;
|
1120
1269
|
sb.appendBuffer(data);
|
1121
1270
|
}
|
@@ -1138,10 +1287,14 @@ export default class BufferController implements ComponentAPI {
|
|
1138
1287
|
const blockingOperations = buffers.map((type) =>
|
1139
1288
|
operationQueue.appendBlocker(type as SourceBufferName),
|
1140
1289
|
);
|
1141
|
-
|
1290
|
+
const audioBlocked = buffers.length > 1 && !!this.blockedAudioAppend;
|
1291
|
+
if (audioBlocked) {
|
1292
|
+
this.unblockAudio();
|
1293
|
+
}
|
1294
|
+
Promise.all(blockingOperations).then((result) => {
|
1142
1295
|
// logger.debug(`[buffer-controller]: Blocking operation resolved; unblocking ${buffers} SourceBuffer`);
|
1143
1296
|
onUnblocked();
|
1144
|
-
buffers.forEach((type) => {
|
1297
|
+
buffers.forEach((type, i) => {
|
1145
1298
|
const sb = this.sourceBuffer[type];
|
1146
1299
|
// Only cycle the queue if the SB is not updating. There's a bug in Chrome which sets the SB updating flag to
|
1147
1300
|
// 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
|
) {
|