hls.js 1.5.14-0.canary.10517 → 1.5.14
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 +2903 -4542
- package/dist/hls.js.d.ts +112 -186
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +2284 -3295
- 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 +1804 -2817
- 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 +4652 -6293
- 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 +2 -5
- package/src/controller/abr-controller.ts +25 -39
- package/src/controller/audio-stream-controller.ts +136 -156
- 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 +107 -263
- package/src/controller/buffer-controller.ts +98 -252
- 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 +22 -28
- package/src/controller/fps-controller.ts +3 -8
- package/src/controller/fragment-finders.ts +16 -44
- package/src/controller/fragment-tracker.ts +25 -58
- package/src/controller/gap-controller.ts +16 -43
- package/src/controller/id3-track-controller.ts +35 -45
- package/src/controller/latency-controller.ts +13 -18
- package/src/controller/level-controller.ts +19 -37
- package/src/controller/stream-controller.ts +83 -100
- package/src/controller/subtitle-stream-controller.ts +47 -35
- package/src/controller/subtitle-track-controller.ts +3 -5
- package/src/controller/timeline-controller.ts +22 -20
- 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 +5 -5
- package/src/demux/audio/ac3-demuxer.ts +4 -5
- 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/inject-worker.ts +4 -38
- package/src/demux/mp4demuxer.ts +7 -7
- package/src/demux/sample-aes.ts +0 -2
- package/src/demux/transmuxer-interface.ts +83 -106
- package/src/demux/transmuxer-worker.ts +77 -111
- package/src/demux/transmuxer.ts +22 -46
- package/src/demux/tsdemuxer.ts +62 -122
- package/src/demux/video/avc-video-parser.ts +121 -210
- package/src/demux/video/base-video-parser.ts +2 -135
- package/src/demux/video/exp-golomb.ts +208 -0
- package/src/errors.ts +0 -2
- package/src/events.ts +1 -8
- package/src/exports-named.ts +1 -1
- package/src/hls.ts +48 -97
- package/src/loader/date-range.ts +5 -71
- package/src/loader/fragment-loader.ts +21 -23
- package/src/loader/fragment.ts +4 -8
- package/src/loader/key-loader.ts +1 -3
- package/src/loader/level-details.ts +6 -6
- package/src/loader/level-key.ts +9 -10
- package/src/loader/m3u8-parser.ts +144 -138
- package/src/loader/playlist-loader.ts +7 -5
- package/src/remux/mp4-generator.ts +1 -196
- package/src/remux/mp4-remuxer.ts +84 -55
- package/src/remux/passthrough-remuxer.ts +8 -23
- package/src/task-loop.ts +2 -5
- package/src/types/component-api.ts +1 -3
- package/src/types/demuxer.ts +0 -3
- package/src/types/events.ts +6 -19
- package/src/types/fragment-tracker.ts +2 -2
- package/src/types/general.ts +6 -0
- package/src/types/media-playlist.ts +1 -9
- package/src/types/remuxer.ts +1 -1
- package/src/utils/attr-list.ts +9 -96
- 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/discontinuities.ts +47 -21
- package/src/utils/fetch-loader.ts +1 -1
- package/src/utils/hdr.ts +7 -4
- package/src/utils/imsc1-ttml-parser.ts +1 -1
- package/src/utils/keysystem-util.ts +6 -1
- package/src/utils/level-helper.ts +44 -71
- package/src/utils/logger.ts +23 -58
- package/src/utils/mp4-tools.ts +3 -5
- package/src/utils/rendition-helper.ts +74 -100
- package/src/utils/variable-substitution.ts +19 -0
- package/src/utils/webvtt-parser.ts +12 -2
- 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/hash.ts +0 -10
- package/src/utils/utf8-utils.ts +0 -18
- package/src/version.ts +0 -1
@@ -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() {
|
@@ -147,7 +131,6 @@ export default class BufferController extends Logger implements ComponentAPI {
|
|
147
131
|
hls.on(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
|
148
132
|
hls.on(Events.FRAG_PARSED, this.onFragParsed, this);
|
149
133
|
hls.on(Events.FRAG_CHANGED, this.onFragChanged, this);
|
150
|
-
hls.on(Events.ERROR, this.onError, this);
|
151
134
|
}
|
152
135
|
|
153
136
|
protected unregisterListeners() {
|
@@ -164,7 +147,6 @@ export default class BufferController extends Logger implements ComponentAPI {
|
|
164
147
|
hls.off(Events.LEVEL_UPDATED, this.onLevelUpdated, this);
|
165
148
|
hls.off(Events.FRAG_PARSED, this.onFragParsed, this);
|
166
149
|
hls.off(Events.FRAG_CHANGED, this.onFragChanged, this);
|
167
|
-
hls.off(Events.ERROR, this.onError, this);
|
168
150
|
}
|
169
151
|
|
170
152
|
private _initSourceBuffer() {
|
@@ -175,10 +157,12 @@ export default class BufferController extends Logger implements ComponentAPI {
|
|
175
157
|
video: [],
|
176
158
|
audiovideo: [],
|
177
159
|
};
|
178
|
-
this.
|
160
|
+
this.appendErrors = {
|
161
|
+
audio: 0,
|
162
|
+
video: 0,
|
163
|
+
audiovideo: 0,
|
164
|
+
};
|
179
165
|
this.lastMpegAudioChunk = null;
|
180
|
-
this.blockedAudioAppend = null;
|
181
|
-
this.lastVideoAppendEnd = 0;
|
182
166
|
}
|
183
167
|
|
184
168
|
private onManifestLoading() {
|
@@ -286,38 +270,37 @@ export default class BufferController extends Logger implements ComponentAPI {
|
|
286
270
|
mediaSource.removeEventListener('endstreaming', this._onEndStreaming);
|
287
271
|
}
|
288
272
|
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
media.removeEventListener('emptied', this._onMediaEmptied);
|
297
|
-
if (_objectUrl) {
|
298
|
-
self.URL.revokeObjectURL(_objectUrl);
|
299
|
-
}
|
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
|
+
}
|
300
280
|
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
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
|
+
);
|
307
293
|
}
|
308
|
-
media.load();
|
309
|
-
} else {
|
310
|
-
this.warn(
|
311
|
-
'media|source.src was changed by a third party - skip cleanup',
|
312
|
-
);
|
313
294
|
}
|
295
|
+
|
296
|
+
this.mediaSource = null;
|
314
297
|
this.media = null;
|
298
|
+
this._objectUrl = null;
|
299
|
+
this.bufferCodecEventsExpected = this._bufferCodecEventsTotal;
|
300
|
+
this.pendingTracks = {};
|
301
|
+
this.tracks = {};
|
315
302
|
}
|
316
303
|
|
317
|
-
this.bufferCodecEventsExpected = this._bufferCodecEventsTotal;
|
318
|
-
this.pendingTracks = {};
|
319
|
-
this.tracks = {};
|
320
|
-
|
321
304
|
this.hls.trigger(Events.MEDIA_DETACHED, undefined);
|
322
305
|
}
|
323
306
|
|
@@ -326,7 +309,6 @@ export default class BufferController extends Logger implements ComponentAPI {
|
|
326
309
|
this.resetBuffer(type);
|
327
310
|
});
|
328
311
|
this._initSourceBuffer();
|
329
|
-
this.hls.resumeBuffering();
|
330
312
|
}
|
331
313
|
|
332
314
|
private resetBuffer(type: SourceBufferName) {
|
@@ -352,11 +334,11 @@ export default class BufferController extends Logger implements ComponentAPI {
|
|
352
334
|
) {
|
353
335
|
const sourceBufferCount = this.getSourceBufferTypes().length;
|
354
336
|
const trackNames = Object.keys(data);
|
355
|
-
trackNames.forEach((trackName
|
337
|
+
trackNames.forEach((trackName) => {
|
356
338
|
if (sourceBufferCount) {
|
357
339
|
// check if SourceBuffer codec needs to change
|
358
340
|
const track = this.tracks[trackName];
|
359
|
-
if (track && typeof track.buffer
|
341
|
+
if (track && typeof track.buffer.changeType === 'function') {
|
360
342
|
const { id, codec, levelCodec, container, metadata } =
|
361
343
|
data[trackName];
|
362
344
|
const currentCodecFull = pickMostCompleteCodecName(
|
@@ -420,7 +402,7 @@ export default class BufferController extends Logger implements ComponentAPI {
|
|
420
402
|
}
|
421
403
|
}
|
422
404
|
|
423
|
-
protected appendChangeType(type
|
405
|
+
protected appendChangeType(type, mimeType) {
|
424
406
|
const { operationQueue } = this;
|
425
407
|
const operation: BufferOperation = {
|
426
408
|
execute: () => {
|
@@ -441,52 +423,14 @@ export default class BufferController extends Logger implements ComponentAPI {
|
|
441
423
|
operationQueue.append(operation, type, !!this.pendingTracks[type]);
|
442
424
|
}
|
443
425
|
|
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
|
-
|
482
426
|
protected onBufferAppending(
|
483
427
|
event: Events.BUFFER_APPENDING,
|
484
428
|
eventData: BufferAppendingData,
|
485
429
|
) {
|
486
|
-
const { operationQueue, tracks } = this;
|
487
|
-
const { data, type,
|
430
|
+
const { hls, operationQueue, tracks } = this;
|
431
|
+
const { data, type, frag, part, chunkMeta } = eventData;
|
488
432
|
const chunkStats = chunkMeta.buffering[type];
|
489
|
-
|
433
|
+
|
490
434
|
const bufferAppendingStart = self.performance.now();
|
491
435
|
chunkStats.start = bufferAppendingStart;
|
492
436
|
const fragBuffering = frag.stats.buffering;
|
@@ -513,44 +457,7 @@ export default class BufferController extends Logger implements ComponentAPI {
|
|
513
457
|
this.lastMpegAudioChunk = chunkMeta;
|
514
458
|
}
|
515
459
|
|
516
|
-
|
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;
|
460
|
+
const fragStart = frag.start;
|
554
461
|
const operation: BufferOperation = {
|
555
462
|
execute: () => {
|
556
463
|
chunkStats.executeStart = self.performance.now();
|
@@ -560,7 +467,7 @@ export default class BufferController extends Logger implements ComponentAPI {
|
|
560
467
|
const delta = fragStart - sb.timestampOffset;
|
561
468
|
if (Math.abs(delta) >= 0.1) {
|
562
469
|
this.log(
|
563
|
-
`Updating audio SourceBuffer timestampOffset to ${fragStart} (delta: ${delta}) sn: ${sn})`,
|
470
|
+
`Updating audio SourceBuffer timestampOffset to ${fragStart} (delta: ${delta}) sn: ${frag.sn})`,
|
564
471
|
);
|
565
472
|
sb.timestampOffset = fragStart;
|
566
473
|
}
|
@@ -629,27 +536,30 @@ export default class BufferController extends Logger implements ComponentAPI {
|
|
629
536
|
browser is able to evict some data from sourcebuffer. Retrying can help recover.
|
630
537
|
*/
|
631
538
|
this.warn(
|
632
|
-
`Failed ${appendErrorCount}/${
|
539
|
+
`Failed ${appendErrorCount}/${hls.config.appendErrorMaxRetry} times to append segment in "${type}" sourceBuffer`,
|
633
540
|
);
|
634
|
-
if (appendErrorCount >=
|
541
|
+
if (appendErrorCount >= hls.config.appendErrorMaxRetry) {
|
635
542
|
event.fatal = true;
|
636
543
|
}
|
637
544
|
}
|
638
|
-
|
545
|
+
hls.trigger(Events.ERROR, event);
|
639
546
|
},
|
640
547
|
};
|
641
548
|
operationQueue.append(operation, type, !!this.pendingTracks[type]);
|
642
549
|
}
|
643
550
|
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
execute: (
|
651
|
-
this
|
652
|
-
|
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
|
+
),
|
653
563
|
onStart: () => {
|
654
564
|
// logger.debug(`[buffer-controller]: Started flushing ${data.startOffset} -> ${data.endOffset} for ${type} Source Buffer`);
|
655
565
|
},
|
@@ -660,26 +570,13 @@ export default class BufferController extends Logger implements ComponentAPI {
|
|
660
570
|
onError: (error: Error) => {
|
661
571
|
this.warn(`Failed to remove from ${type} SourceBuffer`, error);
|
662
572
|
},
|
663
|
-
};
|
664
|
-
}
|
573
|
+
});
|
665
574
|
|
666
|
-
|
667
|
-
|
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
|
-
);
|
575
|
+
if (data.type) {
|
576
|
+
operationQueue.append(flushOperation(data.type), data.type);
|
677
577
|
} else {
|
678
|
-
this.getSourceBufferTypes().forEach((
|
679
|
-
operationQueue.append(
|
680
|
-
this.getFlushOp(sbType, startOffset, endOffset),
|
681
|
-
sbType,
|
682
|
-
);
|
578
|
+
this.getSourceBufferTypes().forEach((type: SourceBufferName) => {
|
579
|
+
operationQueue.append(flushOperation(type), type);
|
683
580
|
});
|
684
581
|
}
|
685
582
|
}
|
@@ -732,9 +629,6 @@ export default class BufferController extends Logger implements ComponentAPI {
|
|
732
629
|
// on BUFFER_EOS mark matching sourcebuffer(s) as ended and trigger checkEos()
|
733
630
|
// an undefined data.type will mark all buffers as EOS.
|
734
631
|
protected onBufferEos(event: Events.BUFFER_EOS, data: BufferEOSData) {
|
735
|
-
if (data.type === 'video') {
|
736
|
-
this.unblockAudio();
|
737
|
-
}
|
738
632
|
const ended = this.getSourceBufferTypes().reduce((acc, type) => {
|
739
633
|
const sb = this.sourceBuffer[type];
|
740
634
|
if (sb && (!data.type || data.type === type)) {
|
@@ -780,34 +674,14 @@ export default class BufferController extends Logger implements ComponentAPI {
|
|
780
674
|
return;
|
781
675
|
}
|
782
676
|
this.details = details;
|
783
|
-
|
784
|
-
if (!durationAndRange) {
|
785
|
-
return;
|
786
|
-
}
|
677
|
+
|
787
678
|
if (this.getSourceBufferTypes().length) {
|
788
|
-
this.blockBuffers(
|
679
|
+
this.blockBuffers(this.updateMediaElementDuration.bind(this));
|
789
680
|
} else {
|
790
|
-
this.
|
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
|
-
}
|
681
|
+
this.updateMediaElementDuration();
|
800
682
|
}
|
801
683
|
}
|
802
684
|
|
803
|
-
private resetAppendErrors() {
|
804
|
-
this.appendErrors = {
|
805
|
-
audio: 0,
|
806
|
-
video: 0,
|
807
|
-
audiovideo: 0,
|
808
|
-
};
|
809
|
-
}
|
810
|
-
|
811
685
|
trimBuffers() {
|
812
686
|
const { hls, details, media } = this;
|
813
687
|
if (!media || details === null) {
|
@@ -956,18 +830,14 @@ export default class BufferController extends Logger implements ComponentAPI {
|
|
956
830
|
* 'liveDurationInfinity` is set to `true`
|
957
831
|
* More details: https://github.com/video-dev/hls.js/issues/355
|
958
832
|
*/
|
959
|
-
private
|
960
|
-
duration: number;
|
961
|
-
start?: number;
|
962
|
-
end?: number;
|
963
|
-
} | null {
|
833
|
+
private updateMediaElementDuration() {
|
964
834
|
if (
|
965
835
|
!this.details ||
|
966
836
|
!this.media ||
|
967
837
|
!this.mediaSource ||
|
968
838
|
this.mediaSource.readyState !== 'open'
|
969
839
|
) {
|
970
|
-
return
|
840
|
+
return;
|
971
841
|
}
|
972
842
|
const { details, hls, media, mediaSource } = this;
|
973
843
|
const levelDuration = details.fragments[0].start + details.totalduration;
|
@@ -979,47 +849,31 @@ export default class BufferController extends Logger implements ComponentAPI {
|
|
979
849
|
if (details.live && hls.config.liveDurationInfinity) {
|
980
850
|
// Override duration to Infinity
|
981
851
|
mediaSource.duration = Infinity;
|
982
|
-
|
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 };
|
852
|
+
this.updateSeekableRange(details);
|
989
853
|
} else if (
|
990
854
|
(levelDuration > msDuration && levelDuration > mediaDuration) ||
|
991
855
|
!Number.isFinite(mediaDuration)
|
992
856
|
) {
|
993
|
-
|
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;
|
994
863
|
}
|
995
|
-
return null;
|
996
864
|
}
|
997
865
|
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1002
|
-
|
1003
|
-
|
1004
|
-
|
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) {
|
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);
|
1019
873
|
this.log(
|
1020
|
-
`Media Source duration is set to ${
|
874
|
+
`Media Source duration is set to ${mediaSource.duration}. Setting seekable range to ${start}-${end}.`,
|
1021
875
|
);
|
1022
|
-
|
876
|
+
mediaSource.setLiveSeekableRange(start, end);
|
1023
877
|
}
|
1024
878
|
}
|
1025
879
|
|
@@ -1140,10 +994,7 @@ export default class BufferController extends Logger implements ComponentAPI {
|
|
1140
994
|
this.log('Media source opened');
|
1141
995
|
if (media) {
|
1142
996
|
media.removeEventListener('emptied', this._onMediaEmptied);
|
1143
|
-
|
1144
|
-
if (durationAndRange) {
|
1145
|
-
this.updateMediaSource(durationAndRange);
|
1146
|
-
}
|
997
|
+
this.updateMediaElementDuration();
|
1147
998
|
this.hls.trigger(Events.MEDIA_ATTACHED, {
|
1148
999
|
media,
|
1149
1000
|
mediaSource: mediaSource as MediaSource,
|
@@ -1168,15 +1019,14 @@ export default class BufferController extends Logger implements ComponentAPI {
|
|
1168
1019
|
private _onMediaEmptied = () => {
|
1169
1020
|
const { mediaSrc, _objectUrl } = this;
|
1170
1021
|
if (mediaSrc !== _objectUrl) {
|
1171
|
-
|
1022
|
+
logger.error(
|
1172
1023
|
`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`,
|
1173
1024
|
);
|
1174
1025
|
}
|
1175
1026
|
};
|
1176
1027
|
|
1177
1028
|
private get mediaSrc(): string | undefined {
|
1178
|
-
const media =
|
1179
|
-
(this.media?.firstChild as HTMLSourceElement | null) || this.media;
|
1029
|
+
const media = this.media?.querySelector?.('source') || this.media;
|
1180
1030
|
return media?.src;
|
1181
1031
|
}
|
1182
1032
|
|
@@ -1264,7 +1114,7 @@ export default class BufferController extends Logger implements ComponentAPI {
|
|
1264
1114
|
}
|
1265
1115
|
return;
|
1266
1116
|
}
|
1267
|
-
|
1117
|
+
|
1268
1118
|
sb.ended = false;
|
1269
1119
|
sb.appendBuffer(data);
|
1270
1120
|
}
|
@@ -1287,14 +1137,10 @@ export default class BufferController extends Logger implements ComponentAPI {
|
|
1287
1137
|
const blockingOperations = buffers.map((type) =>
|
1288
1138
|
operationQueue.appendBlocker(type as SourceBufferName),
|
1289
1139
|
);
|
1290
|
-
|
1291
|
-
if (audioBlocked) {
|
1292
|
-
this.unblockAudio();
|
1293
|
-
}
|
1294
|
-
Promise.all(blockingOperations).then((result) => {
|
1140
|
+
Promise.all(blockingOperations).then(() => {
|
1295
1141
|
// logger.debug(`[buffer-controller]: Blocking operation resolved; unblocking ${buffers} SourceBuffer`);
|
1296
1142
|
onUnblocked();
|
1297
|
-
buffers.forEach((type
|
1143
|
+
buffers.forEach((type) => {
|
1298
1144
|
const sb = this.sourceBuffer[type];
|
1299
1145
|
// Only cycle the queue if the SB is not updating. There's a bug in Chrome which sets the SB updating flag to
|
1300
1146
|
// 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
|
) {
|