hls.js 1.5.7-0.canary.10040 → 1.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -2
- package/dist/hls-demo.js +0 -10
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +1283 -2293
- package/dist/hls.js.d.ts +84 -97
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +1030 -1435
- package/dist/hls.light.js.map +1 -1
- package/dist/hls.light.min.js +1 -1
- package/dist/hls.light.min.js.map +1 -1
- package/dist/hls.light.mjs +809 -1209
- package/dist/hls.light.mjs.map +1 -1
- package/dist/hls.min.js +1 -1
- package/dist/hls.min.js.map +1 -1
- package/dist/hls.mjs +1039 -2030
- package/dist/hls.mjs.map +1 -1
- package/dist/hls.worker.js +1 -1
- package/dist/hls.worker.js.map +1 -1
- package/package.json +22 -22
- package/src/config.ts +2 -3
- package/src/controller/abr-controller.ts +20 -24
- package/src/controller/audio-stream-controller.ts +74 -68
- package/src/controller/audio-track-controller.ts +1 -1
- package/src/controller/base-playlist-controller.ts +8 -20
- package/src/controller/base-stream-controller.ts +36 -157
- package/src/controller/buffer-controller.ts +99 -226
- package/src/controller/buffer-operation-queue.ts +19 -16
- package/src/controller/cap-level-controller.ts +2 -2
- package/src/controller/cmcd-controller.ts +6 -27
- package/src/controller/content-steering-controller.ts +6 -8
- package/src/controller/eme-controller.ts +22 -9
- package/src/controller/error-controller.ts +8 -6
- package/src/controller/fps-controller.ts +3 -2
- package/src/controller/fragment-tracker.ts +11 -15
- package/src/controller/gap-controller.ts +16 -43
- package/src/controller/latency-controller.ts +11 -9
- package/src/controller/level-controller.ts +18 -12
- package/src/controller/stream-controller.ts +31 -36
- package/src/controller/subtitle-stream-controller.ts +40 -28
- package/src/controller/subtitle-track-controller.ts +3 -5
- package/src/controller/timeline-controller.ts +30 -23
- package/src/crypt/aes-crypto.ts +2 -21
- package/src/crypt/decrypter.ts +18 -32
- package/src/crypt/fast-aes-key.ts +5 -24
- package/src/demux/audio/adts.ts +4 -9
- package/src/demux/sample-aes.ts +0 -2
- package/src/demux/transmuxer-interface.ts +12 -4
- package/src/demux/transmuxer-worker.ts +4 -4
- package/src/demux/transmuxer.ts +3 -16
- package/src/demux/tsdemuxer.ts +37 -71
- package/src/demux/video/avc-video-parser.ts +119 -208
- package/src/demux/video/base-video-parser.ts +2 -134
- package/src/demux/video/exp-golomb.ts +208 -0
- package/src/events.ts +0 -7
- package/src/hls.ts +37 -49
- package/src/loader/fragment-loader.ts +2 -9
- package/src/loader/key-loader.ts +0 -2
- package/src/loader/level-key.ts +9 -10
- package/src/loader/playlist-loader.ts +5 -4
- package/src/remux/mp4-generator.ts +1 -196
- package/src/remux/mp4-remuxer.ts +7 -23
- package/src/task-loop.ts +2 -5
- package/src/types/component-api.ts +0 -2
- package/src/types/demuxer.ts +0 -3
- package/src/types/events.ts +0 -4
- package/src/utils/buffer-helper.ts +31 -12
- package/src/utils/codecs.ts +5 -34
- package/src/utils/logger.ts +24 -54
- package/src/utils/mp4-tools.ts +2 -4
- package/src/crypt/decrypter-aes-mode.ts +0 -4
- package/src/demux/video/hevc-video-parser.ts +0 -746
- package/src/utils/encryption-methods-util.ts +0 -21
package/dist/hls.light.mjs
CHANGED
@@ -256,7 +256,6 @@ let Events = /*#__PURE__*/function (Events) {
|
|
256
256
|
Events["MEDIA_ATTACHED"] = "hlsMediaAttached";
|
257
257
|
Events["MEDIA_DETACHING"] = "hlsMediaDetaching";
|
258
258
|
Events["MEDIA_DETACHED"] = "hlsMediaDetached";
|
259
|
-
Events["MEDIA_ENDED"] = "hlsMediaEnded";
|
260
259
|
Events["BUFFER_RESET"] = "hlsBufferReset";
|
261
260
|
Events["BUFFER_CODECS"] = "hlsBufferCodecs";
|
262
261
|
Events["BUFFER_CREATED"] = "hlsBufferCreated";
|
@@ -370,6 +369,58 @@ let ErrorDetails = /*#__PURE__*/function (ErrorDetails) {
|
|
370
369
|
return ErrorDetails;
|
371
370
|
}({});
|
372
371
|
|
372
|
+
const noop = function noop() {};
|
373
|
+
const fakeLogger = {
|
374
|
+
trace: noop,
|
375
|
+
debug: noop,
|
376
|
+
log: noop,
|
377
|
+
warn: noop,
|
378
|
+
info: noop,
|
379
|
+
error: noop
|
380
|
+
};
|
381
|
+
let exportedLogger = fakeLogger;
|
382
|
+
|
383
|
+
// let lastCallTime;
|
384
|
+
// function formatMsgWithTimeInfo(type, msg) {
|
385
|
+
// const now = Date.now();
|
386
|
+
// const diff = lastCallTime ? '+' + (now - lastCallTime) : '0';
|
387
|
+
// lastCallTime = now;
|
388
|
+
// msg = (new Date(now)).toISOString() + ' | [' + type + '] > ' + msg + ' ( ' + diff + ' ms )';
|
389
|
+
// return msg;
|
390
|
+
// }
|
391
|
+
|
392
|
+
function consolePrintFn(type) {
|
393
|
+
const func = self.console[type];
|
394
|
+
if (func) {
|
395
|
+
return func.bind(self.console, `[${type}] >`);
|
396
|
+
}
|
397
|
+
return noop;
|
398
|
+
}
|
399
|
+
function exportLoggerFunctions(debugConfig, ...functions) {
|
400
|
+
functions.forEach(function (type) {
|
401
|
+
exportedLogger[type] = debugConfig[type] ? debugConfig[type].bind(debugConfig) : consolePrintFn(type);
|
402
|
+
});
|
403
|
+
}
|
404
|
+
function enableLogs(debugConfig, id) {
|
405
|
+
// check that console is available
|
406
|
+
if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
|
407
|
+
exportLoggerFunctions(debugConfig,
|
408
|
+
// Remove out from list here to hard-disable a log-level
|
409
|
+
// 'trace',
|
410
|
+
'debug', 'log', 'info', 'warn', 'error');
|
411
|
+
// Some browsers don't allow to use bind on console object anyway
|
412
|
+
// fallback to default if needed
|
413
|
+
try {
|
414
|
+
exportedLogger.log(`Debug logs enabled for "${id}" in hls.js version ${"1.5.7"}`);
|
415
|
+
} catch (e) {
|
416
|
+
exportedLogger = fakeLogger;
|
417
|
+
}
|
418
|
+
} else {
|
419
|
+
exportedLogger = fakeLogger;
|
420
|
+
}
|
421
|
+
}
|
422
|
+
const logger = exportedLogger;
|
423
|
+
|
373
424
|
const DECIMAL_RESOLUTION_REGEX = /^(\d+)x(\d+)$/;
|
374
425
|
const ATTR_LIST_REGEX = /(.+?)=(".*?"|.*?)(?:,|$)/g;
|
375
426
|
|
@@ -451,79 +502,6 @@ class AttrList {
|
|
451
502
|
}
|
452
503
|
}
|
453
504
|
|
454
|
-
class Logger {
|
455
|
-
constructor(label, logger) {
|
456
|
-
this.trace = void 0;
|
457
|
-
this.debug = void 0;
|
458
|
-
this.log = void 0;
|
459
|
-
this.warn = void 0;
|
460
|
-
this.info = void 0;
|
461
|
-
this.error = void 0;
|
462
|
-
const lb = `[${label}]:`;
|
463
|
-
this.trace = noop;
|
464
|
-
this.debug = logger.debug.bind(null, lb);
|
465
|
-
this.log = logger.log.bind(null, lb);
|
466
|
-
this.warn = logger.warn.bind(null, lb);
|
467
|
-
this.info = logger.info.bind(null, lb);
|
468
|
-
this.error = logger.error.bind(null, lb);
|
469
|
-
}
|
470
|
-
}
|
471
|
-
const noop = function noop() {};
|
472
|
-
const fakeLogger = {
|
473
|
-
trace: noop,
|
474
|
-
debug: noop,
|
475
|
-
log: noop,
|
476
|
-
warn: noop,
|
477
|
-
info: noop,
|
478
|
-
error: noop
|
479
|
-
};
|
480
|
-
function createLogger() {
|
481
|
-
return _extends({}, fakeLogger);
|
482
|
-
}
|
483
|
-
|
484
|
-
// let lastCallTime;
|
485
|
-
// function formatMsgWithTimeInfo(type, msg) {
|
486
|
-
// const now = Date.now();
|
487
|
-
// const diff = lastCallTime ? '+' + (now - lastCallTime) : '0';
|
488
|
-
// lastCallTime = now;
|
489
|
-
// msg = (new Date(now)).toISOString() + ' | [' + type + '] > ' + msg + ' ( ' + diff + ' ms )';
|
490
|
-
// return msg;
|
491
|
-
// }
|
492
|
-
|
493
|
-
function consolePrintFn(type, id) {
|
494
|
-
const func = self.console[type];
|
495
|
-
return func ? func.bind(self.console, `${id ? '[' + id + '] ' : ''}[${type}] >`) : noop;
|
496
|
-
}
|
497
|
-
function getLoggerFn(key, debugConfig, id) {
|
498
|
-
return debugConfig[key] ? debugConfig[key].bind(debugConfig) : consolePrintFn(key, id);
|
499
|
-
}
|
500
|
-
const exportedLogger = createLogger();
|
501
|
-
function enableLogs(debugConfig, context, id) {
|
502
|
-
// check that console is available
|
503
|
-
const newLogger = createLogger();
|
504
|
-
if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
|
505
|
-
const keys = [
|
506
|
-
// Remove out from list here to hard-disable a log-level
|
507
|
-
// 'trace',
|
508
|
-
'debug', 'log', 'info', 'warn', 'error'];
|
509
|
-
keys.forEach(key => {
|
510
|
-
newLogger[key] = getLoggerFn(key, debugConfig, id);
|
511
|
-
});
|
512
|
-
// Some browsers don't allow to use bind on console object anyway
|
513
|
-
// fallback to default if needed
|
514
|
-
try {
|
515
|
-
newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.5.7-0.canary.10040"}`);
|
516
|
-
} catch (e) {
|
517
|
-
/* log fn threw an exception. All logger methods are no-ops. */
|
518
|
-
return createLogger();
|
519
|
-
}
|
520
|
-
}
|
521
|
-
// global exported logger uses the log methods from last call to `enableLogs`
|
522
|
-
_extends(exportedLogger, newLogger);
|
523
|
-
return newLogger;
|
524
|
-
}
|
525
|
-
const logger = exportedLogger;
|
526
|
-
|
527
505
|
// Avoid exporting const enum so that these values can be inlined
|
528
506
|
|
529
507
|
function isDateRangeCueAttribute(attrName) {
|
@@ -1013,30 +991,10 @@ class LevelDetails {
|
|
1013
991
|
}
|
1014
992
|
}
|
1015
993
|
|
1016
|
-
var DecrypterAesMode = {
|
1017
|
-
cbc: 0,
|
1018
|
-
ctr: 1
|
1019
|
-
};
|
1020
|
-
|
1021
|
-
function isFullSegmentEncryption(method) {
|
1022
|
-
return method === 'AES-128' || method === 'AES-256' || method === 'AES-256-CTR';
|
1023
|
-
}
|
1024
|
-
function getAesModeFromFullSegmentMethod(method) {
|
1025
|
-
switch (method) {
|
1026
|
-
case 'AES-128':
|
1027
|
-
case 'AES-256':
|
1028
|
-
return DecrypterAesMode.cbc;
|
1029
|
-
case 'AES-256-CTR':
|
1030
|
-
return DecrypterAesMode.ctr;
|
1031
|
-
default:
|
1032
|
-
throw new Error(`invalid full segment method ${method}`);
|
1033
|
-
}
|
1034
|
-
}
|
1035
|
-
|
1036
994
|
// This file is inserted as a shim for modules which we do not want to include into the distro.
|
1037
995
|
// This replacement is done in the "alias" plugin of the rollup config.
|
1038
996
|
var empty = undefined;
|
1039
|
-
var
|
997
|
+
var Cues = /*@__PURE__*/getDefaultExportFromCjs(empty);
|
1040
998
|
|
1041
999
|
function sliceUint8(array, start, end) {
|
1042
1000
|
// @ts-expect-error This polyfills IE11 usage of Uint8Array slice.
|
@@ -1668,7 +1626,7 @@ function parseStsd(stsd) {
|
|
1668
1626
|
{
|
1669
1627
|
const codecBox = findBox(sampleEntries, [fourCC])[0];
|
1670
1628
|
const esdsBox = findBox(codecBox.subarray(28), ['esds'])[0];
|
1671
|
-
if (esdsBox && esdsBox.length >
|
1629
|
+
if (esdsBox && esdsBox.length > 12) {
|
1672
1630
|
let i = 4;
|
1673
1631
|
// ES Descriptor tag
|
1674
1632
|
if (esdsBox[i++] !== 0x03) {
|
@@ -1783,9 +1741,7 @@ function parseStsd(stsd) {
|
|
1783
1741
|
}
|
1784
1742
|
function skipBERInteger(bytes, i) {
|
1785
1743
|
const limit = i + 5;
|
1786
|
-
while (bytes[i++] & 0x80 && i < limit) {
|
1787
|
-
/* do nothing */
|
1788
|
-
}
|
1744
|
+
while (bytes[i++] & 0x80 && i < limit) {}
|
1789
1745
|
return i;
|
1790
1746
|
}
|
1791
1747
|
function toHex(x) {
|
@@ -2477,12 +2433,12 @@ class LevelKey {
|
|
2477
2433
|
this.keyFormatVersions = formatversions;
|
2478
2434
|
this.iv = iv;
|
2479
2435
|
this.encrypted = method ? method !== 'NONE' : false;
|
2480
|
-
this.isCommonEncryption = this.encrypted &&
|
2436
|
+
this.isCommonEncryption = this.encrypted && method !== 'AES-128';
|
2481
2437
|
}
|
2482
2438
|
isSupported() {
|
2483
2439
|
// If it's Segment encryption or No encryption, just select that key system
|
2484
2440
|
if (this.method) {
|
2485
|
-
if (
|
2441
|
+
if (this.method === 'AES-128' || this.method === 'NONE') {
|
2486
2442
|
return true;
|
2487
2443
|
}
|
2488
2444
|
if (this.keyFormat === 'identity') {
|
@@ -2496,13 +2452,14 @@ class LevelKey {
|
|
2496
2452
|
if (!this.encrypted || !this.uri) {
|
2497
2453
|
return null;
|
2498
2454
|
}
|
2499
|
-
if (
|
2455
|
+
if (this.method === 'AES-128' && this.uri && !this.iv) {
|
2500
2456
|
if (typeof sn !== 'number') {
|
2501
2457
|
// We are fetching decryption data for a initialization segment
|
2502
|
-
// If the segment was encrypted with AES-128
|
2458
|
+
// If the segment was encrypted with AES-128
|
2503
2459
|
// It must have an IV defined. We cannot substitute the Segment Number in.
|
2504
|
-
|
2505
|
-
|
2460
|
+
if (this.method === 'AES-128' && !this.iv) {
|
2461
|
+
logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
|
2462
|
+
}
|
2506
2463
|
// Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
|
2507
2464
|
sn = 0;
|
2508
2465
|
}
|
@@ -2649,28 +2606,23 @@ function getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource =
|
|
2649
2606
|
if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) {
|
2650
2607
|
return CODEC_COMPATIBLE_NAMES[lowerCaseCodec];
|
2651
2608
|
}
|
2609
|
+
|
2610
|
+
// Idealy fLaC and Opus would be first (spec-compliant) but
|
2611
|
+
// some browsers will report that fLaC is supported then fail.
|
2612
|
+
// see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
|
2652
2613
|
const codecsToCheck = {
|
2653
|
-
// Idealy fLaC and Opus would be first (spec-compliant) but
|
2654
|
-
// some browsers will report that fLaC is supported then fail.
|
2655
|
-
// see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
|
2656
2614
|
flac: ['flac', 'fLaC', 'FLAC'],
|
2657
|
-
opus: ['opus', 'Opus']
|
2658
|
-
// Replace audio codec info if browser does not support mp4a.40.34,
|
2659
|
-
// and demuxer can fallback to 'audio/mpeg' or 'audio/mp4;codecs="mp3"'
|
2660
|
-
'mp4a.40.34': ['mp3']
|
2615
|
+
opus: ['opus', 'Opus']
|
2661
2616
|
}[lowerCaseCodec];
|
2662
2617
|
for (let i = 0; i < codecsToCheck.length; i++) {
|
2663
|
-
var _getMediaSource;
|
2664
2618
|
if (isCodecMediaSourceSupported(codecsToCheck[i], 'audio', preferManagedMediaSource)) {
|
2665
2619
|
CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
|
2666
2620
|
return codecsToCheck[i];
|
2667
|
-
} else if (codecsToCheck[i] === 'mp3' && (_getMediaSource = getMediaSource(preferManagedMediaSource)) != null && _getMediaSource.isTypeSupported('audio/mpeg')) {
|
2668
|
-
return '';
|
2669
2621
|
}
|
2670
2622
|
}
|
2671
2623
|
return lowerCaseCodec;
|
2672
2624
|
}
|
2673
|
-
const AUDIO_CODEC_REGEXP = /flac|opus
|
2625
|
+
const AUDIO_CODEC_REGEXP = /flac|opus/i;
|
2674
2626
|
function getCodecCompatibleName(codec, preferManagedMediaSource = true) {
|
2675
2627
|
return codec.replace(AUDIO_CODEC_REGEXP, m => getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource));
|
2676
2628
|
}
|
@@ -2693,16 +2645,6 @@ function convertAVC1ToAVCOTI(codec) {
|
|
2693
2645
|
}
|
2694
2646
|
return codec;
|
2695
2647
|
}
|
2696
|
-
function getM2TSSupportedAudioTypes(preferManagedMediaSource) {
|
2697
|
-
const MediaSource = getMediaSource(preferManagedMediaSource) || {
|
2698
|
-
isTypeSupported: () => false
|
2699
|
-
};
|
2700
|
-
return {
|
2701
|
-
mpeg: MediaSource.isTypeSupported('audio/mpeg'),
|
2702
|
-
mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
|
2703
|
-
ac3: false
|
2704
|
-
};
|
2705
|
-
}
|
2706
2648
|
|
2707
2649
|
const MASTER_PLAYLIST_REGEX = /#EXT-X-STREAM-INF:([^\r\n]*)(?:[\r\n](?:#[^\r\n]*)?)*([^\r\n]+)|#EXT-X-(SESSION-DATA|SESSION-KEY|DEFINE|CONTENT-STEERING|START):([^\r\n]*)[\r\n]+/g;
|
2708
2650
|
const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g;
|
@@ -3503,10 +3445,10 @@ class PlaylistLoader {
|
|
3503
3445
|
const loaderContext = loader.context;
|
3504
3446
|
if (loaderContext && loaderContext.url === context.url && loaderContext.level === context.level) {
|
3505
3447
|
// same URL can't overlap
|
3506
|
-
|
3448
|
+
logger.trace('[playlist-loader]: playlist request ongoing');
|
3507
3449
|
return;
|
3508
3450
|
}
|
3509
|
-
|
3451
|
+
logger.log(`[playlist-loader]: aborting previous loader for type: ${context.type}`);
|
3510
3452
|
loader.abort();
|
3511
3453
|
}
|
3512
3454
|
|
@@ -3616,7 +3558,7 @@ class PlaylistLoader {
|
|
3616
3558
|
// alt audio rendition in which quality levels (main)
|
3617
3559
|
// contains both audio+video. but with mixed audio track not signaled
|
3618
3560
|
if (!embeddedAudioFound && levels[0].audioCodec && !levels[0].attrs.AUDIO) {
|
3619
|
-
|
3561
|
+
logger.log('[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one');
|
3620
3562
|
audioTracks.unshift({
|
3621
3563
|
type: 'main',
|
3622
3564
|
name: 'main',
|
@@ -3715,7 +3657,7 @@ class PlaylistLoader {
|
|
3715
3657
|
message += ` id: ${context.id} group-id: "${context.groupId}"`;
|
3716
3658
|
}
|
3717
3659
|
const error = new Error(message);
|
3718
|
-
|
3660
|
+
logger.warn(`[playlist-loader]: ${message}`);
|
3719
3661
|
let details = ErrorDetails.UNKNOWN;
|
3720
3662
|
let fatal = false;
|
3721
3663
|
const loader = this.getInternalLoader(context);
|
@@ -4280,47 +4222,7 @@ class LatencyController {
|
|
4280
4222
|
this.currentTime = 0;
|
4281
4223
|
this.stallCount = 0;
|
4282
4224
|
this._latency = null;
|
4283
|
-
this.
|
4284
|
-
const {
|
4285
|
-
media,
|
4286
|
-
levelDetails
|
4287
|
-
} = this;
|
4288
|
-
if (!media || !levelDetails) {
|
4289
|
-
return;
|
4290
|
-
}
|
4291
|
-
this.currentTime = media.currentTime;
|
4292
|
-
const latency = this.computeLatency();
|
4293
|
-
if (latency === null) {
|
4294
|
-
return;
|
4295
|
-
}
|
4296
|
-
this._latency = latency;
|
4297
|
-
|
4298
|
-
// Adapt playbackRate to meet target latency in low-latency mode
|
4299
|
-
const {
|
4300
|
-
lowLatencyMode,
|
4301
|
-
maxLiveSyncPlaybackRate
|
4302
|
-
} = this.config;
|
4303
|
-
if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
|
4304
|
-
return;
|
4305
|
-
}
|
4306
|
-
const targetLatency = this.targetLatency;
|
4307
|
-
if (targetLatency === null) {
|
4308
|
-
return;
|
4309
|
-
}
|
4310
|
-
const distanceFromTarget = latency - targetLatency;
|
4311
|
-
// Only adjust playbackRate when within one target duration of targetLatency
|
4312
|
-
// and more than one second from under-buffering.
|
4313
|
-
// Playback further than one target duration from target can be considered DVR playback.
|
4314
|
-
const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
|
4315
|
-
const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
|
4316
|
-
if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
|
4317
|
-
const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
|
4318
|
-
const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
|
4319
|
-
media.playbackRate = Math.min(max, Math.max(1, rate));
|
4320
|
-
} else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
|
4321
|
-
media.playbackRate = 1;
|
4322
|
-
}
|
4323
|
-
};
|
4225
|
+
this.timeupdateHandler = () => this.timeupdate();
|
4324
4226
|
this.hls = hls;
|
4325
4227
|
this.config = hls.config;
|
4326
4228
|
this.registerListeners();
|
@@ -4412,7 +4314,7 @@ class LatencyController {
|
|
4412
4314
|
this.onMediaDetaching();
|
4413
4315
|
this.levelDetails = null;
|
4414
4316
|
// @ts-ignore
|
4415
|
-
this.hls = null;
|
4317
|
+
this.hls = this.timeupdateHandler = null;
|
4416
4318
|
}
|
4417
4319
|
registerListeners() {
|
4418
4320
|
this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
@@ -4430,11 +4332,11 @@ class LatencyController {
|
|
4430
4332
|
}
|
4431
4333
|
onMediaAttached(event, data) {
|
4432
4334
|
this.media = data.media;
|
4433
|
-
this.media.addEventListener('timeupdate', this.
|
4335
|
+
this.media.addEventListener('timeupdate', this.timeupdateHandler);
|
4434
4336
|
}
|
4435
4337
|
onMediaDetaching() {
|
4436
4338
|
if (this.media) {
|
4437
|
-
this.media.removeEventListener('timeupdate', this.
|
4339
|
+
this.media.removeEventListener('timeupdate', this.timeupdateHandler);
|
4438
4340
|
this.media = null;
|
4439
4341
|
}
|
4440
4342
|
}
|
@@ -4448,10 +4350,10 @@ class LatencyController {
|
|
4448
4350
|
}) {
|
4449
4351
|
this.levelDetails = details;
|
4450
4352
|
if (details.advanced) {
|
4451
|
-
this.
|
4353
|
+
this.timeupdate();
|
4452
4354
|
}
|
4453
4355
|
if (!details.live && this.media) {
|
4454
|
-
this.media.removeEventListener('timeupdate', this.
|
4356
|
+
this.media.removeEventListener('timeupdate', this.timeupdateHandler);
|
4455
4357
|
}
|
4456
4358
|
}
|
4457
4359
|
onError(event, data) {
|
@@ -4461,7 +4363,48 @@ class LatencyController {
|
|
4461
4363
|
}
|
4462
4364
|
this.stallCount++;
|
4463
4365
|
if ((_this$levelDetails = this.levelDetails) != null && _this$levelDetails.live) {
|
4464
|
-
|
4366
|
+
logger.warn('[playback-rate-controller]: Stall detected, adjusting target latency');
|
4367
|
+
}
|
4368
|
+
}
|
4369
|
+
timeupdate() {
|
4370
|
+
const {
|
4371
|
+
media,
|
4372
|
+
levelDetails
|
4373
|
+
} = this;
|
4374
|
+
if (!media || !levelDetails) {
|
4375
|
+
return;
|
4376
|
+
}
|
4377
|
+
this.currentTime = media.currentTime;
|
4378
|
+
const latency = this.computeLatency();
|
4379
|
+
if (latency === null) {
|
4380
|
+
return;
|
4381
|
+
}
|
4382
|
+
this._latency = latency;
|
4383
|
+
|
4384
|
+
// Adapt playbackRate to meet target latency in low-latency mode
|
4385
|
+
const {
|
4386
|
+
lowLatencyMode,
|
4387
|
+
maxLiveSyncPlaybackRate
|
4388
|
+
} = this.config;
|
4389
|
+
if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
|
4390
|
+
return;
|
4391
|
+
}
|
4392
|
+
const targetLatency = this.targetLatency;
|
4393
|
+
if (targetLatency === null) {
|
4394
|
+
return;
|
4395
|
+
}
|
4396
|
+
const distanceFromTarget = latency - targetLatency;
|
4397
|
+
// Only adjust playbackRate when within one target duration of targetLatency
|
4398
|
+
// and more than one second from under-buffering.
|
4399
|
+
// Playback further than one target duration from target can be considered DVR playback.
|
4400
|
+
const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
|
4401
|
+
const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
|
4402
|
+
if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
|
4403
|
+
const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
|
4404
|
+
const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
|
4405
|
+
media.playbackRate = Math.min(max, Math.max(1, rate));
|
4406
|
+
} else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
|
4407
|
+
media.playbackRate = 1;
|
4465
4408
|
}
|
4466
4409
|
}
|
4467
4410
|
estimateLiveEdge() {
|
@@ -5233,13 +5176,18 @@ var ErrorActionFlags = {
|
|
5233
5176
|
MoveAllAlternatesMatchingHDCP: 2,
|
5234
5177
|
SwitchToSDR: 4
|
5235
5178
|
}; // Reserved for future use
|
5236
|
-
class ErrorController
|
5179
|
+
class ErrorController {
|
5237
5180
|
constructor(hls) {
|
5238
|
-
super('error-controller', hls.logger);
|
5239
5181
|
this.hls = void 0;
|
5240
5182
|
this.playlistError = 0;
|
5241
5183
|
this.penalizedRenditions = {};
|
5184
|
+
this.log = void 0;
|
5185
|
+
this.warn = void 0;
|
5186
|
+
this.error = void 0;
|
5242
5187
|
this.hls = hls;
|
5188
|
+
this.log = logger.log.bind(logger, `[info]:`);
|
5189
|
+
this.warn = logger.warn.bind(logger, `[warning]:`);
|
5190
|
+
this.error = logger.error.bind(logger, `[error]:`);
|
5243
5191
|
this.registerListeners();
|
5244
5192
|
}
|
5245
5193
|
registerListeners() {
|
@@ -5591,13 +5539,16 @@ class ErrorController extends Logger {
|
|
5591
5539
|
}
|
5592
5540
|
}
|
5593
5541
|
|
5594
|
-
class BasePlaylistController
|
5542
|
+
class BasePlaylistController {
|
5595
5543
|
constructor(hls, logPrefix) {
|
5596
|
-
super(logPrefix, hls.logger);
|
5597
5544
|
this.hls = void 0;
|
5598
5545
|
this.timer = -1;
|
5599
5546
|
this.requestScheduled = -1;
|
5600
5547
|
this.canLoad = false;
|
5548
|
+
this.log = void 0;
|
5549
|
+
this.warn = void 0;
|
5550
|
+
this.log = logger.log.bind(logger, `${logPrefix}:`);
|
5551
|
+
this.warn = logger.warn.bind(logger, `${logPrefix}:`);
|
5601
5552
|
this.hls = hls;
|
5602
5553
|
}
|
5603
5554
|
destroy() {
|
@@ -5630,7 +5581,7 @@ class BasePlaylistController extends Logger {
|
|
5630
5581
|
try {
|
5631
5582
|
uri = new self.URL(attr.URI, previous.url).href;
|
5632
5583
|
} catch (error) {
|
5633
|
-
|
5584
|
+
logger.warn(`Could not construct new URL for Rendition Report: ${error}`);
|
5634
5585
|
uri = attr.URI || '';
|
5635
5586
|
}
|
5636
5587
|
// Use exact match. Otherwise, the last partial match, if any, will be used
|
@@ -5717,12 +5668,7 @@ class BasePlaylistController extends Logger {
|
|
5717
5668
|
const cdnAge = lastAdvanced + details.ageHeader;
|
5718
5669
|
let currentGoal = Math.min(cdnAge - details.partTarget, details.targetduration * 1.5);
|
5719
5670
|
if (currentGoal > 0) {
|
5720
|
-
if (
|
5721
|
-
// Omit segment and part directives when the last response was more than 3 target durations ago,
|
5722
|
-
this.log(`Playlist last advanced ${lastAdvanced.toFixed(2)}s ago. Omitting segment and part directives.`);
|
5723
|
-
msn = undefined;
|
5724
|
-
part = undefined;
|
5725
|
-
} else if (previousDetails != null && previousDetails.tuneInGoal && cdnAge - details.partTarget > previousDetails.tuneInGoal) {
|
5671
|
+
if (previousDetails && currentGoal > previousDetails.tuneInGoal) {
|
5726
5672
|
// If we attempted to get the next or latest playlist update, but currentGoal increased,
|
5727
5673
|
// then we either can't catchup, or the "age" header cannot be trusted.
|
5728
5674
|
this.warn(`CDN Tune-in goal increased from: ${previousDetails.tuneInGoal} to: ${currentGoal} with playlist age: ${details.age}`);
|
@@ -6181,9 +6127,8 @@ function getCodecTiers(levels, audioTracksByGroup, minAutoLevel, maxAutoLevel) {
|
|
6181
6127
|
}, {});
|
6182
6128
|
}
|
6183
6129
|
|
6184
|
-
class AbrController
|
6130
|
+
class AbrController {
|
6185
6131
|
constructor(_hls) {
|
6186
|
-
super('abr', _hls.logger);
|
6187
6132
|
this.hls = void 0;
|
6188
6133
|
this.lastLevelLoadSec = 0;
|
6189
6134
|
this.lastLoadedFragLevel = -1;
|
@@ -6297,7 +6242,7 @@ class AbrController extends Logger {
|
|
6297
6242
|
this.resetEstimator(nextLoadLevelBitrate);
|
6298
6243
|
}
|
6299
6244
|
this.clearTimer();
|
6300
|
-
|
6245
|
+
logger.warn(`[abr] Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
|
6301
6246
|
Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s
|
6302
6247
|
Estimated load time for current fragment: ${fragLoadedDelay.toFixed(3)} s
|
6303
6248
|
Estimated load time for down switch fragment: ${fragLevelNextLoadedDelay.toFixed(3)} s
|
@@ -6317,7 +6262,7 @@ class AbrController extends Logger {
|
|
6317
6262
|
}
|
6318
6263
|
resetEstimator(abrEwmaDefaultEstimate) {
|
6319
6264
|
if (abrEwmaDefaultEstimate) {
|
6320
|
-
|
6265
|
+
logger.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
|
6321
6266
|
this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate;
|
6322
6267
|
}
|
6323
6268
|
this.firstSelection = -1;
|
@@ -6549,7 +6494,7 @@ class AbrController extends Logger {
|
|
6549
6494
|
}
|
6550
6495
|
const firstLevel = this.hls.firstLevel;
|
6551
6496
|
const clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel);
|
6552
|
-
|
6497
|
+
logger.warn(`[abr] Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
|
6553
6498
|
return clamped;
|
6554
6499
|
}
|
6555
6500
|
get forcedAutoLevel() {
|
@@ -6595,9 +6540,6 @@ class AbrController extends Logger {
|
|
6595
6540
|
partCurrent,
|
6596
6541
|
hls
|
6597
6542
|
} = this;
|
6598
|
-
if (hls.levels.length <= 1) {
|
6599
|
-
return hls.loadLevel;
|
6600
|
-
}
|
6601
6543
|
const {
|
6602
6544
|
maxAutoLevel,
|
6603
6545
|
config,
|
@@ -6630,13 +6572,13 @@ class AbrController extends Logger {
|
|
6630
6572
|
// cap maxLoadingDelay and ensure it is not bigger 'than bitrate test' frag duration
|
6631
6573
|
const maxLoadingDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxLoadingDelay) : config.maxLoadingDelay;
|
6632
6574
|
maxStarvationDelay = maxLoadingDelay - bitrateTestDelay;
|
6633
|
-
|
6575
|
+
logger.info(`[abr] bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
|
6634
6576
|
// don't use conservative factor on bitrate test
|
6635
6577
|
bwFactor = bwUpFactor = 1;
|
6636
6578
|
}
|
6637
6579
|
}
|
6638
6580
|
const bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor);
|
6639
|
-
|
6581
|
+
logger.info(`[abr] ${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
|
6640
6582
|
if (bestLevel > -1) {
|
6641
6583
|
return bestLevel;
|
6642
6584
|
}
|
@@ -6710,7 +6652,7 @@ class AbrController extends Logger {
|
|
6710
6652
|
currentVideoRange = preferHDR ? videoRanges[videoRanges.length - 1] : videoRanges[0];
|
6711
6653
|
currentFrameRate = minFramerate;
|
6712
6654
|
currentBw = Math.max(currentBw, minBitrate);
|
6713
|
-
|
6655
|
+
logger.log(`[abr] picked start tier ${JSON.stringify(startTier)}`);
|
6714
6656
|
} else {
|
6715
6657
|
currentCodecSet = level == null ? void 0 : level.codecSet;
|
6716
6658
|
currentVideoRange = level == null ? void 0 : level.videoRange;
|
@@ -6763,9 +6705,9 @@ class AbrController extends Logger {
|
|
6763
6705
|
const forcedAutoLevel = this.forcedAutoLevel;
|
6764
6706
|
if (i !== loadLevel && (forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)) {
|
6765
6707
|
if (levelsSkipped.length) {
|
6766
|
-
|
6708
|
+
logger.trace(`[abr] Skipped level(s) ${levelsSkipped.join(',')} of ${maxAutoLevel} max with CODECS and VIDEO-RANGE:"${levels[levelsSkipped[0]].codecs}" ${levels[levelsSkipped[0]].videoRange}; not compatible with "${level.codecs}" ${currentVideoRange}`);
|
6767
6709
|
}
|
6768
|
-
|
6710
|
+
logger.info(`[abr] switch candidate:${selectionBaseLevel}->${i} adjustedbw(${Math.round(adjustedbw)})-bitrate=${Math.round(adjustedbw - bitrate)} ttfb:${ttfbEstimateSec.toFixed(1)} avgDuration:${avgDuration.toFixed(1)} maxFetchDuration:${maxFetchDuration.toFixed(1)} fetchDuration:${fetchDuration.toFixed(1)} firstSelection:${firstSelection} codecSet:${currentCodecSet} videoRange:${currentVideoRange} hls.loadLevel:${loadLevel}`);
|
6769
6711
|
}
|
6770
6712
|
if (firstSelection) {
|
6771
6713
|
this.firstSelection = i;
|
@@ -6808,29 +6750,40 @@ class BufferHelper {
|
|
6808
6750
|
* Return true if `media`'s buffered include `position`
|
6809
6751
|
*/
|
6810
6752
|
static isBuffered(media, position) {
|
6811
|
-
|
6812
|
-
|
6813
|
-
|
6814
|
-
|
6815
|
-
|
6753
|
+
try {
|
6754
|
+
if (media) {
|
6755
|
+
const buffered = BufferHelper.getBuffered(media);
|
6756
|
+
for (let i = 0; i < buffered.length; i++) {
|
6757
|
+
if (position >= buffered.start(i) && position <= buffered.end(i)) {
|
6758
|
+
return true;
|
6759
|
+
}
|
6816
6760
|
}
|
6817
6761
|
}
|
6762
|
+
} catch (error) {
|
6763
|
+
// this is to catch
|
6764
|
+
// InvalidStateError: Failed to read the 'buffered' property from 'SourceBuffer':
|
6765
|
+
// This SourceBuffer has been removed from the parent media source
|
6818
6766
|
}
|
6819
6767
|
return false;
|
6820
6768
|
}
|
6821
6769
|
static bufferInfo(media, pos, maxHoleDuration) {
|
6822
|
-
|
6823
|
-
|
6824
|
-
|
6770
|
+
try {
|
6771
|
+
if (media) {
|
6772
|
+
const vbuffered = BufferHelper.getBuffered(media);
|
6825
6773
|
const buffered = [];
|
6826
|
-
|
6774
|
+
let i;
|
6775
|
+
for (i = 0; i < vbuffered.length; i++) {
|
6827
6776
|
buffered.push({
|
6828
6777
|
start: vbuffered.start(i),
|
6829
6778
|
end: vbuffered.end(i)
|
6830
6779
|
});
|
6831
6780
|
}
|
6832
|
-
return
|
6781
|
+
return this.bufferedInfo(buffered, pos, maxHoleDuration);
|
6833
6782
|
}
|
6783
|
+
} catch (error) {
|
6784
|
+
// this is to catch
|
6785
|
+
// InvalidStateError: Failed to read the 'buffered' property from 'SourceBuffer':
|
6786
|
+
// This SourceBuffer has been removed from the parent media source
|
6834
6787
|
}
|
6835
6788
|
return {
|
6836
6789
|
len: 0,
|
@@ -6842,7 +6795,14 @@ class BufferHelper {
|
|
6842
6795
|
static bufferedInfo(buffered, pos, maxHoleDuration) {
|
6843
6796
|
pos = Math.max(0, pos);
|
6844
6797
|
// sort on buffer.start/smaller end (IE does not always return sorted buffered range)
|
6845
|
-
buffered.sort((a, b)
|
6798
|
+
buffered.sort(function (a, b) {
|
6799
|
+
const diff = a.start - b.start;
|
6800
|
+
if (diff) {
|
6801
|
+
return diff;
|
6802
|
+
} else {
|
6803
|
+
return b.end - a.end;
|
6804
|
+
}
|
6805
|
+
});
|
6846
6806
|
let buffered2 = [];
|
6847
6807
|
if (maxHoleDuration) {
|
6848
6808
|
// there might be some small holes between buffer time range
|
@@ -6909,7 +6869,7 @@ class BufferHelper {
|
|
6909
6869
|
*/
|
6910
6870
|
static getBuffered(media) {
|
6911
6871
|
try {
|
6912
|
-
return media.buffered
|
6872
|
+
return media.buffered;
|
6913
6873
|
} catch (e) {
|
6914
6874
|
logger.log('failed to get media.buffered', e);
|
6915
6875
|
return noopBuffered;
|
@@ -6934,22 +6894,24 @@ class BufferOperationQueue {
|
|
6934
6894
|
this.executeNext(type);
|
6935
6895
|
}
|
6936
6896
|
}
|
6897
|
+
insertAbort(operation, type) {
|
6898
|
+
const queue = this.queues[type];
|
6899
|
+
queue.unshift(operation);
|
6900
|
+
this.executeNext(type);
|
6901
|
+
}
|
6937
6902
|
appendBlocker(type) {
|
6938
|
-
|
6939
|
-
|
6940
|
-
|
6941
|
-
onStart: () => {},
|
6942
|
-
onComplete: () => {},
|
6943
|
-
onError: () => {}
|
6944
|
-
};
|
6945
|
-
this.append(operation, type);
|
6903
|
+
let execute;
|
6904
|
+
const promise = new Promise(resolve => {
|
6905
|
+
execute = resolve;
|
6946
6906
|
});
|
6947
|
-
|
6948
|
-
|
6949
|
-
|
6950
|
-
|
6951
|
-
|
6952
|
-
}
|
6907
|
+
const operation = {
|
6908
|
+
execute,
|
6909
|
+
onStart: () => {},
|
6910
|
+
onComplete: () => {},
|
6911
|
+
onError: () => {}
|
6912
|
+
};
|
6913
|
+
this.append(operation, type);
|
6914
|
+
return promise;
|
6953
6915
|
}
|
6954
6916
|
executeNext(type) {
|
6955
6917
|
const queue = this.queues[type];
|
@@ -6981,9 +6943,8 @@ class BufferOperationQueue {
|
|
6981
6943
|
}
|
6982
6944
|
|
6983
6945
|
const VIDEO_CODEC_PROFILE_REPLACE = /(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\.[^.,]+)+/;
|
6984
|
-
class BufferController
|
6985
|
-
constructor(hls
|
6986
|
-
super('buffer-controller', hls.logger);
|
6946
|
+
class BufferController {
|
6947
|
+
constructor(hls) {
|
6987
6948
|
// The level details used to determine duration, target-duration and live
|
6988
6949
|
this.details = null;
|
6989
6950
|
// cache the self generated object url to detect hijack of video tag
|
@@ -6993,7 +6954,6 @@ class BufferController extends Logger {
|
|
6993
6954
|
// References to event listeners for each SourceBuffer, so that they can be referenced for event removal
|
6994
6955
|
this.listeners = void 0;
|
6995
6956
|
this.hls = void 0;
|
6996
|
-
this.fragmentTracker = void 0;
|
6997
6957
|
// The number of BUFFER_CODEC events received before any sourceBuffers are created
|
6998
6958
|
this.bufferCodecEventsExpected = 0;
|
6999
6959
|
// The total number of BUFFER_CODEC events received
|
@@ -7004,10 +6964,6 @@ class BufferController extends Logger {
|
|
7004
6964
|
this.mediaSource = null;
|
7005
6965
|
// Last MP3 audio chunk appended
|
7006
6966
|
this.lastMpegAudioChunk = null;
|
7007
|
-
// Audio fragment blocked from appending until corresponding video appends or context changes
|
7008
|
-
this.blockedAudioAppend = null;
|
7009
|
-
// Keep track of video append position for unblocking audio
|
7010
|
-
this.lastVideoAppendEnd = 0;
|
7011
6967
|
this.appendSource = void 0;
|
7012
6968
|
// counters
|
7013
6969
|
this.appendErrors = {
|
@@ -7018,6 +6974,9 @@ class BufferController extends Logger {
|
|
7018
6974
|
this.tracks = {};
|
7019
6975
|
this.pendingTracks = {};
|
7020
6976
|
this.sourceBuffer = void 0;
|
6977
|
+
this.log = void 0;
|
6978
|
+
this.warn = void 0;
|
6979
|
+
this.error = void 0;
|
7021
6980
|
this._onEndStreaming = event => {
|
7022
6981
|
if (!this.hls) {
|
7023
6982
|
return;
|
@@ -7039,10 +6998,7 @@ class BufferController extends Logger {
|
|
7039
6998
|
this.log('Media source opened');
|
7040
6999
|
if (media) {
|
7041
7000
|
media.removeEventListener('emptied', this._onMediaEmptied);
|
7042
|
-
|
7043
|
-
if (durationAndRange) {
|
7044
|
-
this.updateMediaSource(durationAndRange);
|
7045
|
-
}
|
7001
|
+
this.updateMediaElementDuration();
|
7046
7002
|
this.hls.trigger(Events.MEDIA_ATTACHED, {
|
7047
7003
|
media,
|
7048
7004
|
mediaSource: mediaSource
|
@@ -7066,12 +7022,15 @@ class BufferController extends Logger {
|
|
7066
7022
|
_objectUrl
|
7067
7023
|
} = this;
|
7068
7024
|
if (mediaSrc !== _objectUrl) {
|
7069
|
-
|
7025
|
+
logger.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
|
7070
7026
|
}
|
7071
7027
|
};
|
7072
7028
|
this.hls = hls;
|
7073
|
-
|
7074
|
-
this.appendSource = hls.config.preferManagedMediaSource;
|
7029
|
+
const logPrefix = '[buffer-controller]';
|
7030
|
+
this.appendSource = hls.config.preferManagedMediaSource && typeof self !== 'undefined' && self.ManagedMediaSource;
|
7031
|
+
this.log = logger.log.bind(logger, logPrefix);
|
7032
|
+
this.warn = logger.warn.bind(logger, logPrefix);
|
7033
|
+
this.error = logger.error.bind(logger, logPrefix);
|
7075
7034
|
this._initSourceBuffer();
|
7076
7035
|
this.registerListeners();
|
7077
7036
|
}
|
@@ -7083,13 +7042,7 @@ class BufferController extends Logger {
|
|
7083
7042
|
this.details = null;
|
7084
7043
|
this.lastMpegAudioChunk = null;
|
7085
7044
|
// @ts-ignore
|
7086
|
-
this.hls =
|
7087
|
-
// @ts-ignore
|
7088
|
-
this._onMediaSourceOpen = this._onMediaSourceClose = null;
|
7089
|
-
// @ts-ignore
|
7090
|
-
this._onMediaSourceEnded = null;
|
7091
|
-
// @ts-ignore
|
7092
|
-
this._onStartStreaming = this._onEndStreaming = null;
|
7045
|
+
this.hls = null;
|
7093
7046
|
}
|
7094
7047
|
registerListeners() {
|
7095
7048
|
const {
|
@@ -7139,8 +7092,6 @@ class BufferController extends Logger {
|
|
7139
7092
|
audiovideo: 0
|
7140
7093
|
};
|
7141
7094
|
this.lastMpegAudioChunk = null;
|
7142
|
-
this.blockedAudioAppend = null;
|
7143
|
-
this.lastVideoAppendEnd = 0;
|
7144
7095
|
}
|
7145
7096
|
onManifestLoading() {
|
7146
7097
|
this.bufferCodecEventsExpected = this._bufferCodecEventsTotal = 0;
|
@@ -7169,8 +7120,10 @@ class BufferController extends Logger {
|
|
7169
7120
|
ms.addEventListener('sourceopen', this._onMediaSourceOpen);
|
7170
7121
|
ms.addEventListener('sourceended', this._onMediaSourceEnded);
|
7171
7122
|
ms.addEventListener('sourceclose', this._onMediaSourceClose);
|
7172
|
-
|
7173
|
-
|
7123
|
+
if (this.appendSource) {
|
7124
|
+
ms.addEventListener('startstreaming', this._onStartStreaming);
|
7125
|
+
ms.addEventListener('endstreaming', this._onEndStreaming);
|
7126
|
+
}
|
7174
7127
|
|
7175
7128
|
// cache the locally generated object url
|
7176
7129
|
const objectUrl = this._objectUrl = self.URL.createObjectURL(ms);
|
@@ -7217,8 +7170,10 @@ class BufferController extends Logger {
|
|
7217
7170
|
mediaSource.removeEventListener('sourceopen', this._onMediaSourceOpen);
|
7218
7171
|
mediaSource.removeEventListener('sourceended', this._onMediaSourceEnded);
|
7219
7172
|
mediaSource.removeEventListener('sourceclose', this._onMediaSourceClose);
|
7220
|
-
|
7221
|
-
|
7173
|
+
if (this.appendSource) {
|
7174
|
+
mediaSource.removeEventListener('startstreaming', this._onStartStreaming);
|
7175
|
+
mediaSource.removeEventListener('endstreaming', this._onEndStreaming);
|
7176
|
+
}
|
7222
7177
|
|
7223
7178
|
// Detach properly the MediaSource from the HTMLMediaElement as
|
7224
7179
|
// suggested in https://github.com/w3c/media-source/issues/53.
|
@@ -7254,7 +7209,6 @@ class BufferController extends Logger {
|
|
7254
7209
|
this.resetBuffer(type);
|
7255
7210
|
});
|
7256
7211
|
this._initSourceBuffer();
|
7257
|
-
this.hls.resumeBuffering();
|
7258
7212
|
}
|
7259
7213
|
resetBuffer(type) {
|
7260
7214
|
const sb = this.sourceBuffer[type];
|
@@ -7278,10 +7232,9 @@ class BufferController extends Logger {
|
|
7278
7232
|
const trackNames = Object.keys(data);
|
7279
7233
|
trackNames.forEach(trackName => {
|
7280
7234
|
if (sourceBufferCount) {
|
7281
|
-
var _track$buffer;
|
7282
7235
|
// check if SourceBuffer codec needs to change
|
7283
7236
|
const track = this.tracks[trackName];
|
7284
|
-
if (track && typeof
|
7237
|
+
if (track && typeof track.buffer.changeType === 'function') {
|
7285
7238
|
var _trackCodec;
|
7286
7239
|
const {
|
7287
7240
|
id,
|
@@ -7296,7 +7249,7 @@ class BufferController extends Logger {
|
|
7296
7249
|
const nextCodec = (_trackCodec = trackCodec) == null ? void 0 : _trackCodec.replace(VIDEO_CODEC_PROFILE_REPLACE, '$1');
|
7297
7250
|
if (trackCodec && currentCodec !== nextCodec) {
|
7298
7251
|
if (trackName.slice(0, 5) === 'audio') {
|
7299
|
-
trackCodec = getCodecCompatibleName(trackCodec, this.
|
7252
|
+
trackCodec = getCodecCompatibleName(trackCodec, this.appendSource);
|
7300
7253
|
}
|
7301
7254
|
const mimeType = `${container};codecs=${trackCodec}`;
|
7302
7255
|
this.appendChangeType(trackName, mimeType);
|
@@ -7351,54 +7304,20 @@ class BufferController extends Logger {
|
|
7351
7304
|
};
|
7352
7305
|
operationQueue.append(operation, type, !!this.pendingTracks[type]);
|
7353
7306
|
}
|
7354
|
-
blockAudio(partOrFrag) {
|
7355
|
-
var _this$fragmentTracker;
|
7356
|
-
const pStart = partOrFrag.start;
|
7357
|
-
const pTime = pStart + partOrFrag.duration * 0.05;
|
7358
|
-
const atGap = ((_this$fragmentTracker = this.fragmentTracker.getAppendedFrag(pStart, PlaylistLevelType.MAIN)) == null ? void 0 : _this$fragmentTracker.gap) === true;
|
7359
|
-
if (atGap) {
|
7360
|
-
return;
|
7361
|
-
}
|
7362
|
-
const op = {
|
7363
|
-
execute: () => {
|
7364
|
-
var _this$fragmentTracker2;
|
7365
|
-
if (this.lastVideoAppendEnd > pTime || this.sourceBuffer.video && BufferHelper.isBuffered(this.sourceBuffer.video, pTime) || ((_this$fragmentTracker2 = this.fragmentTracker.getAppendedFrag(pTime, PlaylistLevelType.MAIN)) == null ? void 0 : _this$fragmentTracker2.gap) === true) {
|
7366
|
-
this.blockedAudioAppend = null;
|
7367
|
-
this.operationQueue.shiftAndExecuteNext('audio');
|
7368
|
-
}
|
7369
|
-
},
|
7370
|
-
onStart: () => {},
|
7371
|
-
onComplete: () => {},
|
7372
|
-
onError: () => {}
|
7373
|
-
};
|
7374
|
-
this.blockedAudioAppend = {
|
7375
|
-
op,
|
7376
|
-
frag: partOrFrag
|
7377
|
-
};
|
7378
|
-
this.operationQueue.append(op, 'audio', true);
|
7379
|
-
}
|
7380
|
-
unblockAudio() {
|
7381
|
-
const blockedAudioAppend = this.blockedAudioAppend;
|
7382
|
-
if (blockedAudioAppend) {
|
7383
|
-
this.blockedAudioAppend = null;
|
7384
|
-
this.operationQueue.unblockAudio(blockedAudioAppend.op);
|
7385
|
-
}
|
7386
|
-
}
|
7387
7307
|
onBufferAppending(event, eventData) {
|
7388
7308
|
const {
|
7309
|
+
hls,
|
7389
7310
|
operationQueue,
|
7390
7311
|
tracks
|
7391
7312
|
} = this;
|
7392
7313
|
const {
|
7393
7314
|
data,
|
7394
7315
|
type,
|
7395
|
-
parent,
|
7396
7316
|
frag,
|
7397
7317
|
part,
|
7398
7318
|
chunkMeta
|
7399
7319
|
} = eventData;
|
7400
7320
|
const chunkStats = chunkMeta.buffering[type];
|
7401
|
-
const sn = frag.sn;
|
7402
7321
|
const bufferAppendingStart = self.performance.now();
|
7403
7322
|
chunkStats.start = bufferAppendingStart;
|
7404
7323
|
const fragBuffering = frag.stats.buffering;
|
@@ -7421,36 +7340,7 @@ class BufferController extends Logger {
|
|
7421
7340
|
checkTimestampOffset = !this.lastMpegAudioChunk || chunkMeta.id === 1 || this.lastMpegAudioChunk.sn !== chunkMeta.sn;
|
7422
7341
|
this.lastMpegAudioChunk = chunkMeta;
|
7423
7342
|
}
|
7424
|
-
|
7425
|
-
// Block audio append until overlapping video append
|
7426
|
-
const videoSb = this.sourceBuffer.video;
|
7427
|
-
if (videoSb && sn !== 'initSegment') {
|
7428
|
-
const partOrFrag = part || frag;
|
7429
|
-
const blockedAudioAppend = this.blockedAudioAppend;
|
7430
|
-
if (type === 'audio' && parent !== 'main' && !this.blockedAudioAppend) {
|
7431
|
-
const pStart = partOrFrag.start;
|
7432
|
-
const pTime = pStart + partOrFrag.duration * 0.05;
|
7433
|
-
const vbuffered = videoSb.buffered;
|
7434
|
-
const vappending = this.operationQueue.current('video');
|
7435
|
-
if (!vbuffered.length && !vappending) {
|
7436
|
-
// wait for video before appending audio
|
7437
|
-
this.blockAudio(partOrFrag);
|
7438
|
-
} else if (!vappending && !BufferHelper.isBuffered(videoSb, pTime) && this.lastVideoAppendEnd < pTime) {
|
7439
|
-
// audio is ahead of video
|
7440
|
-
this.blockAudio(partOrFrag);
|
7441
|
-
}
|
7442
|
-
} else if (type === 'video') {
|
7443
|
-
const videoAppendEnd = partOrFrag.end;
|
7444
|
-
if (blockedAudioAppend) {
|
7445
|
-
const audioStart = blockedAudioAppend.frag.start;
|
7446
|
-
if (videoAppendEnd > audioStart || videoAppendEnd < this.lastVideoAppendEnd || BufferHelper.isBuffered(videoSb, audioStart)) {
|
7447
|
-
this.unblockAudio();
|
7448
|
-
}
|
7449
|
-
}
|
7450
|
-
this.lastVideoAppendEnd = videoAppendEnd;
|
7451
|
-
}
|
7452
|
-
}
|
7453
|
-
const fragStart = (part || frag).start;
|
7343
|
+
const fragStart = frag.start;
|
7454
7344
|
const operation = {
|
7455
7345
|
execute: () => {
|
7456
7346
|
chunkStats.executeStart = self.performance.now();
|
@@ -7459,7 +7349,7 @@ class BufferController extends Logger {
|
|
7459
7349
|
if (sb) {
|
7460
7350
|
const delta = fragStart - sb.timestampOffset;
|
7461
7351
|
if (Math.abs(delta) >= 0.1) {
|
7462
|
-
this.log(`Updating audio SourceBuffer timestampOffset to ${fragStart} (delta: ${delta}) sn: ${sn})`);
|
7352
|
+
this.log(`Updating audio SourceBuffer timestampOffset to ${fragStart} (delta: ${delta}) sn: ${frag.sn})`);
|
7463
7353
|
sb.timestampOffset = fragStart;
|
7464
7354
|
}
|
7465
7355
|
}
|
@@ -7526,21 +7416,22 @@ class BufferController extends Logger {
|
|
7526
7416
|
/* with UHD content, we could get loop of quota exceeded error until
|
7527
7417
|
browser is able to evict some data from sourcebuffer. Retrying can help recover.
|
7528
7418
|
*/
|
7529
|
-
this.warn(`Failed ${appendErrorCount}/${
|
7530
|
-
if (appendErrorCount >=
|
7419
|
+
this.warn(`Failed ${appendErrorCount}/${hls.config.appendErrorMaxRetry} times to append segment in "${type}" sourceBuffer`);
|
7420
|
+
if (appendErrorCount >= hls.config.appendErrorMaxRetry) {
|
7531
7421
|
event.fatal = true;
|
7532
7422
|
}
|
7533
7423
|
}
|
7534
|
-
|
7424
|
+
hls.trigger(Events.ERROR, event);
|
7535
7425
|
}
|
7536
7426
|
};
|
7537
7427
|
operationQueue.append(operation, type, !!this.pendingTracks[type]);
|
7538
7428
|
}
|
7539
|
-
|
7540
|
-
|
7541
|
-
|
7542
|
-
|
7543
|
-
|
7429
|
+
onBufferFlushing(event, data) {
|
7430
|
+
const {
|
7431
|
+
operationQueue
|
7432
|
+
} = this;
|
7433
|
+
const flushOperation = type => ({
|
7434
|
+
execute: this.removeExecutor.bind(this, type, data.startOffset, data.endOffset),
|
7544
7435
|
onStart: () => {
|
7545
7436
|
// logger.debug(`[buffer-controller]: Started flushing ${data.startOffset} -> ${data.endOffset} for ${type} Source Buffer`);
|
7546
7437
|
},
|
@@ -7553,22 +7444,12 @@ class BufferController extends Logger {
|
|
7553
7444
|
onError: error => {
|
7554
7445
|
this.warn(`Failed to remove from ${type} SourceBuffer`, error);
|
7555
7446
|
}
|
7556
|
-
};
|
7557
|
-
|
7558
|
-
|
7559
|
-
const {
|
7560
|
-
operationQueue
|
7561
|
-
} = this;
|
7562
|
-
const {
|
7563
|
-
type,
|
7564
|
-
startOffset,
|
7565
|
-
endOffset
|
7566
|
-
} = data;
|
7567
|
-
if (type) {
|
7568
|
-
operationQueue.append(this.getFlushOp(type, startOffset, endOffset), type);
|
7447
|
+
});
|
7448
|
+
if (data.type) {
|
7449
|
+
operationQueue.append(flushOperation(data.type), data.type);
|
7569
7450
|
} else {
|
7570
|
-
this.getSourceBufferTypes().forEach(
|
7571
|
-
operationQueue.append(
|
7451
|
+
this.getSourceBufferTypes().forEach(type => {
|
7452
|
+
operationQueue.append(flushOperation(type), type);
|
7572
7453
|
});
|
7573
7454
|
}
|
7574
7455
|
}
|
@@ -7615,9 +7496,6 @@ class BufferController extends Logger {
|
|
7615
7496
|
// on BUFFER_EOS mark matching sourcebuffer(s) as ended and trigger checkEos()
|
7616
7497
|
// an undefined data.type will mark all buffers as EOS.
|
7617
7498
|
onBufferEos(event, data) {
|
7618
|
-
if (data.type === 'video') {
|
7619
|
-
this.unblockAudio();
|
7620
|
-
}
|
7621
7499
|
const ended = this.getSourceBufferTypes().reduce((acc, type) => {
|
7622
7500
|
const sb = this.sourceBuffer[type];
|
7623
7501
|
if (sb && (!data.type || data.type === type)) {
|
@@ -7660,14 +7538,10 @@ class BufferController extends Logger {
|
|
7660
7538
|
return;
|
7661
7539
|
}
|
7662
7540
|
this.details = details;
|
7663
|
-
const durationAndRange = this.getDurationAndRange();
|
7664
|
-
if (!durationAndRange) {
|
7665
|
-
return;
|
7666
|
-
}
|
7667
7541
|
if (this.getSourceBufferTypes().length) {
|
7668
|
-
this.blockBuffers(
|
7542
|
+
this.blockBuffers(this.updateMediaElementDuration.bind(this));
|
7669
7543
|
} else {
|
7670
|
-
this.
|
7544
|
+
this.updateMediaElementDuration();
|
7671
7545
|
}
|
7672
7546
|
}
|
7673
7547
|
trimBuffers() {
|
@@ -7772,9 +7646,9 @@ class BufferController extends Logger {
|
|
7772
7646
|
* 'liveDurationInfinity` is set to `true`
|
7773
7647
|
* More details: https://github.com/video-dev/hls.js/issues/355
|
7774
7648
|
*/
|
7775
|
-
|
7649
|
+
updateMediaElementDuration() {
|
7776
7650
|
if (!this.details || !this.media || !this.mediaSource || this.mediaSource.readyState !== 'open') {
|
7777
|
-
return
|
7651
|
+
return;
|
7778
7652
|
}
|
7779
7653
|
const {
|
7780
7654
|
details,
|
@@ -7788,41 +7662,25 @@ class BufferController extends Logger {
|
|
7788
7662
|
if (details.live && hls.config.liveDurationInfinity) {
|
7789
7663
|
// Override duration to Infinity
|
7790
7664
|
mediaSource.duration = Infinity;
|
7791
|
-
|
7792
|
-
if (len && details.live && !!mediaSource.setLiveSeekableRange) {
|
7793
|
-
const start = Math.max(0, details.fragments[0].start);
|
7794
|
-
const end = Math.max(start, start + details.totalduration);
|
7795
|
-
return {
|
7796
|
-
duration: Infinity,
|
7797
|
-
start,
|
7798
|
-
end
|
7799
|
-
};
|
7800
|
-
}
|
7801
|
-
return {
|
7802
|
-
duration: Infinity
|
7803
|
-
};
|
7665
|
+
this.updateSeekableRange(details);
|
7804
7666
|
} else if (levelDuration > msDuration && levelDuration > mediaDuration || !isFiniteNumber(mediaDuration)) {
|
7805
|
-
|
7806
|
-
|
7807
|
-
|
7667
|
+
// levelDuration was the last value we set.
|
7668
|
+
// not using mediaSource.duration as the browser may tweak this value
|
7669
|
+
// only update Media Source duration if its value increase, this is to avoid
|
7670
|
+
// flushing already buffered portion when switching between quality level
|
7671
|
+
this.log(`Updating Media Source duration to ${levelDuration.toFixed(3)}`);
|
7672
|
+
mediaSource.duration = levelDuration;
|
7808
7673
|
}
|
7809
|
-
return null;
|
7810
7674
|
}
|
7811
|
-
|
7812
|
-
|
7813
|
-
|
7814
|
-
|
7815
|
-
|
7816
|
-
|
7817
|
-
|
7818
|
-
|
7819
|
-
|
7820
|
-
this.log(`Updating Media Source duration to ${duration.toFixed(3)}`);
|
7821
|
-
}
|
7822
|
-
this.mediaSource.duration = duration;
|
7823
|
-
if (start !== undefined && end !== undefined) {
|
7824
|
-
this.log(`Media Source duration is set to ${this.mediaSource.duration}. Setting seekable range to ${start}-${end}.`);
|
7825
|
-
this.mediaSource.setLiveSeekableRange(start, end);
|
7675
|
+
updateSeekableRange(levelDetails) {
|
7676
|
+
const mediaSource = this.mediaSource;
|
7677
|
+
const fragments = levelDetails.fragments;
|
7678
|
+
const len = fragments.length;
|
7679
|
+
if (len && levelDetails.live && mediaSource != null && mediaSource.setLiveSeekableRange) {
|
7680
|
+
const start = Math.max(0, fragments[0].start);
|
7681
|
+
const end = Math.max(start, start + levelDetails.totalduration);
|
7682
|
+
this.log(`Media Source duration is set to ${mediaSource.duration}. Setting seekable range to ${start}-${end}.`);
|
7683
|
+
mediaSource.setLiveSeekableRange(start, end);
|
7826
7684
|
}
|
7827
7685
|
}
|
7828
7686
|
checkPendingTracks() {
|
@@ -7880,7 +7738,7 @@ class BufferController extends Logger {
|
|
7880
7738
|
let codec = track.levelCodec || track.codec;
|
7881
7739
|
if (codec) {
|
7882
7740
|
if (trackName.slice(0, 5) === 'audio') {
|
7883
|
-
codec = getCodecCompatibleName(codec, this.
|
7741
|
+
codec = getCodecCompatibleName(codec, this.appendSource);
|
7884
7742
|
}
|
7885
7743
|
}
|
7886
7744
|
const mimeType = `${track.container};codecs=${codec}`;
|
@@ -7892,15 +7750,17 @@ class BufferController extends Logger {
|
|
7892
7750
|
this.addBufferListener(sbName, 'updateend', this._onSBUpdateEnd);
|
7893
7751
|
this.addBufferListener(sbName, 'error', this._onSBUpdateError);
|
7894
7752
|
// ManagedSourceBuffer bufferedchange event
|
7895
|
-
this.
|
7896
|
-
|
7897
|
-
|
7898
|
-
|
7899
|
-
|
7900
|
-
|
7901
|
-
|
7902
|
-
|
7903
|
-
|
7753
|
+
if (this.appendSource) {
|
7754
|
+
this.addBufferListener(sbName, 'bufferedchange', (type, event) => {
|
7755
|
+
// If media was ejected check for a change. Added ranges are redundant with changes on 'updateend' event.
|
7756
|
+
const removedRanges = event.removedRanges;
|
7757
|
+
if (removedRanges != null && removedRanges.length) {
|
7758
|
+
this.hls.trigger(Events.BUFFER_FLUSHED, {
|
7759
|
+
type: trackName
|
7760
|
+
});
|
7761
|
+
}
|
7762
|
+
});
|
7763
|
+
}
|
7904
7764
|
this.tracks[trackName] = {
|
7905
7765
|
buffer: sb,
|
7906
7766
|
codec: codec,
|
@@ -8005,7 +7865,6 @@ class BufferController extends Logger {
|
|
8005
7865
|
}
|
8006
7866
|
return;
|
8007
7867
|
}
|
8008
|
-
sb.ending = false;
|
8009
7868
|
sb.ended = false;
|
8010
7869
|
sb.appendBuffer(data);
|
8011
7870
|
}
|
@@ -8025,14 +7884,10 @@ class BufferController extends Logger {
|
|
8025
7884
|
|
8026
7885
|
// logger.debug(`[buffer-controller]: Blocking ${buffers} SourceBuffer`);
|
8027
7886
|
const blockingOperations = buffers.map(type => operationQueue.appendBlocker(type));
|
8028
|
-
|
8029
|
-
if (audioBlocked) {
|
8030
|
-
this.unblockAudio();
|
8031
|
-
}
|
8032
|
-
Promise.all(blockingOperations).then(result => {
|
7887
|
+
Promise.all(blockingOperations).then(() => {
|
8033
7888
|
// logger.debug(`[buffer-controller]: Blocking operation resolved; unblocking ${buffers} SourceBuffer`);
|
8034
7889
|
onUnblocked();
|
8035
|
-
buffers.forEach(
|
7890
|
+
buffers.forEach(type => {
|
8036
7891
|
const sb = this.sourceBuffer[type];
|
8037
7892
|
// Only cycle the queue if the SB is not updating. There's a bug in Chrome which sets the SB updating flag to
|
8038
7893
|
// true when changing the MediaSource duration (https://bugs.chromium.org/p/chromium/issues/detail?id=959359&can=2&q=mediasource%20duration)
|
@@ -8193,10 +8048,10 @@ class CapLevelController {
|
|
8193
8048
|
const hls = this.hls;
|
8194
8049
|
const maxLevel = this.getMaxLevel(levels.length - 1);
|
8195
8050
|
if (maxLevel !== this.autoLevelCapping) {
|
8196
|
-
|
8051
|
+
logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
|
8197
8052
|
}
|
8198
8053
|
hls.autoLevelCapping = maxLevel;
|
8199
|
-
if (hls.
|
8054
|
+
if (hls.autoLevelCapping > this.autoLevelCapping && this.streamController) {
|
8200
8055
|
// if auto level capping has a higher value for the previous one, flush the buffer using nextLevelSwitch
|
8201
8056
|
// usually happen when the user go to the fullscreen mode.
|
8202
8057
|
this.streamController.nextLevelSwitch();
|
@@ -8371,10 +8226,10 @@ class FPSController {
|
|
8371
8226
|
totalDroppedFrames: droppedFrames
|
8372
8227
|
});
|
8373
8228
|
if (droppedFPS > 0) {
|
8374
|
-
//
|
8229
|
+
// logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
|
8375
8230
|
if (currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
|
8376
8231
|
let currentLevel = hls.currentLevel;
|
8377
|
-
|
8232
|
+
logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
|
8378
8233
|
if (currentLevel > 0 && (hls.autoLevelCapping === -1 || hls.autoLevelCapping >= currentLevel)) {
|
8379
8234
|
currentLevel = currentLevel - 1;
|
8380
8235
|
hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, {
|
@@ -8407,10 +8262,10 @@ class FPSController {
|
|
8407
8262
|
}
|
8408
8263
|
|
8409
8264
|
const PATHWAY_PENALTY_DURATION_MS = 300000;
|
8410
|
-
class ContentSteeringController
|
8265
|
+
class ContentSteeringController {
|
8411
8266
|
constructor(hls) {
|
8412
|
-
super('content-steering', hls.logger);
|
8413
8267
|
this.hls = void 0;
|
8268
|
+
this.log = void 0;
|
8414
8269
|
this.loader = null;
|
8415
8270
|
this.uri = null;
|
8416
8271
|
this.pathwayId = '.';
|
@@ -8425,6 +8280,7 @@ class ContentSteeringController extends Logger {
|
|
8425
8280
|
this.subtitleTracks = null;
|
8426
8281
|
this.penalizedPathways = {};
|
8427
8282
|
this.hls = hls;
|
8283
|
+
this.log = logger.log.bind(logger, `[content-steering]:`);
|
8428
8284
|
this.registerListeners();
|
8429
8285
|
}
|
8430
8286
|
registerListeners() {
|
@@ -8548,7 +8404,7 @@ class ContentSteeringController extends Logger {
|
|
8548
8404
|
errorAction.resolved = this.pathwayId !== errorPathway;
|
8549
8405
|
}
|
8550
8406
|
if (!errorAction.resolved) {
|
8551
|
-
|
8407
|
+
logger.warn(`Could not resolve ${data.details} ("${data.error.message}") with content-steering for Pathway: ${errorPathway} levels: ${levels ? levels.length : levels} priorities: ${JSON.stringify(pathwayPriority)} penalized: ${JSON.stringify(this.penalizedPathways)}`);
|
8552
8408
|
}
|
8553
8409
|
}
|
8554
8410
|
}
|
@@ -8719,7 +8575,7 @@ class ContentSteeringController extends Logger {
|
|
8719
8575
|
onSuccess: (response, stats, context, networkDetails) => {
|
8720
8576
|
this.log(`Loaded steering manifest: "${url}"`);
|
8721
8577
|
const steeringData = response.data;
|
8722
|
-
if (
|
8578
|
+
if (steeringData.VERSION !== 1) {
|
8723
8579
|
this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
|
8724
8580
|
return;
|
8725
8581
|
}
|
@@ -9627,7 +9483,7 @@ const hlsDefaultConfig = _objectSpread2(_objectSpread2({
|
|
9627
9483
|
});
|
9628
9484
|
function timelineConfig() {
|
9629
9485
|
return {
|
9630
|
-
cueHandler:
|
9486
|
+
cueHandler: Cues,
|
9631
9487
|
// used by timeline-controller
|
9632
9488
|
enableWebVTT: false,
|
9633
9489
|
// used by timeline-controller
|
@@ -9658,7 +9514,7 @@ function timelineConfig() {
|
|
9658
9514
|
/**
|
9659
9515
|
* @ignore
|
9660
9516
|
*/
|
9661
|
-
function mergeConfig(defaultConfig, userConfig
|
9517
|
+
function mergeConfig(defaultConfig, userConfig) {
|
9662
9518
|
if ((userConfig.liveSyncDurationCount || userConfig.liveMaxLatencyDurationCount) && (userConfig.liveSyncDuration || userConfig.liveMaxLatencyDuration)) {
|
9663
9519
|
throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration");
|
9664
9520
|
}
|
@@ -9728,7 +9584,7 @@ function deepCpy(obj) {
|
|
9728
9584
|
/**
|
9729
9585
|
* @ignore
|
9730
9586
|
*/
|
9731
|
-
function enableStreamingMode(config
|
9587
|
+
function enableStreamingMode(config) {
|
9732
9588
|
const currentLoader = config.loader;
|
9733
9589
|
if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
|
9734
9590
|
// If a developer has configured their own loader, respect that choice
|
@@ -9745,9 +9601,10 @@ function enableStreamingMode(config, logger) {
|
|
9745
9601
|
}
|
9746
9602
|
}
|
9747
9603
|
|
9604
|
+
let chromeOrFirefox;
|
9748
9605
|
class LevelController extends BasePlaylistController {
|
9749
9606
|
constructor(hls, contentSteeringController) {
|
9750
|
-
super(hls, 'level-controller');
|
9607
|
+
super(hls, '[level-controller]');
|
9751
9608
|
this._levels = [];
|
9752
9609
|
this._firstLevel = -1;
|
9753
9610
|
this._maxAutoLevel = -1;
|
@@ -9818,15 +9675,23 @@ class LevelController extends BasePlaylistController {
|
|
9818
9675
|
let videoCodecFound = false;
|
9819
9676
|
let audioCodecFound = false;
|
9820
9677
|
data.levels.forEach(levelParsed => {
|
9821
|
-
var _videoCodec;
|
9678
|
+
var _audioCodec, _videoCodec;
|
9822
9679
|
const attributes = levelParsed.attrs;
|
9680
|
+
|
9681
|
+
// erase audio codec info if browser does not support mp4a.40.34.
|
9682
|
+
// demuxer will autodetect codec and fallback to mpeg/audio
|
9823
9683
|
let {
|
9824
9684
|
audioCodec,
|
9825
9685
|
videoCodec
|
9826
9686
|
} = levelParsed;
|
9687
|
+
if (((_audioCodec = audioCodec) == null ? void 0 : _audioCodec.indexOf('mp4a.40.34')) !== -1) {
|
9688
|
+
chromeOrFirefox || (chromeOrFirefox = /chrome|firefox/i.test(navigator.userAgent));
|
9689
|
+
if (chromeOrFirefox) {
|
9690
|
+
levelParsed.audioCodec = audioCodec = undefined;
|
9691
|
+
}
|
9692
|
+
}
|
9827
9693
|
if (audioCodec) {
|
9828
|
-
|
9829
|
-
levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource) || undefined;
|
9694
|
+
levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource);
|
9830
9695
|
}
|
9831
9696
|
if (((_videoCodec = videoCodec) == null ? void 0 : _videoCodec.indexOf('avc1')) === 0) {
|
9832
9697
|
videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
|
@@ -10168,12 +10033,7 @@ class LevelController extends BasePlaylistController {
|
|
10168
10033
|
if (curLevel.fragmentError === 0) {
|
10169
10034
|
curLevel.loadError = 0;
|
10170
10035
|
}
|
10171
|
-
|
10172
|
-
let previousDetails = curLevel.details;
|
10173
|
-
if (previousDetails === data.details && previousDetails.advanced) {
|
10174
|
-
previousDetails = undefined;
|
10175
|
-
}
|
10176
|
-
this.playlistLoaded(level, data, previousDetails);
|
10036
|
+
this.playlistLoaded(level, data, curLevel.details);
|
10177
10037
|
} else if ((_data$deliveryDirecti2 = data.deliveryDirectives) != null && _data$deliveryDirecti2.skip) {
|
10178
10038
|
// received a delta playlist update that cannot be merged
|
10179
10039
|
details.deltaUpdateFailed = true;
|
@@ -10351,16 +10211,13 @@ class FragmentTracker {
|
|
10351
10211
|
* If not found any Fragment, return null
|
10352
10212
|
*/
|
10353
10213
|
getBufferedFrag(position, levelType) {
|
10354
|
-
return this.getFragAtPos(position, levelType, true);
|
10355
|
-
}
|
10356
|
-
getFragAtPos(position, levelType, buffered) {
|
10357
10214
|
const {
|
10358
10215
|
fragments
|
10359
10216
|
} = this;
|
10360
10217
|
const keys = Object.keys(fragments);
|
10361
10218
|
for (let i = keys.length; i--;) {
|
10362
10219
|
const fragmentEntity = fragments[keys[i]];
|
10363
|
-
if ((fragmentEntity == null ? void 0 : fragmentEntity.body.type) === levelType &&
|
10220
|
+
if ((fragmentEntity == null ? void 0 : fragmentEntity.body.type) === levelType && fragmentEntity.buffered) {
|
10364
10221
|
const frag = fragmentEntity.body;
|
10365
10222
|
if (frag.start <= position && position <= frag.end) {
|
10366
10223
|
return frag;
|
@@ -10615,8 +10472,7 @@ class FragmentTracker {
|
|
10615
10472
|
const {
|
10616
10473
|
frag,
|
10617
10474
|
part,
|
10618
|
-
timeRanges
|
10619
|
-
type
|
10475
|
+
timeRanges
|
10620
10476
|
} = data;
|
10621
10477
|
if (frag.sn === 'initSegment') {
|
10622
10478
|
return;
|
@@ -10631,8 +10487,10 @@ class FragmentTracker {
|
|
10631
10487
|
}
|
10632
10488
|
// Store the latest timeRanges loaded in the buffer
|
10633
10489
|
this.timeRanges = timeRanges;
|
10634
|
-
|
10635
|
-
|
10490
|
+
Object.keys(timeRanges).forEach(elementaryStream => {
|
10491
|
+
const timeRange = timeRanges[elementaryStream];
|
10492
|
+
this.detectEvictedFragments(elementaryStream, timeRange, playlistType, part);
|
10493
|
+
});
|
10636
10494
|
}
|
10637
10495
|
onFragBuffered(event, data) {
|
10638
10496
|
this.detectPartialFragments(data);
|
@@ -10961,8 +10819,8 @@ function createLoaderContext(frag, part = null) {
|
|
10961
10819
|
var _frag$decryptdata;
|
10962
10820
|
let byteRangeStart = start;
|
10963
10821
|
let byteRangeEnd = end;
|
10964
|
-
if (frag.sn === 'initSegment' &&
|
10965
|
-
// MAP segment encrypted with method 'AES-128'
|
10822
|
+
if (frag.sn === 'initSegment' && ((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method) === 'AES-128') {
|
10823
|
+
// MAP segment encrypted with method 'AES-128', when served with HTTP Range,
|
10966
10824
|
// has the unencrypted size specified in the range.
|
10967
10825
|
// Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
|
10968
10826
|
const fragmentLen = end - start;
|
@@ -10995,9 +10853,6 @@ function createGapLoadError(frag, part) {
|
|
10995
10853
|
(part ? part : frag).stats.aborted = true;
|
10996
10854
|
return new LoadError(errorData);
|
10997
10855
|
}
|
10998
|
-
function isMethodFullSegmentAesCbc(method) {
|
10999
|
-
return method === 'AES-128' || method === 'AES-256';
|
11000
|
-
}
|
11001
10856
|
class LoadError extends Error {
|
11002
10857
|
constructor(data) {
|
11003
10858
|
super(data.error.message);
|
@@ -11143,8 +10998,6 @@ class KeyLoader {
|
|
11143
10998
|
}
|
11144
10999
|
return this.loadKeyEME(keyInfo, frag);
|
11145
11000
|
case 'AES-128':
|
11146
|
-
case 'AES-256':
|
11147
|
-
case 'AES-256-CTR':
|
11148
11001
|
return this.loadKeyHTTP(keyInfo, frag);
|
11149
11002
|
default:
|
11150
11003
|
return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`)));
|
@@ -11280,9 +11133,8 @@ class KeyLoader {
|
|
11280
11133
|
* we are limiting the task execution per call stack to exactly one, but scheduling/post-poning further
|
11281
11134
|
* task processing on the next main loop iteration (also known as "next tick" in the Node/JS runtime lingo).
|
11282
11135
|
*/
|
11283
|
-
class TaskLoop
|
11284
|
-
constructor(
|
11285
|
-
super(label, logger);
|
11136
|
+
class TaskLoop {
|
11137
|
+
constructor() {
|
11286
11138
|
this._boundTick = void 0;
|
11287
11139
|
this._tickTimer = null;
|
11288
11140
|
this._tickInterval = null;
|
@@ -11550,61 +11402,33 @@ function alignMediaPlaylistByPDT(details, refDetails) {
|
|
11550
11402
|
}
|
11551
11403
|
|
11552
11404
|
class AESCrypto {
|
11553
|
-
constructor(subtle, iv
|
11405
|
+
constructor(subtle, iv) {
|
11554
11406
|
this.subtle = void 0;
|
11555
11407
|
this.aesIV = void 0;
|
11556
|
-
this.aesMode = void 0;
|
11557
11408
|
this.subtle = subtle;
|
11558
11409
|
this.aesIV = iv;
|
11559
|
-
this.aesMode = aesMode;
|
11560
11410
|
}
|
11561
11411
|
decrypt(data, key) {
|
11562
|
-
|
11563
|
-
|
11564
|
-
|
11565
|
-
|
11566
|
-
iv: this.aesIV
|
11567
|
-
}, key, data);
|
11568
|
-
case DecrypterAesMode.ctr:
|
11569
|
-
return this.subtle.decrypt({
|
11570
|
-
name: 'AES-CTR',
|
11571
|
-
counter: this.aesIV,
|
11572
|
-
length: 64
|
11573
|
-
},
|
11574
|
-
//64 : NIST SP800-38A standard suggests that the counter should occupy half of the counter block
|
11575
|
-
key, data);
|
11576
|
-
default:
|
11577
|
-
throw new Error(`[AESCrypto] invalid aes mode ${this.aesMode}`);
|
11578
|
-
}
|
11412
|
+
return this.subtle.decrypt({
|
11413
|
+
name: 'AES-CBC',
|
11414
|
+
iv: this.aesIV
|
11415
|
+
}, key, data);
|
11579
11416
|
}
|
11580
11417
|
}
|
11581
11418
|
|
11582
11419
|
class FastAESKey {
|
11583
|
-
constructor(subtle, key
|
11420
|
+
constructor(subtle, key) {
|
11584
11421
|
this.subtle = void 0;
|
11585
11422
|
this.key = void 0;
|
11586
|
-
this.aesMode = void 0;
|
11587
11423
|
this.subtle = subtle;
|
11588
11424
|
this.key = key;
|
11589
|
-
this.aesMode = aesMode;
|
11590
11425
|
}
|
11591
11426
|
expandKey() {
|
11592
|
-
const subtleAlgoName = getSubtleAlgoName(this.aesMode);
|
11593
11427
|
return this.subtle.importKey('raw', this.key, {
|
11594
|
-
name:
|
11428
|
+
name: 'AES-CBC'
|
11595
11429
|
}, false, ['encrypt', 'decrypt']);
|
11596
11430
|
}
|
11597
11431
|
}
|
11598
|
-
function getSubtleAlgoName(aesMode) {
|
11599
|
-
switch (aesMode) {
|
11600
|
-
case DecrypterAesMode.cbc:
|
11601
|
-
return 'AES-CBC';
|
11602
|
-
case DecrypterAesMode.ctr:
|
11603
|
-
return 'AES-CTR';
|
11604
|
-
default:
|
11605
|
-
throw new Error(`[FastAESKey] invalid aes mode ${aesMode}`);
|
11606
|
-
}
|
11607
|
-
}
|
11608
11432
|
|
11609
11433
|
// PKCS7
|
11610
11434
|
function removePadding(array) {
|
@@ -11854,8 +11678,7 @@ class Decrypter {
|
|
11854
11678
|
this.currentIV = null;
|
11855
11679
|
this.currentResult = null;
|
11856
11680
|
this.useSoftware = void 0;
|
11857
|
-
this.
|
11858
|
-
this.enableSoftwareAES = config.enableSoftwareAES;
|
11681
|
+
this.useSoftware = config.enableSoftwareAES;
|
11859
11682
|
this.removePKCS7Padding = removePKCS7Padding;
|
11860
11683
|
// built in decryptor expects PKCS7 padding
|
11861
11684
|
if (removePKCS7Padding) {
|
@@ -11868,7 +11691,9 @@ class Decrypter {
|
|
11868
11691
|
/* no-op */
|
11869
11692
|
}
|
11870
11693
|
}
|
11871
|
-
|
11694
|
+
if (this.subtle === null) {
|
11695
|
+
this.useSoftware = true;
|
11696
|
+
}
|
11872
11697
|
}
|
11873
11698
|
destroy() {
|
11874
11699
|
this.subtle = null;
|
@@ -11906,10 +11731,10 @@ class Decrypter {
|
|
11906
11731
|
this.softwareDecrypter = null;
|
11907
11732
|
}
|
11908
11733
|
}
|
11909
|
-
decrypt(data, key, iv
|
11734
|
+
decrypt(data, key, iv) {
|
11910
11735
|
if (this.useSoftware) {
|
11911
11736
|
return new Promise((resolve, reject) => {
|
11912
|
-
this.softwareDecrypt(new Uint8Array(data), key, iv
|
11737
|
+
this.softwareDecrypt(new Uint8Array(data), key, iv);
|
11913
11738
|
const decryptResult = this.flush();
|
11914
11739
|
if (decryptResult) {
|
11915
11740
|
resolve(decryptResult.buffer);
|
@@ -11918,21 +11743,17 @@ class Decrypter {
|
|
11918
11743
|
}
|
11919
11744
|
});
|
11920
11745
|
}
|
11921
|
-
return this.webCryptoDecrypt(new Uint8Array(data), key, iv
|
11746
|
+
return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
|
11922
11747
|
}
|
11923
11748
|
|
11924
11749
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
11925
11750
|
// data is handled in the flush() call
|
11926
|
-
softwareDecrypt(data, key, iv
|
11751
|
+
softwareDecrypt(data, key, iv) {
|
11927
11752
|
const {
|
11928
11753
|
currentIV,
|
11929
11754
|
currentResult,
|
11930
11755
|
remainderData
|
11931
11756
|
} = this;
|
11932
|
-
if (aesMode !== DecrypterAesMode.cbc || key.byteLength !== 16) {
|
11933
|
-
logger.warn('SoftwareDecrypt: can only handle AES-128-CBC');
|
11934
|
-
return null;
|
11935
|
-
}
|
11936
11757
|
this.logOnce('JS AES decrypt');
|
11937
11758
|
// The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
|
11938
11759
|
// This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached
|
@@ -11965,11 +11786,11 @@ class Decrypter {
|
|
11965
11786
|
}
|
11966
11787
|
return result;
|
11967
11788
|
}
|
11968
|
-
webCryptoDecrypt(data, key, iv
|
11789
|
+
webCryptoDecrypt(data, key, iv) {
|
11969
11790
|
const subtle = this.subtle;
|
11970
11791
|
if (this.key !== key || !this.fastAesKey) {
|
11971
11792
|
this.key = key;
|
11972
|
-
this.fastAesKey = new FastAESKey(subtle, key
|
11793
|
+
this.fastAesKey = new FastAESKey(subtle, key);
|
11973
11794
|
}
|
11974
11795
|
return this.fastAesKey.expandKey().then(aesKey => {
|
11975
11796
|
// decrypt using web crypto
|
@@ -11977,25 +11798,22 @@ class Decrypter {
|
|
11977
11798
|
return Promise.reject(new Error('web crypto not initialized'));
|
11978
11799
|
}
|
11979
11800
|
this.logOnce('WebCrypto AES decrypt');
|
11980
|
-
const crypto = new AESCrypto(subtle, new Uint8Array(iv)
|
11801
|
+
const crypto = new AESCrypto(subtle, new Uint8Array(iv));
|
11981
11802
|
return crypto.decrypt(data.buffer, aesKey);
|
11982
11803
|
}).catch(err => {
|
11983
11804
|
logger.warn(`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`);
|
11984
|
-
return this.onWebCryptoError(data, key, iv
|
11805
|
+
return this.onWebCryptoError(data, key, iv);
|
11985
11806
|
});
|
11986
11807
|
}
|
11987
|
-
onWebCryptoError(data, key, iv
|
11988
|
-
|
11989
|
-
|
11990
|
-
|
11991
|
-
|
11992
|
-
|
11993
|
-
|
11994
|
-
if (decryptResult) {
|
11995
|
-
return decryptResult.buffer;
|
11996
|
-
}
|
11808
|
+
onWebCryptoError(data, key, iv) {
|
11809
|
+
this.useSoftware = true;
|
11810
|
+
this.logEnabled = true;
|
11811
|
+
this.softwareDecrypt(data, key, iv);
|
11812
|
+
const decryptResult = this.flush();
|
11813
|
+
if (decryptResult) {
|
11814
|
+
return decryptResult.buffer;
|
11997
11815
|
}
|
11998
|
-
throw new Error('WebCrypto
|
11816
|
+
throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data');
|
11999
11817
|
}
|
12000
11818
|
getValidChunk(data) {
|
12001
11819
|
let currentChunk = data;
|
@@ -12046,7 +11864,7 @@ const State = {
|
|
12046
11864
|
};
|
12047
11865
|
class BaseStreamController extends TaskLoop {
|
12048
11866
|
constructor(hls, fragmentTracker, keyLoader, logPrefix, playlistType) {
|
12049
|
-
super(
|
11867
|
+
super();
|
12050
11868
|
this.hls = void 0;
|
12051
11869
|
this.fragPrevious = null;
|
12052
11870
|
this.fragCurrent = null;
|
@@ -12071,98 +11889,22 @@ class BaseStreamController extends TaskLoop {
|
|
12071
11889
|
this.startFragRequested = false;
|
12072
11890
|
this.decrypter = void 0;
|
12073
11891
|
this.initPTS = [];
|
12074
|
-
this.
|
12075
|
-
this.
|
12076
|
-
this.
|
12077
|
-
|
12078
|
-
|
12079
|
-
fragCurrent,
|
12080
|
-
media,
|
12081
|
-
mediaBuffer,
|
12082
|
-
state
|
12083
|
-
} = this;
|
12084
|
-
const currentTime = media ? media.currentTime : 0;
|
12085
|
-
const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
|
12086
|
-
this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
|
12087
|
-
if (this.state === State.ENDED) {
|
12088
|
-
this.resetLoadingState();
|
12089
|
-
} else if (fragCurrent) {
|
12090
|
-
// Seeking while frag load is in progress
|
12091
|
-
const tolerance = config.maxFragLookUpTolerance;
|
12092
|
-
const fragStartOffset = fragCurrent.start - tolerance;
|
12093
|
-
const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
|
12094
|
-
// if seeking out of buffered range or into new one
|
12095
|
-
if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
|
12096
|
-
const pastFragment = currentTime > fragEndOffset;
|
12097
|
-
// if the seek position is outside the current fragment range
|
12098
|
-
if (currentTime < fragStartOffset || pastFragment) {
|
12099
|
-
if (pastFragment && fragCurrent.loader) {
|
12100
|
-
this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
|
12101
|
-
fragCurrent.abortRequests();
|
12102
|
-
this.resetLoadingState();
|
12103
|
-
}
|
12104
|
-
this.fragPrevious = null;
|
12105
|
-
}
|
12106
|
-
}
|
12107
|
-
}
|
12108
|
-
if (media) {
|
12109
|
-
// Remove gap fragments
|
12110
|
-
this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
|
12111
|
-
this.lastCurrentTime = currentTime;
|
12112
|
-
if (!this.loadingParts) {
|
12113
|
-
const bufferEnd = Math.max(bufferInfo.end, currentTime);
|
12114
|
-
const shouldLoadParts = this.shouldLoadParts(this.getLevelDetails(), bufferEnd);
|
12115
|
-
if (shouldLoadParts) {
|
12116
|
-
this.log(`LL-Part loading ON after seeking to ${currentTime.toFixed(2)} with buffer @${bufferEnd.toFixed(2)}`);
|
12117
|
-
this.loadingParts = shouldLoadParts;
|
12118
|
-
}
|
12119
|
-
}
|
12120
|
-
}
|
12121
|
-
|
12122
|
-
// in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
|
12123
|
-
if (!this.loadedmetadata && !bufferInfo.len) {
|
12124
|
-
this.nextLoadPosition = this.startPosition = currentTime;
|
12125
|
-
}
|
12126
|
-
|
12127
|
-
// Async tick to speed up processing
|
12128
|
-
this.tickImmediate();
|
12129
|
-
};
|
12130
|
-
this.onMediaEnded = () => {
|
12131
|
-
// reset startPosition and lastCurrentTime to restart playback @ stream beginning
|
12132
|
-
this.startPosition = this.lastCurrentTime = 0;
|
12133
|
-
if (this.playlistType === PlaylistLevelType.MAIN) {
|
12134
|
-
this.hls.trigger(Events.MEDIA_ENDED, {
|
12135
|
-
stalled: false
|
12136
|
-
});
|
12137
|
-
}
|
12138
|
-
};
|
11892
|
+
this.onvseeking = null;
|
11893
|
+
this.onvended = null;
|
11894
|
+
this.logPrefix = '';
|
11895
|
+
this.log = void 0;
|
11896
|
+
this.warn = void 0;
|
12139
11897
|
this.playlistType = playlistType;
|
11898
|
+
this.logPrefix = logPrefix;
|
11899
|
+
this.log = logger.log.bind(logger, `${logPrefix}:`);
|
11900
|
+
this.warn = logger.warn.bind(logger, `${logPrefix}:`);
|
12140
11901
|
this.hls = hls;
|
12141
11902
|
this.fragmentLoader = new FragmentLoader(hls.config);
|
12142
11903
|
this.keyLoader = keyLoader;
|
12143
11904
|
this.fragmentTracker = fragmentTracker;
|
12144
11905
|
this.config = hls.config;
|
12145
11906
|
this.decrypter = new Decrypter(hls.config);
|
12146
|
-
}
|
12147
|
-
registerListeners() {
|
12148
|
-
const {
|
12149
|
-
hls
|
12150
|
-
} = this;
|
12151
|
-
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
12152
|
-
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
12153
|
-
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
12154
11907
|
hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
12155
|
-
hls.on(Events.ERROR, this.onError, this);
|
12156
|
-
}
|
12157
|
-
unregisterListeners() {
|
12158
|
-
const {
|
12159
|
-
hls
|
12160
|
-
} = this;
|
12161
|
-
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
12162
|
-
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
12163
|
-
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
12164
|
-
hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
12165
|
-
hls.off(Events.ERROR, this.onError, this);
|
12166
11908
|
}
|
12167
11909
|
doTick() {
|
12168
11910
|
this.onTickEnd();
|
@@ -12186,12 +11928,6 @@ class BaseStreamController extends TaskLoop {
|
|
12186
11928
|
this.clearNextTick();
|
12187
11929
|
this.state = State.STOPPED;
|
12188
11930
|
}
|
12189
|
-
pauseBuffering() {
|
12190
|
-
this.buffering = false;
|
12191
|
-
}
|
12192
|
-
resumeBuffering() {
|
12193
|
-
this.buffering = true;
|
12194
|
-
}
|
12195
11931
|
_streamEnded(bufferInfo, levelDetails) {
|
12196
11932
|
// If playlist is live, there is another buffered range after the current range, nothing buffered, media is detached,
|
12197
11933
|
// of nothing loading/loaded return false
|
@@ -12222,8 +11958,10 @@ class BaseStreamController extends TaskLoop {
|
|
12222
11958
|
}
|
12223
11959
|
onMediaAttached(event, data) {
|
12224
11960
|
const media = this.media = this.mediaBuffer = data.media;
|
12225
|
-
|
12226
|
-
|
11961
|
+
this.onvseeking = this.onMediaSeeking.bind(this);
|
11962
|
+
this.onvended = this.onMediaEnded.bind(this);
|
11963
|
+
media.addEventListener('seeking', this.onvseeking);
|
11964
|
+
media.addEventListener('ended', this.onvended);
|
12227
11965
|
const config = this.config;
|
12228
11966
|
if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
|
12229
11967
|
this.startLoad(config.startPosition);
|
@@ -12237,9 +11975,10 @@ class BaseStreamController extends TaskLoop {
|
|
12237
11975
|
}
|
12238
11976
|
|
12239
11977
|
// remove video listeners
|
12240
|
-
if (media) {
|
12241
|
-
media.removeEventListener('seeking', this.
|
12242
|
-
media.removeEventListener('ended', this.
|
11978
|
+
if (media && this.onvseeking && this.onvended) {
|
11979
|
+
media.removeEventListener('seeking', this.onvseeking);
|
11980
|
+
media.removeEventListener('ended', this.onvended);
|
11981
|
+
this.onvseeking = this.onvended = null;
|
12243
11982
|
}
|
12244
11983
|
if (this.keyLoader) {
|
12245
11984
|
this.keyLoader.detach();
|
@@ -12249,8 +11988,56 @@ class BaseStreamController extends TaskLoop {
|
|
12249
11988
|
this.fragmentTracker.removeAllFragments();
|
12250
11989
|
this.stopLoad();
|
12251
11990
|
}
|
12252
|
-
|
12253
|
-
|
11991
|
+
onMediaSeeking() {
|
11992
|
+
const {
|
11993
|
+
config,
|
11994
|
+
fragCurrent,
|
11995
|
+
media,
|
11996
|
+
mediaBuffer,
|
11997
|
+
state
|
11998
|
+
} = this;
|
11999
|
+
const currentTime = media ? media.currentTime : 0;
|
12000
|
+
const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
|
12001
|
+
this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
|
12002
|
+
if (this.state === State.ENDED) {
|
12003
|
+
this.resetLoadingState();
|
12004
|
+
} else if (fragCurrent) {
|
12005
|
+
// Seeking while frag load is in progress
|
12006
|
+
const tolerance = config.maxFragLookUpTolerance;
|
12007
|
+
const fragStartOffset = fragCurrent.start - tolerance;
|
12008
|
+
const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
|
12009
|
+
// if seeking out of buffered range or into new one
|
12010
|
+
if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
|
12011
|
+
const pastFragment = currentTime > fragEndOffset;
|
12012
|
+
// if the seek position is outside the current fragment range
|
12013
|
+
if (currentTime < fragStartOffset || pastFragment) {
|
12014
|
+
if (pastFragment && fragCurrent.loader) {
|
12015
|
+
this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
|
12016
|
+
fragCurrent.abortRequests();
|
12017
|
+
this.resetLoadingState();
|
12018
|
+
}
|
12019
|
+
this.fragPrevious = null;
|
12020
|
+
}
|
12021
|
+
}
|
12022
|
+
}
|
12023
|
+
if (media) {
|
12024
|
+
// Remove gap fragments
|
12025
|
+
this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
|
12026
|
+
this.lastCurrentTime = currentTime;
|
12027
|
+
}
|
12028
|
+
|
12029
|
+
// in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
|
12030
|
+
if (!this.loadedmetadata && !bufferInfo.len) {
|
12031
|
+
this.nextLoadPosition = this.startPosition = currentTime;
|
12032
|
+
}
|
12033
|
+
|
12034
|
+
// Async tick to speed up processing
|
12035
|
+
this.tickImmediate();
|
12036
|
+
}
|
12037
|
+
onMediaEnded() {
|
12038
|
+
// reset startPosition and lastCurrentTime to restart playback @ stream beginning
|
12039
|
+
this.startPosition = this.lastCurrentTime = 0;
|
12040
|
+
}
|
12254
12041
|
onManifestLoaded(event, data) {
|
12255
12042
|
this.startTimeOffset = data.startTimeOffset;
|
12256
12043
|
this.initPTS = [];
|
@@ -12260,7 +12047,7 @@ class BaseStreamController extends TaskLoop {
|
|
12260
12047
|
this.stopLoad();
|
12261
12048
|
super.onHandlerDestroying();
|
12262
12049
|
// @ts-ignore
|
12263
|
-
this.hls =
|
12050
|
+
this.hls = null;
|
12264
12051
|
}
|
12265
12052
|
onHandlerDestroyed() {
|
12266
12053
|
this.state = State.STOPPED;
|
@@ -12391,10 +12178,10 @@ class BaseStreamController extends TaskLoop {
|
|
12391
12178
|
const decryptData = frag.decryptdata;
|
12392
12179
|
|
12393
12180
|
// check to see if the payload needs to be decrypted
|
12394
|
-
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv &&
|
12181
|
+
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
|
12395
12182
|
const startTime = self.performance.now();
|
12396
12183
|
// decrypt init segment data
|
12397
|
-
return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer
|
12184
|
+
return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
|
12398
12185
|
hls.trigger(Events.ERROR, {
|
12399
12186
|
type: ErrorTypes.MEDIA_ERROR,
|
12400
12187
|
details: ErrorDetails.FRAG_DECRYPT_ERROR,
|
@@ -12506,7 +12293,7 @@ class BaseStreamController extends TaskLoop {
|
|
12506
12293
|
}
|
12507
12294
|
let keyLoadingPromise = null;
|
12508
12295
|
if (frag.encrypted && !((_frag$decryptdata = frag.decryptdata) != null && _frag$decryptdata.key)) {
|
12509
|
-
this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.
|
12296
|
+
this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.logPrefix === '[stream-controller]' ? 'level' : 'track'} ${frag.level}`);
|
12510
12297
|
this.state = State.KEY_LOADING;
|
12511
12298
|
this.fragCurrent = frag;
|
12512
12299
|
keyLoadingPromise = this.keyLoader.load(frag).then(keyLoadedData => {
|
@@ -12527,16 +12314,8 @@ class BaseStreamController extends TaskLoop {
|
|
12527
12314
|
} else if (!frag.encrypted && details.encryptedFragments.length) {
|
12528
12315
|
this.keyLoader.loadClear(frag, details.encryptedFragments);
|
12529
12316
|
}
|
12530
|
-
const fragPrevious = this.fragPrevious;
|
12531
|
-
if (frag.sn !== 'initSegment' && (!fragPrevious || frag.sn !== fragPrevious.sn)) {
|
12532
|
-
const shouldLoadParts = this.shouldLoadParts(level.details, frag.end);
|
12533
|
-
if (shouldLoadParts !== this.loadingParts) {
|
12534
|
-
this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} loading sn ${fragPrevious == null ? void 0 : fragPrevious.sn}->${frag.sn}`);
|
12535
|
-
this.loadingParts = shouldLoadParts;
|
12536
|
-
}
|
12537
|
-
}
|
12538
12317
|
targetBufferTime = Math.max(frag.start, targetBufferTime || 0);
|
12539
|
-
if (this.
|
12318
|
+
if (this.config.lowLatencyMode && frag.sn !== 'initSegment') {
|
12540
12319
|
const partList = details.partList;
|
12541
12320
|
if (partList && progressCallback) {
|
12542
12321
|
if (targetBufferTime > frag.end && details.fragmentHint) {
|
@@ -12545,7 +12324,7 @@ class BaseStreamController extends TaskLoop {
|
|
12545
12324
|
const partIndex = this.getNextPart(partList, frag, targetBufferTime);
|
12546
12325
|
if (partIndex > -1) {
|
12547
12326
|
const part = partList[partIndex];
|
12548
|
-
this.log(`Loading part sn: ${frag.sn} p: ${part.index} cc: ${frag.cc} of playlist [${details.startSN}-${details.endSN}] parts [0-${partIndex}-${partList.length - 1}] ${this.
|
12327
|
+
this.log(`Loading part sn: ${frag.sn} p: ${part.index} cc: ${frag.cc} of playlist [${details.startSN}-${details.endSN}] parts [0-${partIndex}-${partList.length - 1}] ${this.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
|
12549
12328
|
this.nextLoadPosition = part.start + part.duration;
|
12550
12329
|
this.state = State.FRAG_LOADING;
|
12551
12330
|
let _result;
|
@@ -12574,14 +12353,7 @@ class BaseStreamController extends TaskLoop {
|
|
12574
12353
|
}
|
12575
12354
|
}
|
12576
12355
|
}
|
12577
|
-
|
12578
|
-
this.log(`LL-Part loading OFF after next part miss @${targetBufferTime.toFixed(2)}`);
|
12579
|
-
this.loadingParts = false;
|
12580
|
-
} else if (!frag.url) {
|
12581
|
-
// Selected fragment hint for part but not loading parts
|
12582
|
-
return Promise.resolve(null);
|
12583
|
-
}
|
12584
|
-
this.log(`Loading fragment ${frag.sn} cc: ${frag.cc} ${details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''}${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
|
12356
|
+
this.log(`Loading fragment ${frag.sn} cc: ${frag.cc} ${details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''}${this.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
|
12585
12357
|
// Don't update nextLoadPosition for fragments which are not buffered
|
12586
12358
|
if (isFiniteNumber(frag.sn) && !this.bitrateTest) {
|
12587
12359
|
this.nextLoadPosition = frag.start + frag.duration;
|
@@ -12679,36 +12451,8 @@ class BaseStreamController extends TaskLoop {
|
|
12679
12451
|
if (part) {
|
12680
12452
|
part.stats.parsing.end = now;
|
12681
12453
|
}
|
12682
|
-
// See if part loading should be disabled/enabled based on buffer and playback position.
|
12683
|
-
if (frag.sn !== 'initSegment') {
|
12684
|
-
const levelDetails = this.getLevelDetails();
|
12685
|
-
const loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
|
12686
|
-
const shouldLoadParts = loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
|
12687
|
-
if (shouldLoadParts !== this.loadingParts) {
|
12688
|
-
this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} after parsing segment ending @${frag.end.toFixed(2)}`);
|
12689
|
-
this.loadingParts = shouldLoadParts;
|
12690
|
-
}
|
12691
|
-
}
|
12692
12454
|
this.updateLevelTiming(frag, part, level, chunkMeta.partial);
|
12693
12455
|
}
|
12694
|
-
shouldLoadParts(details, bufferEnd) {
|
12695
|
-
if (this.config.lowLatencyMode) {
|
12696
|
-
if (!details) {
|
12697
|
-
return this.loadingParts;
|
12698
|
-
}
|
12699
|
-
if (details != null && details.partList) {
|
12700
|
-
var _details$fragmentHint;
|
12701
|
-
// Buffer must be ahead of first part + duration of parts after last segment
|
12702
|
-
// and playback must be at or past segment adjacent to part list
|
12703
|
-
const firstPart = details.partList[0];
|
12704
|
-
const safePartStart = firstPart.end + (((_details$fragmentHint = details.fragmentHint) == null ? void 0 : _details$fragmentHint.duration) || 0);
|
12705
|
-
if (bufferEnd >= safePartStart && this.lastCurrentTime > firstPart.start - firstPart.fragment.duration) {
|
12706
|
-
return true;
|
12707
|
-
}
|
12708
|
-
}
|
12709
|
-
}
|
12710
|
-
return false;
|
12711
|
-
}
|
12712
12456
|
getCurrentContext(chunkMeta) {
|
12713
12457
|
const {
|
12714
12458
|
levels,
|
@@ -12809,7 +12553,7 @@ class BaseStreamController extends TaskLoop {
|
|
12809
12553
|
// Workaround flaw in getting forward buffer when maxBufferHole is smaller than gap at current pos
|
12810
12554
|
if (bufferInfo.len === 0 && bufferInfo.nextStart !== undefined) {
|
12811
12555
|
const bufferedFragAtPos = this.fragmentTracker.getBufferedFrag(pos, type);
|
12812
|
-
if (bufferedFragAtPos &&
|
12556
|
+
if (bufferedFragAtPos && bufferInfo.nextStart < bufferedFragAtPos.end) {
|
12813
12557
|
return BufferHelper.bufferInfo(bufferable, pos, Math.max(bufferInfo.nextStart, maxBufferHole));
|
12814
12558
|
}
|
12815
12559
|
}
|
@@ -12857,8 +12601,7 @@ class BaseStreamController extends TaskLoop {
|
|
12857
12601
|
config
|
12858
12602
|
} = this;
|
12859
12603
|
const start = fragments[0].start;
|
12860
|
-
|
12861
|
-
let frag = null;
|
12604
|
+
let frag;
|
12862
12605
|
if (levelDetails.live) {
|
12863
12606
|
const initialLiveManifestSize = config.initialLiveManifestSize;
|
12864
12607
|
if (fragLen < initialLiveManifestSize) {
|
@@ -12870,10 +12613,6 @@ class BaseStreamController extends TaskLoop {
|
|
12870
12613
|
// Do not load using live logic if the starting frag is requested - we want to use getFragmentAtPosition() so that
|
12871
12614
|
// we get the fragment matching that start time
|
12872
12615
|
if (!levelDetails.PTSKnown && !this.startFragRequested && this.startPosition === -1 || pos < start) {
|
12873
|
-
if (canLoadParts && !this.loadingParts) {
|
12874
|
-
this.log(`LL-Part loading ON for initial live fragment`);
|
12875
|
-
this.loadingParts = true;
|
12876
|
-
}
|
12877
12616
|
frag = this.getInitialLiveFragment(levelDetails, fragments);
|
12878
12617
|
this.startPosition = this.nextLoadPosition = frag ? this.hls.liveSyncPosition || frag.start : pos;
|
12879
12618
|
}
|
@@ -12884,7 +12623,7 @@ class BaseStreamController extends TaskLoop {
|
|
12884
12623
|
|
12885
12624
|
// If we haven't run into any special cases already, just load the fragment most closely matching the requested position
|
12886
12625
|
if (!frag) {
|
12887
|
-
const end =
|
12626
|
+
const end = config.lowLatencyMode ? levelDetails.partEnd : levelDetails.fragmentEnd;
|
12888
12627
|
frag = this.getFragmentAtPosition(pos, end, levelDetails);
|
12889
12628
|
}
|
12890
12629
|
return this.mapToInitFragWhenRequired(frag);
|
@@ -13006,7 +12745,7 @@ class BaseStreamController extends TaskLoop {
|
|
13006
12745
|
} = levelDetails;
|
13007
12746
|
const tolerance = config.maxFragLookUpTolerance;
|
13008
12747
|
const partList = levelDetails.partList;
|
13009
|
-
const loadingParts = !!(
|
12748
|
+
const loadingParts = !!(config.lowLatencyMode && partList != null && partList.length && fragmentHint);
|
13010
12749
|
if (loadingParts && fragmentHint && !this.bitrateTest) {
|
13011
12750
|
// Include incomplete fragment with parts at end
|
13012
12751
|
fragments = fragments.concat(fragmentHint);
|
@@ -13199,7 +12938,7 @@ class BaseStreamController extends TaskLoop {
|
|
13199
12938
|
errorAction.resolved = true;
|
13200
12939
|
}
|
13201
12940
|
} else {
|
13202
|
-
|
12941
|
+
logger.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
|
13203
12942
|
return;
|
13204
12943
|
}
|
13205
12944
|
} else if ((errorAction == null ? void 0 : errorAction.action) === NetworkErrorAction.SendAlternateToPenaltyBox) {
|
@@ -13267,9 +13006,7 @@ class BaseStreamController extends TaskLoop {
|
|
13267
13006
|
this.log('Reset loading state');
|
13268
13007
|
this.fragCurrent = null;
|
13269
13008
|
this.fragPrevious = null;
|
13270
|
-
|
13271
|
-
this.state = State.IDLE;
|
13272
|
-
}
|
13009
|
+
this.state = State.IDLE;
|
13273
13010
|
}
|
13274
13011
|
resetStartWhenNotLoaded(level) {
|
13275
13012
|
// if loadedmetadata is not set, it means that first frag request failed
|
@@ -13596,7 +13333,6 @@ const initPTSFn = (timestamp, timeOffset, initPTS) => {
|
|
13596
13333
|
*/
|
13597
13334
|
function getAudioConfig(observer, data, offset, audioCodec) {
|
13598
13335
|
let adtsObjectType;
|
13599
|
-
let originalAdtsObjectType;
|
13600
13336
|
let adtsExtensionSamplingIndex;
|
13601
13337
|
let adtsChannelConfig;
|
13602
13338
|
let config;
|
@@ -13604,7 +13340,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
13604
13340
|
const manifestCodec = audioCodec;
|
13605
13341
|
const adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
|
13606
13342
|
// byte 2
|
13607
|
-
adtsObjectType =
|
13343
|
+
adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
|
13608
13344
|
const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
|
13609
13345
|
if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
|
13610
13346
|
const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
|
@@ -13621,8 +13357,8 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
13621
13357
|
// byte 3
|
13622
13358
|
adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6;
|
13623
13359
|
logger.log(`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`);
|
13624
|
-
//
|
13625
|
-
if (/firefox
|
13360
|
+
// firefox: freq less than 24kHz = AAC SBR (HE-AAC)
|
13361
|
+
if (/firefox/i.test(userAgent)) {
|
13626
13362
|
if (adtsSamplingIndex >= 6) {
|
13627
13363
|
adtsObjectType = 5;
|
13628
13364
|
config = new Array(4);
|
@@ -13716,7 +13452,6 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
13716
13452
|
samplerate: adtsSamplingRates[adtsSamplingIndex],
|
13717
13453
|
channelCount: adtsChannelConfig,
|
13718
13454
|
codec: 'mp4a.40.' + adtsObjectType,
|
13719
|
-
parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
|
13720
13455
|
manifestCodec
|
13721
13456
|
};
|
13722
13457
|
}
|
@@ -13771,8 +13506,7 @@ function initTrackConfig(track, observer, data, offset, audioCodec) {
|
|
13771
13506
|
track.channelCount = config.channelCount;
|
13772
13507
|
track.codec = config.codec;
|
13773
13508
|
track.manifestCodec = config.manifestCodec;
|
13774
|
-
track.
|
13775
|
-
logger.log(`parsed codec:${track.parsedCodec}, codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
|
13509
|
+
logger.log(`parsed codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
|
13776
13510
|
}
|
13777
13511
|
}
|
13778
13512
|
function getFrameDuration(samplerate) {
|
@@ -14250,110 +13984,6 @@ class BaseVideoParser {
|
|
14250
13984
|
logger.log(VideoSample.pts + '/' + VideoSample.dts + ':' + VideoSample.debug);
|
14251
13985
|
}
|
14252
13986
|
}
|
14253
|
-
parseNALu(track, array) {
|
14254
|
-
const len = array.byteLength;
|
14255
|
-
let state = track.naluState || 0;
|
14256
|
-
const lastState = state;
|
14257
|
-
const units = [];
|
14258
|
-
let i = 0;
|
14259
|
-
let value;
|
14260
|
-
let overflow;
|
14261
|
-
let unitType;
|
14262
|
-
let lastUnitStart = -1;
|
14263
|
-
let lastUnitType = 0;
|
14264
|
-
// logger.log('PES:' + Hex.hexDump(array));
|
14265
|
-
|
14266
|
-
if (state === -1) {
|
14267
|
-
// special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
|
14268
|
-
lastUnitStart = 0;
|
14269
|
-
// NALu type is value read from offset 0
|
14270
|
-
lastUnitType = this.getNALuType(array, 0);
|
14271
|
-
state = 0;
|
14272
|
-
i = 1;
|
14273
|
-
}
|
14274
|
-
while (i < len) {
|
14275
|
-
value = array[i++];
|
14276
|
-
// optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
|
14277
|
-
if (!state) {
|
14278
|
-
state = value ? 0 : 1;
|
14279
|
-
continue;
|
14280
|
-
}
|
14281
|
-
if (state === 1) {
|
14282
|
-
state = value ? 0 : 2;
|
14283
|
-
continue;
|
14284
|
-
}
|
14285
|
-
// here we have state either equal to 2 or 3
|
14286
|
-
if (!value) {
|
14287
|
-
state = 3;
|
14288
|
-
} else if (value === 1) {
|
14289
|
-
overflow = i - state - 1;
|
14290
|
-
if (lastUnitStart >= 0) {
|
14291
|
-
const unit = {
|
14292
|
-
data: array.subarray(lastUnitStart, overflow),
|
14293
|
-
type: lastUnitType
|
14294
|
-
};
|
14295
|
-
// logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
|
14296
|
-
units.push(unit);
|
14297
|
-
} else {
|
14298
|
-
// lastUnitStart is undefined => this is the first start code found in this PES packet
|
14299
|
-
// first check if start code delimiter is overlapping between 2 PES packets,
|
14300
|
-
// ie it started in last packet (lastState not zero)
|
14301
|
-
// and ended at the beginning of this PES packet (i <= 4 - lastState)
|
14302
|
-
const lastUnit = this.getLastNalUnit(track.samples);
|
14303
|
-
if (lastUnit) {
|
14304
|
-
if (lastState && i <= 4 - lastState) {
|
14305
|
-
// start delimiter overlapping between PES packets
|
14306
|
-
// strip start delimiter bytes from the end of last NAL unit
|
14307
|
-
// check if lastUnit had a state different from zero
|
14308
|
-
if (lastUnit.state) {
|
14309
|
-
// strip last bytes
|
14310
|
-
lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
|
14311
|
-
}
|
14312
|
-
}
|
14313
|
-
// If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
|
14314
|
-
|
14315
|
-
if (overflow > 0) {
|
14316
|
-
// logger.log('first NALU found with overflow:' + overflow);
|
14317
|
-
lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
|
14318
|
-
lastUnit.state = 0;
|
14319
|
-
}
|
14320
|
-
}
|
14321
|
-
}
|
14322
|
-
// check if we can read unit type
|
14323
|
-
if (i < len) {
|
14324
|
-
unitType = this.getNALuType(array, i);
|
14325
|
-
// logger.log('find NALU @ offset:' + i + ',type:' + unitType);
|
14326
|
-
lastUnitStart = i;
|
14327
|
-
lastUnitType = unitType;
|
14328
|
-
state = 0;
|
14329
|
-
} else {
|
14330
|
-
// not enough byte to read unit type. let's read it on next PES parsing
|
14331
|
-
state = -1;
|
14332
|
-
}
|
14333
|
-
} else {
|
14334
|
-
state = 0;
|
14335
|
-
}
|
14336
|
-
}
|
14337
|
-
if (lastUnitStart >= 0 && state >= 0) {
|
14338
|
-
const unit = {
|
14339
|
-
data: array.subarray(lastUnitStart, len),
|
14340
|
-
type: lastUnitType,
|
14341
|
-
state: state
|
14342
|
-
};
|
14343
|
-
units.push(unit);
|
14344
|
-
// logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
|
14345
|
-
}
|
14346
|
-
// no NALu found
|
14347
|
-
if (units.length === 0) {
|
14348
|
-
// append pes.data to previous NAL unit
|
14349
|
-
const lastUnit = this.getLastNalUnit(track.samples);
|
14350
|
-
if (lastUnit) {
|
14351
|
-
lastUnit.data = appendUint8Array(lastUnit.data, array);
|
14352
|
-
}
|
14353
|
-
}
|
14354
|
-
track.naluState = state;
|
14355
|
-
return units;
|
14356
|
-
}
|
14357
13987
|
}
|
14358
13988
|
|
14359
13989
|
/**
|
@@ -14496,11 +14126,194 @@ class ExpGolomb {
|
|
14496
14126
|
readUInt() {
|
14497
14127
|
return this.readBits(32);
|
14498
14128
|
}
|
14129
|
+
|
14130
|
+
/**
|
14131
|
+
* Advance the ExpGolomb decoder past a scaling list. The scaling
|
14132
|
+
* list is optionally transmitted as part of a sequence parameter
|
14133
|
+
* set and is not relevant to transmuxing.
|
14134
|
+
* @param count the number of entries in this scaling list
|
14135
|
+
* @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
|
14136
|
+
*/
|
14137
|
+
skipScalingList(count) {
|
14138
|
+
let lastScale = 8;
|
14139
|
+
let nextScale = 8;
|
14140
|
+
let deltaScale;
|
14141
|
+
for (let j = 0; j < count; j++) {
|
14142
|
+
if (nextScale !== 0) {
|
14143
|
+
deltaScale = this.readEG();
|
14144
|
+
nextScale = (lastScale + deltaScale + 256) % 256;
|
14145
|
+
}
|
14146
|
+
lastScale = nextScale === 0 ? lastScale : nextScale;
|
14147
|
+
}
|
14148
|
+
}
|
14149
|
+
|
14150
|
+
/**
|
14151
|
+
* Read a sequence parameter set and return some interesting video
|
14152
|
+
* properties. A sequence parameter set is the H264 metadata that
|
14153
|
+
* describes the properties of upcoming video frames.
|
14154
|
+
* @returns an object with configuration parsed from the
|
14155
|
+
* sequence parameter set, including the dimensions of the
|
14156
|
+
* associated video frames.
|
14157
|
+
*/
|
14158
|
+
readSPS() {
|
14159
|
+
let frameCropLeftOffset = 0;
|
14160
|
+
let frameCropRightOffset = 0;
|
14161
|
+
let frameCropTopOffset = 0;
|
14162
|
+
let frameCropBottomOffset = 0;
|
14163
|
+
let numRefFramesInPicOrderCntCycle;
|
14164
|
+
let scalingListCount;
|
14165
|
+
let i;
|
14166
|
+
const readUByte = this.readUByte.bind(this);
|
14167
|
+
const readBits = this.readBits.bind(this);
|
14168
|
+
const readUEG = this.readUEG.bind(this);
|
14169
|
+
const readBoolean = this.readBoolean.bind(this);
|
14170
|
+
const skipBits = this.skipBits.bind(this);
|
14171
|
+
const skipEG = this.skipEG.bind(this);
|
14172
|
+
const skipUEG = this.skipUEG.bind(this);
|
14173
|
+
const skipScalingList = this.skipScalingList.bind(this);
|
14174
|
+
readUByte();
|
14175
|
+
const profileIdc = readUByte(); // profile_idc
|
14176
|
+
readBits(5); // profileCompat constraint_set[0-4]_flag, u(5)
|
14177
|
+
skipBits(3); // reserved_zero_3bits u(3),
|
14178
|
+
readUByte(); // level_idc u(8)
|
14179
|
+
skipUEG(); // seq_parameter_set_id
|
14180
|
+
// some profiles have more optional data we don't need
|
14181
|
+
if (profileIdc === 100 || profileIdc === 110 || profileIdc === 122 || profileIdc === 244 || profileIdc === 44 || profileIdc === 83 || profileIdc === 86 || profileIdc === 118 || profileIdc === 128) {
|
14182
|
+
const chromaFormatIdc = readUEG();
|
14183
|
+
if (chromaFormatIdc === 3) {
|
14184
|
+
skipBits(1);
|
14185
|
+
} // separate_colour_plane_flag
|
14186
|
+
|
14187
|
+
skipUEG(); // bit_depth_luma_minus8
|
14188
|
+
skipUEG(); // bit_depth_chroma_minus8
|
14189
|
+
skipBits(1); // qpprime_y_zero_transform_bypass_flag
|
14190
|
+
if (readBoolean()) {
|
14191
|
+
// seq_scaling_matrix_present_flag
|
14192
|
+
scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
|
14193
|
+
for (i = 0; i < scalingListCount; i++) {
|
14194
|
+
if (readBoolean()) {
|
14195
|
+
// seq_scaling_list_present_flag[ i ]
|
14196
|
+
if (i < 6) {
|
14197
|
+
skipScalingList(16);
|
14198
|
+
} else {
|
14199
|
+
skipScalingList(64);
|
14200
|
+
}
|
14201
|
+
}
|
14202
|
+
}
|
14203
|
+
}
|
14204
|
+
}
|
14205
|
+
skipUEG(); // log2_max_frame_num_minus4
|
14206
|
+
const picOrderCntType = readUEG();
|
14207
|
+
if (picOrderCntType === 0) {
|
14208
|
+
readUEG(); // log2_max_pic_order_cnt_lsb_minus4
|
14209
|
+
} else if (picOrderCntType === 1) {
|
14210
|
+
skipBits(1); // delta_pic_order_always_zero_flag
|
14211
|
+
skipEG(); // offset_for_non_ref_pic
|
14212
|
+
skipEG(); // offset_for_top_to_bottom_field
|
14213
|
+
numRefFramesInPicOrderCntCycle = readUEG();
|
14214
|
+
for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
|
14215
|
+
skipEG();
|
14216
|
+
} // offset_for_ref_frame[ i ]
|
14217
|
+
}
|
14218
|
+
skipUEG(); // max_num_ref_frames
|
14219
|
+
skipBits(1); // gaps_in_frame_num_value_allowed_flag
|
14220
|
+
const picWidthInMbsMinus1 = readUEG();
|
14221
|
+
const picHeightInMapUnitsMinus1 = readUEG();
|
14222
|
+
const frameMbsOnlyFlag = readBits(1);
|
14223
|
+
if (frameMbsOnlyFlag === 0) {
|
14224
|
+
skipBits(1);
|
14225
|
+
} // mb_adaptive_frame_field_flag
|
14226
|
+
|
14227
|
+
skipBits(1); // direct_8x8_inference_flag
|
14228
|
+
if (readBoolean()) {
|
14229
|
+
// frame_cropping_flag
|
14230
|
+
frameCropLeftOffset = readUEG();
|
14231
|
+
frameCropRightOffset = readUEG();
|
14232
|
+
frameCropTopOffset = readUEG();
|
14233
|
+
frameCropBottomOffset = readUEG();
|
14234
|
+
}
|
14235
|
+
let pixelRatio = [1, 1];
|
14236
|
+
if (readBoolean()) {
|
14237
|
+
// vui_parameters_present_flag
|
14238
|
+
if (readBoolean()) {
|
14239
|
+
// aspect_ratio_info_present_flag
|
14240
|
+
const aspectRatioIdc = readUByte();
|
14241
|
+
switch (aspectRatioIdc) {
|
14242
|
+
case 1:
|
14243
|
+
pixelRatio = [1, 1];
|
14244
|
+
break;
|
14245
|
+
case 2:
|
14246
|
+
pixelRatio = [12, 11];
|
14247
|
+
break;
|
14248
|
+
case 3:
|
14249
|
+
pixelRatio = [10, 11];
|
14250
|
+
break;
|
14251
|
+
case 4:
|
14252
|
+
pixelRatio = [16, 11];
|
14253
|
+
break;
|
14254
|
+
case 5:
|
14255
|
+
pixelRatio = [40, 33];
|
14256
|
+
break;
|
14257
|
+
case 6:
|
14258
|
+
pixelRatio = [24, 11];
|
14259
|
+
break;
|
14260
|
+
case 7:
|
14261
|
+
pixelRatio = [20, 11];
|
14262
|
+
break;
|
14263
|
+
case 8:
|
14264
|
+
pixelRatio = [32, 11];
|
14265
|
+
break;
|
14266
|
+
case 9:
|
14267
|
+
pixelRatio = [80, 33];
|
14268
|
+
break;
|
14269
|
+
case 10:
|
14270
|
+
pixelRatio = [18, 11];
|
14271
|
+
break;
|
14272
|
+
case 11:
|
14273
|
+
pixelRatio = [15, 11];
|
14274
|
+
break;
|
14275
|
+
case 12:
|
14276
|
+
pixelRatio = [64, 33];
|
14277
|
+
break;
|
14278
|
+
case 13:
|
14279
|
+
pixelRatio = [160, 99];
|
14280
|
+
break;
|
14281
|
+
case 14:
|
14282
|
+
pixelRatio = [4, 3];
|
14283
|
+
break;
|
14284
|
+
case 15:
|
14285
|
+
pixelRatio = [3, 2];
|
14286
|
+
break;
|
14287
|
+
case 16:
|
14288
|
+
pixelRatio = [2, 1];
|
14289
|
+
break;
|
14290
|
+
case 255:
|
14291
|
+
{
|
14292
|
+
pixelRatio = [readUByte() << 8 | readUByte(), readUByte() << 8 | readUByte()];
|
14293
|
+
break;
|
14294
|
+
}
|
14295
|
+
}
|
14296
|
+
}
|
14297
|
+
}
|
14298
|
+
return {
|
14299
|
+
width: Math.ceil((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2),
|
14300
|
+
height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - (frameMbsOnlyFlag ? 2 : 4) * (frameCropTopOffset + frameCropBottomOffset),
|
14301
|
+
pixelRatio: pixelRatio
|
14302
|
+
};
|
14303
|
+
}
|
14304
|
+
readSliceType() {
|
14305
|
+
// skip NALu type
|
14306
|
+
this.readUByte();
|
14307
|
+
// discard first_mb_in_slice
|
14308
|
+
this.readUEG();
|
14309
|
+
// return slice_type
|
14310
|
+
return this.readUEG();
|
14311
|
+
}
|
14499
14312
|
}
|
14500
14313
|
|
14501
14314
|
class AvcVideoParser extends BaseVideoParser {
|
14502
|
-
|
14503
|
-
const units = this.
|
14315
|
+
parseAVCPES(track, textTrack, pes, last, duration) {
|
14316
|
+
const units = this.parseAVCNALu(track, pes.data);
|
14504
14317
|
let VideoSample = this.VideoSample;
|
14505
14318
|
let push;
|
14506
14319
|
let spsfound = false;
|
@@ -14525,7 +14338,7 @@ class AvcVideoParser extends BaseVideoParser {
|
|
14525
14338
|
// only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
|
14526
14339
|
if (spsfound && data.length > 4) {
|
14527
14340
|
// retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
|
14528
|
-
const sliceType =
|
14341
|
+
const sliceType = new ExpGolomb(data).readSliceType();
|
14529
14342
|
// 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
|
14530
14343
|
// SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
|
14531
14344
|
// An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
|
@@ -14579,7 +14392,8 @@ class AvcVideoParser extends BaseVideoParser {
|
|
14579
14392
|
push = true;
|
14580
14393
|
spsfound = true;
|
14581
14394
|
const sps = unit.data;
|
14582
|
-
const
|
14395
|
+
const expGolombDecoder = new ExpGolomb(sps);
|
14396
|
+
const config = expGolombDecoder.readSPS();
|
14583
14397
|
if (!track.sps || track.width !== config.width || track.height !== config.height || ((_track$pixelRatio = track.pixelRatio) == null ? void 0 : _track$pixelRatio[0]) !== config.pixelRatio[0] || ((_track$pixelRatio2 = track.pixelRatio) == null ? void 0 : _track$pixelRatio2[1]) !== config.pixelRatio[1]) {
|
14584
14398
|
track.width = config.width;
|
14585
14399
|
track.height = config.height;
|
@@ -14635,192 +14449,109 @@ class AvcVideoParser extends BaseVideoParser {
|
|
14635
14449
|
this.VideoSample = null;
|
14636
14450
|
}
|
14637
14451
|
}
|
14638
|
-
|
14639
|
-
|
14640
|
-
|
14641
|
-
|
14642
|
-
const
|
14643
|
-
|
14644
|
-
|
14645
|
-
|
14646
|
-
|
14647
|
-
|
14648
|
-
|
14649
|
-
|
14452
|
+
parseAVCNALu(track, array) {
|
14453
|
+
const len = array.byteLength;
|
14454
|
+
let state = track.naluState || 0;
|
14455
|
+
const lastState = state;
|
14456
|
+
const units = [];
|
14457
|
+
let i = 0;
|
14458
|
+
let value;
|
14459
|
+
let overflow;
|
14460
|
+
let unitType;
|
14461
|
+
let lastUnitStart = -1;
|
14462
|
+
let lastUnitType = 0;
|
14463
|
+
// logger.log('PES:' + Hex.hexDump(array));
|
14650
14464
|
|
14651
|
-
|
14652
|
-
|
14653
|
-
|
14654
|
-
|
14655
|
-
|
14656
|
-
|
14657
|
-
|
14658
|
-
let lastScale = 8;
|
14659
|
-
let nextScale = 8;
|
14660
|
-
let deltaScale;
|
14661
|
-
for (let j = 0; j < count; j++) {
|
14662
|
-
if (nextScale !== 0) {
|
14663
|
-
deltaScale = reader.readEG();
|
14664
|
-
nextScale = (lastScale + deltaScale + 256) % 256;
|
14665
|
-
}
|
14666
|
-
lastScale = nextScale === 0 ? lastScale : nextScale;
|
14465
|
+
if (state === -1) {
|
14466
|
+
// special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
|
14467
|
+
lastUnitStart = 0;
|
14468
|
+
// NALu type is value read from offset 0
|
14469
|
+
lastUnitType = array[0] & 0x1f;
|
14470
|
+
state = 0;
|
14471
|
+
i = 1;
|
14667
14472
|
}
|
14668
|
-
|
14669
|
-
|
14670
|
-
|
14671
|
-
|
14672
|
-
|
14673
|
-
|
14674
|
-
|
14675
|
-
|
14676
|
-
|
14677
|
-
|
14678
|
-
|
14679
|
-
|
14680
|
-
|
14681
|
-
|
14682
|
-
|
14683
|
-
|
14684
|
-
|
14685
|
-
|
14686
|
-
|
14687
|
-
|
14688
|
-
|
14689
|
-
|
14690
|
-
|
14691
|
-
|
14692
|
-
|
14693
|
-
|
14694
|
-
|
14695
|
-
|
14696
|
-
|
14697
|
-
|
14698
|
-
|
14699
|
-
|
14700
|
-
|
14701
|
-
|
14702
|
-
|
14703
|
-
|
14704
|
-
|
14705
|
-
|
14706
|
-
|
14473
|
+
while (i < len) {
|
14474
|
+
value = array[i++];
|
14475
|
+
// optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
|
14476
|
+
if (!state) {
|
14477
|
+
state = value ? 0 : 1;
|
14478
|
+
continue;
|
14479
|
+
}
|
14480
|
+
if (state === 1) {
|
14481
|
+
state = value ? 0 : 2;
|
14482
|
+
continue;
|
14483
|
+
}
|
14484
|
+
// here we have state either equal to 2 or 3
|
14485
|
+
if (!value) {
|
14486
|
+
state = 3;
|
14487
|
+
} else if (value === 1) {
|
14488
|
+
overflow = i - state - 1;
|
14489
|
+
if (lastUnitStart >= 0) {
|
14490
|
+
const unit = {
|
14491
|
+
data: array.subarray(lastUnitStart, overflow),
|
14492
|
+
type: lastUnitType
|
14493
|
+
};
|
14494
|
+
// logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
|
14495
|
+
units.push(unit);
|
14496
|
+
} else {
|
14497
|
+
// lastUnitStart is undefined => this is the first start code found in this PES packet
|
14498
|
+
// first check if start code delimiter is overlapping between 2 PES packets,
|
14499
|
+
// ie it started in last packet (lastState not zero)
|
14500
|
+
// and ended at the beginning of this PES packet (i <= 4 - lastState)
|
14501
|
+
const lastUnit = this.getLastNalUnit(track.samples);
|
14502
|
+
if (lastUnit) {
|
14503
|
+
if (lastState && i <= 4 - lastState) {
|
14504
|
+
// start delimiter overlapping between PES packets
|
14505
|
+
// strip start delimiter bytes from the end of last NAL unit
|
14506
|
+
// check if lastUnit had a state different from zero
|
14507
|
+
if (lastUnit.state) {
|
14508
|
+
// strip last bytes
|
14509
|
+
lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
|
14510
|
+
}
|
14511
|
+
}
|
14512
|
+
// If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
|
14707
14513
|
|
14708
|
-
|
14709
|
-
|
14710
|
-
|
14711
|
-
|
14712
|
-
// seq_scaling_matrix_present_flag
|
14713
|
-
scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
|
14714
|
-
for (i = 0; i < scalingListCount; i++) {
|
14715
|
-
if (readBoolean()) {
|
14716
|
-
// seq_scaling_list_present_flag[ i ]
|
14717
|
-
if (i < 6) {
|
14718
|
-
skipScalingList(16, eg);
|
14719
|
-
} else {
|
14720
|
-
skipScalingList(64, eg);
|
14514
|
+
if (overflow > 0) {
|
14515
|
+
// logger.log('first NALU found with overflow:' + overflow);
|
14516
|
+
lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
|
14517
|
+
lastUnit.state = 0;
|
14721
14518
|
}
|
14722
14519
|
}
|
14723
14520
|
}
|
14521
|
+
// check if we can read unit type
|
14522
|
+
if (i < len) {
|
14523
|
+
unitType = array[i] & 0x1f;
|
14524
|
+
// logger.log('find NALU @ offset:' + i + ',type:' + unitType);
|
14525
|
+
lastUnitStart = i;
|
14526
|
+
lastUnitType = unitType;
|
14527
|
+
state = 0;
|
14528
|
+
} else {
|
14529
|
+
// not enough byte to read unit type. let's read it on next PES parsing
|
14530
|
+
state = -1;
|
14531
|
+
}
|
14532
|
+
} else {
|
14533
|
+
state = 0;
|
14724
14534
|
}
|
14725
14535
|
}
|
14726
|
-
|
14727
|
-
|
14728
|
-
|
14729
|
-
|
14730
|
-
|
14731
|
-
|
14732
|
-
|
14733
|
-
|
14734
|
-
numRefFramesInPicOrderCntCycle = readUEG();
|
14735
|
-
for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
|
14736
|
-
skipEG();
|
14737
|
-
} // offset_for_ref_frame[ i ]
|
14738
|
-
}
|
14739
|
-
skipUEG(); // max_num_ref_frames
|
14740
|
-
skipBits(1); // gaps_in_frame_num_value_allowed_flag
|
14741
|
-
const picWidthInMbsMinus1 = readUEG();
|
14742
|
-
const picHeightInMapUnitsMinus1 = readUEG();
|
14743
|
-
const frameMbsOnlyFlag = readBits(1);
|
14744
|
-
if (frameMbsOnlyFlag === 0) {
|
14745
|
-
skipBits(1);
|
14746
|
-
} // mb_adaptive_frame_field_flag
|
14747
|
-
|
14748
|
-
skipBits(1); // direct_8x8_inference_flag
|
14749
|
-
if (readBoolean()) {
|
14750
|
-
// frame_cropping_flag
|
14751
|
-
frameCropLeftOffset = readUEG();
|
14752
|
-
frameCropRightOffset = readUEG();
|
14753
|
-
frameCropTopOffset = readUEG();
|
14754
|
-
frameCropBottomOffset = readUEG();
|
14536
|
+
if (lastUnitStart >= 0 && state >= 0) {
|
14537
|
+
const unit = {
|
14538
|
+
data: array.subarray(lastUnitStart, len),
|
14539
|
+
type: lastUnitType,
|
14540
|
+
state: state
|
14541
|
+
};
|
14542
|
+
units.push(unit);
|
14543
|
+
// logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
|
14755
14544
|
}
|
14756
|
-
|
14757
|
-
if (
|
14758
|
-
//
|
14759
|
-
|
14760
|
-
|
14761
|
-
|
14762
|
-
switch (aspectRatioIdc) {
|
14763
|
-
case 1:
|
14764
|
-
pixelRatio = [1, 1];
|
14765
|
-
break;
|
14766
|
-
case 2:
|
14767
|
-
pixelRatio = [12, 11];
|
14768
|
-
break;
|
14769
|
-
case 3:
|
14770
|
-
pixelRatio = [10, 11];
|
14771
|
-
break;
|
14772
|
-
case 4:
|
14773
|
-
pixelRatio = [16, 11];
|
14774
|
-
break;
|
14775
|
-
case 5:
|
14776
|
-
pixelRatio = [40, 33];
|
14777
|
-
break;
|
14778
|
-
case 6:
|
14779
|
-
pixelRatio = [24, 11];
|
14780
|
-
break;
|
14781
|
-
case 7:
|
14782
|
-
pixelRatio = [20, 11];
|
14783
|
-
break;
|
14784
|
-
case 8:
|
14785
|
-
pixelRatio = [32, 11];
|
14786
|
-
break;
|
14787
|
-
case 9:
|
14788
|
-
pixelRatio = [80, 33];
|
14789
|
-
break;
|
14790
|
-
case 10:
|
14791
|
-
pixelRatio = [18, 11];
|
14792
|
-
break;
|
14793
|
-
case 11:
|
14794
|
-
pixelRatio = [15, 11];
|
14795
|
-
break;
|
14796
|
-
case 12:
|
14797
|
-
pixelRatio = [64, 33];
|
14798
|
-
break;
|
14799
|
-
case 13:
|
14800
|
-
pixelRatio = [160, 99];
|
14801
|
-
break;
|
14802
|
-
case 14:
|
14803
|
-
pixelRatio = [4, 3];
|
14804
|
-
break;
|
14805
|
-
case 15:
|
14806
|
-
pixelRatio = [3, 2];
|
14807
|
-
break;
|
14808
|
-
case 16:
|
14809
|
-
pixelRatio = [2, 1];
|
14810
|
-
break;
|
14811
|
-
case 255:
|
14812
|
-
{
|
14813
|
-
pixelRatio = [readUByte() << 8 | readUByte(), readUByte() << 8 | readUByte()];
|
14814
|
-
break;
|
14815
|
-
}
|
14816
|
-
}
|
14545
|
+
// no NALu found
|
14546
|
+
if (units.length === 0) {
|
14547
|
+
// append pes.data to previous NAL unit
|
14548
|
+
const lastUnit = this.getLastNalUnit(track.samples);
|
14549
|
+
if (lastUnit) {
|
14550
|
+
lastUnit.data = appendUint8Array(lastUnit.data, array);
|
14817
14551
|
}
|
14818
14552
|
}
|
14819
|
-
|
14820
|
-
|
14821
|
-
height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - (frameMbsOnlyFlag ? 2 : 4) * (frameCropTopOffset + frameCropBottomOffset),
|
14822
|
-
pixelRatio: pixelRatio
|
14823
|
-
};
|
14553
|
+
track.naluState = state;
|
14554
|
+
return units;
|
14824
14555
|
}
|
14825
14556
|
}
|
14826
14557
|
|
@@ -14838,7 +14569,7 @@ class SampleAesDecrypter {
|
|
14838
14569
|
});
|
14839
14570
|
}
|
14840
14571
|
decryptBuffer(encryptedData) {
|
14841
|
-
return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer
|
14572
|
+
return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer);
|
14842
14573
|
}
|
14843
14574
|
|
14844
14575
|
// AAC - encrypt all full 16 bytes blocks starting from offset 16
|
@@ -14952,7 +14683,7 @@ class TSDemuxer {
|
|
14952
14683
|
this.observer = observer;
|
14953
14684
|
this.config = config;
|
14954
14685
|
this.typeSupported = typeSupported;
|
14955
|
-
this.videoParser =
|
14686
|
+
this.videoParser = new AvcVideoParser();
|
14956
14687
|
}
|
14957
14688
|
static probe(data) {
|
14958
14689
|
const syncOffset = TSDemuxer.syncOffset(data);
|
@@ -15117,16 +14848,7 @@ class TSDemuxer {
|
|
15117
14848
|
case videoPid:
|
15118
14849
|
if (stt) {
|
15119
14850
|
if (videoData && (pes = parsePES(videoData))) {
|
15120
|
-
|
15121
|
-
switch (videoTrack.segmentCodec) {
|
15122
|
-
case 'avc':
|
15123
|
-
this.videoParser = new AvcVideoParser();
|
15124
|
-
break;
|
15125
|
-
}
|
15126
|
-
}
|
15127
|
-
if (this.videoParser !== null) {
|
15128
|
-
this.videoParser.parsePES(videoTrack, textTrack, pes, false, this._duration);
|
15129
|
-
}
|
14851
|
+
this.videoParser.parseAVCPES(videoTrack, textTrack, pes, false, this._duration);
|
15130
14852
|
}
|
15131
14853
|
videoData = {
|
15132
14854
|
data: [],
|
@@ -15288,17 +15010,8 @@ class TSDemuxer {
|
|
15288
15010
|
// try to parse last PES packets
|
15289
15011
|
let pes;
|
15290
15012
|
if (videoData && (pes = parsePES(videoData))) {
|
15291
|
-
|
15292
|
-
|
15293
|
-
case 'avc':
|
15294
|
-
this.videoParser = new AvcVideoParser();
|
15295
|
-
break;
|
15296
|
-
}
|
15297
|
-
}
|
15298
|
-
if (this.videoParser !== null) {
|
15299
|
-
this.videoParser.parsePES(videoTrack, textTrack, pes, true, this._duration);
|
15300
|
-
videoTrack.pesData = null;
|
15301
|
-
}
|
15013
|
+
this.videoParser.parseAVCPES(videoTrack, textTrack, pes, true, this._duration);
|
15014
|
+
videoTrack.pesData = null;
|
15302
15015
|
} else {
|
15303
15016
|
// either avcData null or PES truncated, keep it for next frag parsing
|
15304
15017
|
videoTrack.pesData = videoData;
|
@@ -15601,10 +15314,7 @@ function parsePMT(data, offset, typeSupported, isSampleAes) {
|
|
15601
15314
|
logger.warn('Unsupported EC-3 in M2TS found');
|
15602
15315
|
break;
|
15603
15316
|
case 0x24:
|
15604
|
-
|
15605
|
-
{
|
15606
|
-
logger.warn('Unsupported HEVC in M2TS found');
|
15607
|
-
}
|
15317
|
+
logger.warn('Unsupported HEVC in M2TS found');
|
15608
15318
|
break;
|
15609
15319
|
}
|
15610
15320
|
// move to the next table entry
|
@@ -15827,8 +15537,6 @@ class MP4 {
|
|
15827
15537
|
avc1: [],
|
15828
15538
|
// codingname
|
15829
15539
|
avcC: [],
|
15830
|
-
hvc1: [],
|
15831
|
-
hvcC: [],
|
15832
15540
|
btrt: [],
|
15833
15541
|
dinf: [],
|
15834
15542
|
dref: [],
|
@@ -16253,10 +15961,8 @@ class MP4 {
|
|
16253
15961
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.ac3(track));
|
16254
15962
|
}
|
16255
15963
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track));
|
16256
|
-
} else if (track.segmentCodec === 'avc') {
|
16257
|
-
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
|
16258
15964
|
} else {
|
16259
|
-
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.
|
15965
|
+
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
|
16260
15966
|
}
|
16261
15967
|
}
|
16262
15968
|
static tkhd(track) {
|
@@ -16394,84 +16100,6 @@ class MP4 {
|
|
16394
16100
|
const result = appendUint8Array(MP4.FTYP, movie);
|
16395
16101
|
return result;
|
16396
16102
|
}
|
16397
|
-
static hvc1(track) {
|
16398
|
-
const ps = track.params;
|
16399
|
-
const units = [track.vps, track.sps, track.pps];
|
16400
|
-
const NALuLengthSize = 4;
|
16401
|
-
const config = new Uint8Array([0x01, ps.general_profile_space << 6 | (ps.general_tier_flag ? 32 : 0) | ps.general_profile_idc, ps.general_profile_compatibility_flags[0], ps.general_profile_compatibility_flags[1], ps.general_profile_compatibility_flags[2], ps.general_profile_compatibility_flags[3], ps.general_constraint_indicator_flags[0], ps.general_constraint_indicator_flags[1], ps.general_constraint_indicator_flags[2], ps.general_constraint_indicator_flags[3], ps.general_constraint_indicator_flags[4], ps.general_constraint_indicator_flags[5], ps.general_level_idc, 240 | ps.min_spatial_segmentation_idc >> 8, 255 & ps.min_spatial_segmentation_idc, 252 | ps.parallelismType, 252 | ps.chroma_format_idc, 248 | ps.bit_depth_luma_minus8, 248 | ps.bit_depth_chroma_minus8, 0x00, parseInt(ps.frame_rate.fps), NALuLengthSize - 1 | ps.temporal_id_nested << 2 | ps.num_temporal_layers << 3 | (ps.frame_rate.fixed ? 64 : 0), units.length]);
|
16402
|
-
|
16403
|
-
// compute hvcC size in bytes
|
16404
|
-
let length = config.length;
|
16405
|
-
for (let i = 0; i < units.length; i += 1) {
|
16406
|
-
length += 3;
|
16407
|
-
for (let j = 0; j < units[i].length; j += 1) {
|
16408
|
-
length += 2 + units[i][j].length;
|
16409
|
-
}
|
16410
|
-
}
|
16411
|
-
const hvcC = new Uint8Array(length);
|
16412
|
-
hvcC.set(config, 0);
|
16413
|
-
length = config.length;
|
16414
|
-
// append parameter set units: one vps, one or more sps and pps
|
16415
|
-
const iMax = units.length - 1;
|
16416
|
-
for (let i = 0; i < units.length; i += 1) {
|
16417
|
-
hvcC.set(new Uint8Array([32 + i | (i === iMax ? 128 : 0), 0x00, units[i].length]), length);
|
16418
|
-
length += 3;
|
16419
|
-
for (let j = 0; j < units[i].length; j += 1) {
|
16420
|
-
hvcC.set(new Uint8Array([units[i][j].length >> 8, units[i][j].length & 255]), length);
|
16421
|
-
length += 2;
|
16422
|
-
hvcC.set(units[i][j], length);
|
16423
|
-
length += units[i][j].length;
|
16424
|
-
}
|
16425
|
-
}
|
16426
|
-
const hvcc = MP4.box(MP4.types.hvcC, hvcC);
|
16427
|
-
const width = track.width;
|
16428
|
-
const height = track.height;
|
16429
|
-
const hSpacing = track.pixelRatio[0];
|
16430
|
-
const vSpacing = track.pixelRatio[1];
|
16431
|
-
return MP4.box(MP4.types.hvc1, new Uint8Array([0x00, 0x00, 0x00,
|
16432
|
-
// reserved
|
16433
|
-
0x00, 0x00, 0x00,
|
16434
|
-
// reserved
|
16435
|
-
0x00, 0x01,
|
16436
|
-
// data_reference_index
|
16437
|
-
0x00, 0x00,
|
16438
|
-
// pre_defined
|
16439
|
-
0x00, 0x00,
|
16440
|
-
// reserved
|
16441
|
-
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
16442
|
-
// pre_defined
|
16443
|
-
width >> 8 & 0xff, width & 0xff,
|
16444
|
-
// width
|
16445
|
-
height >> 8 & 0xff, height & 0xff,
|
16446
|
-
// height
|
16447
|
-
0x00, 0x48, 0x00, 0x00,
|
16448
|
-
// horizresolution
|
16449
|
-
0x00, 0x48, 0x00, 0x00,
|
16450
|
-
// vertresolution
|
16451
|
-
0x00, 0x00, 0x00, 0x00,
|
16452
|
-
// reserved
|
16453
|
-
0x00, 0x01,
|
16454
|
-
// frame_count
|
16455
|
-
0x12, 0x64, 0x61, 0x69, 0x6c,
|
16456
|
-
// dailymotion/hls.js
|
16457
|
-
0x79, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x68, 0x6c, 0x73, 0x2e, 0x6a, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
16458
|
-
// compressorname
|
16459
|
-
0x00, 0x18,
|
16460
|
-
// depth = 24
|
16461
|
-
0x11, 0x11]),
|
16462
|
-
// pre_defined = -1
|
16463
|
-
hvcc, MP4.box(MP4.types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80,
|
16464
|
-
// bufferSizeDB
|
16465
|
-
0x00, 0x2d, 0xc6, 0xc0,
|
16466
|
-
// maxBitrate
|
16467
|
-
0x00, 0x2d, 0xc6, 0xc0])),
|
16468
|
-
// avgBitrate
|
16469
|
-
MP4.box(MP4.types.pasp, new Uint8Array([hSpacing >> 24,
|
16470
|
-
// hSpacing
|
16471
|
-
hSpacing >> 16 & 0xff, hSpacing >> 8 & 0xff, hSpacing & 0xff, vSpacing >> 24,
|
16472
|
-
// vSpacing
|
16473
|
-
vSpacing >> 16 & 0xff, vSpacing >> 8 & 0xff, vSpacing & 0xff])));
|
16474
|
-
}
|
16475
16103
|
}
|
16476
16104
|
MP4.types = void 0;
|
16477
16105
|
MP4.HDLR_TYPES = void 0;
|
@@ -16847,9 +16475,9 @@ class MP4Remuxer {
|
|
16847
16475
|
const foundOverlap = delta < -1;
|
16848
16476
|
if (foundHole || foundOverlap) {
|
16849
16477
|
if (foundHole) {
|
16850
|
-
logger.warn(
|
16478
|
+
logger.warn(`AVC: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
|
16851
16479
|
} else {
|
16852
|
-
logger.warn(
|
16480
|
+
logger.warn(`AVC: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
|
16853
16481
|
}
|
16854
16482
|
if (!foundOverlap || nextAvcDts >= inputSamples[0].pts || chromeVersion) {
|
16855
16483
|
firstDTS = nextAvcDts;
|
@@ -16858,24 +16486,12 @@ class MP4Remuxer {
|
|
16858
16486
|
inputSamples[0].dts = firstDTS;
|
16859
16487
|
inputSamples[0].pts = firstPTS;
|
16860
16488
|
} else {
|
16861
|
-
let isPTSOrderRetained = true;
|
16862
16489
|
for (let i = 0; i < inputSamples.length; i++) {
|
16863
|
-
if (inputSamples[i].dts > firstPTS
|
16490
|
+
if (inputSamples[i].dts > firstPTS) {
|
16864
16491
|
break;
|
16865
16492
|
}
|
16866
|
-
const prevPTS = inputSamples[i].pts;
|
16867
16493
|
inputSamples[i].dts -= delta;
|
16868
16494
|
inputSamples[i].pts -= delta;
|
16869
|
-
|
16870
|
-
// check to see if this sample's PTS order has changed
|
16871
|
-
// relative to the next one
|
16872
|
-
if (i < inputSamples.length - 1) {
|
16873
|
-
const nextSamplePTS = inputSamples[i + 1].pts;
|
16874
|
-
const currentSamplePTS = inputSamples[i].pts;
|
16875
|
-
const currentOrder = nextSamplePTS <= currentSamplePTS;
|
16876
|
-
const prevOrder = nextSamplePTS <= prevPTS;
|
16877
|
-
isPTSOrderRetained = currentOrder == prevOrder;
|
16878
|
-
}
|
16879
16495
|
}
|
16880
16496
|
}
|
16881
16497
|
logger.log(`Video: Initial PTS/DTS adjusted: ${toMsFromMpegTsClock(firstPTS, true)}/${toMsFromMpegTsClock(firstDTS, true)}, delta: ${toMsFromMpegTsClock(delta, true)} ms`);
|
@@ -17023,7 +16639,7 @@ class MP4Remuxer {
|
|
17023
16639
|
}
|
17024
16640
|
}
|
17025
16641
|
}
|
17026
|
-
// next AVC
|
16642
|
+
// next AVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
|
17027
16643
|
mp4SampleDuration = stretchedLastFrame || !mp4SampleDuration ? averageSampleDuration : mp4SampleDuration;
|
17028
16644
|
this.nextAvcDts = nextAvcDts = lastDTS + mp4SampleDuration;
|
17029
16645
|
this.videoSampleDuration = mp4SampleDuration;
|
@@ -17156,7 +16772,7 @@ class MP4Remuxer {
|
|
17156
16772
|
logger.warn(`[mp4-remuxer]: Injecting ${missing} audio frame @ ${(nextPts / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`);
|
17157
16773
|
for (let j = 0; j < missing; j++) {
|
17158
16774
|
const newStamp = Math.max(nextPts, 0);
|
17159
|
-
let fillFrame = AAC.getSilentFrame(track.
|
16775
|
+
let fillFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
|
17160
16776
|
if (!fillFrame) {
|
17161
16777
|
logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.');
|
17162
16778
|
fillFrame = sample.unit.subarray();
|
@@ -17284,7 +16900,7 @@ class MP4Remuxer {
|
|
17284
16900
|
// samples count of this segment's duration
|
17285
16901
|
const nbSamples = Math.ceil((endDTS - startDTS) / frameDuration);
|
17286
16902
|
// silent frame
|
17287
|
-
const silentFrame = AAC.getSilentFrame(track.
|
16903
|
+
const silentFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
|
17288
16904
|
logger.warn('[mp4-remuxer]: remux empty Audio');
|
17289
16905
|
// Can't remux if we can't generate a silent frame...
|
17290
16906
|
if (!silentFrame) {
|
@@ -17675,15 +17291,13 @@ class Transmuxer {
|
|
17675
17291
|
initSegmentData
|
17676
17292
|
} = transmuxConfig;
|
17677
17293
|
const keyData = getEncryptionType(uintData, decryptdata);
|
17678
|
-
if (keyData &&
|
17294
|
+
if (keyData && keyData.method === 'AES-128') {
|
17679
17295
|
const decrypter = this.getDecrypter();
|
17680
|
-
const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
|
17681
|
-
|
17682
17296
|
// Software decryption is synchronous; webCrypto is not
|
17683
17297
|
if (decrypter.isSync()) {
|
17684
17298
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
17685
17299
|
// data is handled in the flush() call
|
17686
|
-
let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer
|
17300
|
+
let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer);
|
17687
17301
|
// For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
|
17688
17302
|
const loadingParts = chunkMeta.part > -1;
|
17689
17303
|
if (loadingParts) {
|
@@ -17695,7 +17309,7 @@ class Transmuxer {
|
|
17695
17309
|
}
|
17696
17310
|
uintData = new Uint8Array(decryptedData);
|
17697
17311
|
} else {
|
17698
|
-
this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer
|
17312
|
+
this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer).then(decryptedData => {
|
17699
17313
|
// Calling push here is important; if flush() is called while this is still resolving, this ensures that
|
17700
17314
|
// the decrypted data has been transmuxed
|
17701
17315
|
const result = this.push(decryptedData, null, chunkMeta);
|
@@ -18349,7 +17963,14 @@ class TransmuxerInterface {
|
|
18349
17963
|
this.observer = new EventEmitter();
|
18350
17964
|
this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
|
18351
17965
|
this.observer.on(Events.ERROR, forwardMessage);
|
18352
|
-
const
|
17966
|
+
const MediaSource = getMediaSource(config.preferManagedMediaSource) || {
|
17967
|
+
isTypeSupported: () => false
|
17968
|
+
};
|
17969
|
+
const m2tsTypeSupported = {
|
17970
|
+
mpeg: MediaSource.isTypeSupported('audio/mpeg'),
|
17971
|
+
mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
|
17972
|
+
ac3: false
|
17973
|
+
};
|
18353
17974
|
|
18354
17975
|
// navigator.vendor is not always available in Web Worker
|
18355
17976
|
// refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
|
@@ -18613,9 +18234,8 @@ const STALL_MINIMUM_DURATION_MS = 250;
|
|
18613
18234
|
const MAX_START_GAP_JUMP = 2.0;
|
18614
18235
|
const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
|
18615
18236
|
const SKIP_BUFFER_RANGE_START = 0.05;
|
18616
|
-
class GapController
|
18237
|
+
class GapController {
|
18617
18238
|
constructor(config, media, fragmentTracker, hls) {
|
18618
|
-
super('gap-controller', hls.logger);
|
18619
18239
|
this.config = void 0;
|
18620
18240
|
this.media = null;
|
18621
18241
|
this.fragmentTracker = void 0;
|
@@ -18625,7 +18245,6 @@ class GapController extends Logger {
|
|
18625
18245
|
this.stalled = null;
|
18626
18246
|
this.moved = false;
|
18627
18247
|
this.seeking = false;
|
18628
|
-
this.ended = 0;
|
18629
18248
|
this.config = config;
|
18630
18249
|
this.media = media;
|
18631
18250
|
this.fragmentTracker = fragmentTracker;
|
@@ -18643,7 +18262,7 @@ class GapController extends Logger {
|
|
18643
18262
|
*
|
18644
18263
|
* @param lastCurrentTime - Previously read playhead position
|
18645
18264
|
*/
|
18646
|
-
poll(lastCurrentTime, activeFrag
|
18265
|
+
poll(lastCurrentTime, activeFrag) {
|
18647
18266
|
const {
|
18648
18267
|
config,
|
18649
18268
|
media,
|
@@ -18662,7 +18281,6 @@ class GapController extends Logger {
|
|
18662
18281
|
|
18663
18282
|
// The playhead is moving, no-op
|
18664
18283
|
if (currentTime !== lastCurrentTime) {
|
18665
|
-
this.ended = 0;
|
18666
18284
|
this.moved = true;
|
18667
18285
|
if (!seeking) {
|
18668
18286
|
this.nudgeRetry = 0;
|
@@ -18671,7 +18289,7 @@ class GapController extends Logger {
|
|
18671
18289
|
// The playhead is now moving, but was previously stalled
|
18672
18290
|
if (this.stallReported) {
|
18673
18291
|
const _stalledDuration = self.performance.now() - stalled;
|
18674
|
-
|
18292
|
+
logger.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
|
18675
18293
|
this.stallReported = false;
|
18676
18294
|
}
|
18677
18295
|
this.stalled = null;
|
@@ -18707,6 +18325,7 @@ class GapController extends Logger {
|
|
18707
18325
|
// Skip start gaps if we haven't played, but the last poll detected the start of a stall
|
18708
18326
|
// The addition poll gives the browser a chance to jump the gap for us
|
18709
18327
|
if (!this.moved && this.stalled !== null) {
|
18328
|
+
var _level$details;
|
18710
18329
|
// There is no playable buffer (seeked, waiting for buffer)
|
18711
18330
|
const isBuffered = bufferInfo.len > 0;
|
18712
18331
|
if (!isBuffered && !nextStart) {
|
@@ -18718,8 +18337,9 @@ class GapController extends Logger {
|
|
18718
18337
|
// When joining a live stream with audio tracks, account for live playlist window sliding by allowing
|
18719
18338
|
// a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
|
18720
18339
|
// that begins over 1 target duration after the video start position.
|
18721
|
-
const
|
18722
|
-
const
|
18340
|
+
const level = this.hls.levels ? this.hls.levels[this.hls.currentLevel] : null;
|
18341
|
+
const isLive = level == null ? void 0 : (_level$details = level.details) == null ? void 0 : _level$details.live;
|
18342
|
+
const maxStartGapJump = isLive ? level.details.targetduration * 2 : MAX_START_GAP_JUMP;
|
18723
18343
|
const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
|
18724
18344
|
if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
|
18725
18345
|
if (!media.paused) {
|
@@ -18737,17 +18357,6 @@ class GapController extends Logger {
|
|
18737
18357
|
}
|
18738
18358
|
const stalledDuration = tnow - stalled;
|
18739
18359
|
if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) {
|
18740
|
-
// Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
|
18741
|
-
if (state === State.ENDED && !(levelDetails && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? void 0 : levelDetails.edge) || 0)) < 1) {
|
18742
|
-
if (stalledDuration < 1000 || this.ended) {
|
18743
|
-
return;
|
18744
|
-
}
|
18745
|
-
this.ended = currentTime;
|
18746
|
-
this.hls.trigger(Events.MEDIA_ENDED, {
|
18747
|
-
stalled: true
|
18748
|
-
});
|
18749
|
-
return;
|
18750
|
-
}
|
18751
18360
|
// Report stalling after trying to fix
|
18752
18361
|
this._reportStall(bufferInfo);
|
18753
18362
|
if (!this.media) {
|
@@ -18791,7 +18400,7 @@ class GapController extends Logger {
|
|
18791
18400
|
// needs to cross some sort of threshold covering all source-buffers content
|
18792
18401
|
// to start playing properly.
|
18793
18402
|
if ((bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
|
18794
|
-
|
18403
|
+
logger.warn('Trying to nudge playhead over buffer-hole');
|
18795
18404
|
// Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
|
18796
18405
|
// We only try to jump the hole if it's under the configured size
|
18797
18406
|
// Reset stalled so to rearm watchdog timer
|
@@ -18815,7 +18424,7 @@ class GapController extends Logger {
|
|
18815
18424
|
// Report stalled error once
|
18816
18425
|
this.stallReported = true;
|
18817
18426
|
const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${JSON.stringify(bufferInfo)})`);
|
18818
|
-
|
18427
|
+
logger.warn(error.message);
|
18819
18428
|
hls.trigger(Events.ERROR, {
|
18820
18429
|
type: ErrorTypes.MEDIA_ERROR,
|
18821
18430
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
@@ -18883,7 +18492,7 @@ class GapController extends Logger {
|
|
18883
18492
|
}
|
18884
18493
|
}
|
18885
18494
|
const targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
|
18886
|
-
|
18495
|
+
logger.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
|
18887
18496
|
this.moved = true;
|
18888
18497
|
this.stalled = null;
|
18889
18498
|
media.currentTime = targetTime;
|
@@ -18924,7 +18533,7 @@ class GapController extends Logger {
|
|
18924
18533
|
const targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset;
|
18925
18534
|
// playback stalled in buffered area ... let's nudge currentTime to try to overcome this
|
18926
18535
|
const error = new Error(`Nudging 'currentTime' from ${currentTime} to ${targetTime}`);
|
18927
|
-
|
18536
|
+
logger.warn(error.message);
|
18928
18537
|
media.currentTime = targetTime;
|
18929
18538
|
hls.trigger(Events.ERROR, {
|
18930
18539
|
type: ErrorTypes.MEDIA_ERROR,
|
@@ -18934,7 +18543,7 @@ class GapController extends Logger {
|
|
18934
18543
|
});
|
18935
18544
|
} else {
|
18936
18545
|
const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`);
|
18937
|
-
|
18546
|
+
logger.error(error.message);
|
18938
18547
|
hls.trigger(Events.ERROR, {
|
18939
18548
|
type: ErrorTypes.MEDIA_ERROR,
|
18940
18549
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
@@ -18949,7 +18558,7 @@ const TICK_INTERVAL = 100; // how often to tick in ms
|
|
18949
18558
|
|
18950
18559
|
class StreamController extends BaseStreamController {
|
18951
18560
|
constructor(hls, fragmentTracker, keyLoader) {
|
18952
|
-
super(hls, fragmentTracker, keyLoader, 'stream-controller', PlaylistLevelType.MAIN);
|
18561
|
+
super(hls, fragmentTracker, keyLoader, '[stream-controller]', PlaylistLevelType.MAIN);
|
18953
18562
|
this.audioCodecSwap = false;
|
18954
18563
|
this.gapController = null;
|
18955
18564
|
this.level = -1;
|
@@ -18957,43 +18566,27 @@ class StreamController extends BaseStreamController {
|
|
18957
18566
|
this.altAudio = false;
|
18958
18567
|
this.audioOnly = false;
|
18959
18568
|
this.fragPlaying = null;
|
18569
|
+
this.onvplaying = null;
|
18570
|
+
this.onvseeked = null;
|
18960
18571
|
this.fragLastKbps = 0;
|
18961
18572
|
this.couldBacktrack = false;
|
18962
18573
|
this.backtrackFragment = null;
|
18963
18574
|
this.audioCodecSwitch = false;
|
18964
18575
|
this.videoBuffer = null;
|
18965
|
-
this.
|
18966
|
-
// tick to speed up FRAG_CHANGED triggering
|
18967
|
-
this.tick();
|
18968
|
-
};
|
18969
|
-
this.onMediaSeeked = () => {
|
18970
|
-
const media = this.media;
|
18971
|
-
const currentTime = media ? media.currentTime : null;
|
18972
|
-
if (isFiniteNumber(currentTime)) {
|
18973
|
-
this.log(`Media seeked to ${currentTime.toFixed(3)}`);
|
18974
|
-
}
|
18975
|
-
|
18976
|
-
// If seeked was issued before buffer was appended do not tick immediately
|
18977
|
-
const bufferInfo = this.getMainFwdBufferInfo();
|
18978
|
-
if (bufferInfo === null || bufferInfo.len === 0) {
|
18979
|
-
this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
|
18980
|
-
return;
|
18981
|
-
}
|
18982
|
-
|
18983
|
-
// tick to speed up FRAG_CHANGED triggering
|
18984
|
-
this.tick();
|
18985
|
-
};
|
18986
|
-
this.registerListeners();
|
18576
|
+
this._registerListeners();
|
18987
18577
|
}
|
18988
|
-
|
18989
|
-
super.registerListeners();
|
18578
|
+
_registerListeners() {
|
18990
18579
|
const {
|
18991
18580
|
hls
|
18992
18581
|
} = this;
|
18582
|
+
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
18583
|
+
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
18584
|
+
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
18993
18585
|
hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
18994
18586
|
hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
|
18995
18587
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
18996
18588
|
hls.on(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
|
18589
|
+
hls.on(Events.ERROR, this.onError, this);
|
18997
18590
|
hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
18998
18591
|
hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
18999
18592
|
hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -19001,14 +18594,17 @@ class StreamController extends BaseStreamController {
|
|
19001
18594
|
hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
|
19002
18595
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
19003
18596
|
}
|
19004
|
-
|
19005
|
-
super.unregisterListeners();
|
18597
|
+
_unregisterListeners() {
|
19006
18598
|
const {
|
19007
18599
|
hls
|
19008
18600
|
} = this;
|
18601
|
+
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
18602
|
+
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
18603
|
+
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
19009
18604
|
hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
19010
18605
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
19011
18606
|
hls.off(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
|
18607
|
+
hls.off(Events.ERROR, this.onError, this);
|
19012
18608
|
hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
19013
18609
|
hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
19014
18610
|
hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -19017,9 +18613,7 @@ class StreamController extends BaseStreamController {
|
|
19017
18613
|
hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
19018
18614
|
}
|
19019
18615
|
onHandlerDestroying() {
|
19020
|
-
|
19021
|
-
this.onMediaPlaying = this.onMediaSeeked = null;
|
19022
|
-
this.unregisterListeners();
|
18616
|
+
this._unregisterListeners();
|
19023
18617
|
super.onHandlerDestroying();
|
19024
18618
|
}
|
19025
18619
|
startLoad(startPosition) {
|
@@ -19117,9 +18711,6 @@ class StreamController extends BaseStreamController {
|
|
19117
18711
|
this.checkFragmentChanged();
|
19118
18712
|
}
|
19119
18713
|
doTickIdle() {
|
19120
|
-
if (!this.buffering) {
|
19121
|
-
return;
|
19122
|
-
}
|
19123
18714
|
const {
|
19124
18715
|
hls,
|
19125
18716
|
levelLastLoaded,
|
@@ -19347,17 +18938,20 @@ class StreamController extends BaseStreamController {
|
|
19347
18938
|
onMediaAttached(event, data) {
|
19348
18939
|
super.onMediaAttached(event, data);
|
19349
18940
|
const media = data.media;
|
19350
|
-
|
19351
|
-
|
18941
|
+
this.onvplaying = this.onMediaPlaying.bind(this);
|
18942
|
+
this.onvseeked = this.onMediaSeeked.bind(this);
|
18943
|
+
media.addEventListener('playing', this.onvplaying);
|
18944
|
+
media.addEventListener('seeked', this.onvseeked);
|
19352
18945
|
this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
|
19353
18946
|
}
|
19354
18947
|
onMediaDetaching() {
|
19355
18948
|
const {
|
19356
18949
|
media
|
19357
18950
|
} = this;
|
19358
|
-
if (media) {
|
19359
|
-
media.removeEventListener('playing', this.
|
19360
|
-
media.removeEventListener('seeked', this.
|
18951
|
+
if (media && this.onvplaying && this.onvseeked) {
|
18952
|
+
media.removeEventListener('playing', this.onvplaying);
|
18953
|
+
media.removeEventListener('seeked', this.onvseeked);
|
18954
|
+
this.onvplaying = this.onvseeked = null;
|
19361
18955
|
this.videoBuffer = null;
|
19362
18956
|
}
|
19363
18957
|
this.fragPlaying = null;
|
@@ -19367,6 +18961,27 @@ class StreamController extends BaseStreamController {
|
|
19367
18961
|
}
|
19368
18962
|
super.onMediaDetaching();
|
19369
18963
|
}
|
18964
|
+
onMediaPlaying() {
|
18965
|
+
// tick to speed up FRAG_CHANGED triggering
|
18966
|
+
this.tick();
|
18967
|
+
}
|
18968
|
+
onMediaSeeked() {
|
18969
|
+
const media = this.media;
|
18970
|
+
const currentTime = media ? media.currentTime : null;
|
18971
|
+
if (isFiniteNumber(currentTime)) {
|
18972
|
+
this.log(`Media seeked to ${currentTime.toFixed(3)}`);
|
18973
|
+
}
|
18974
|
+
|
18975
|
+
// If seeked was issued before buffer was appended do not tick immediately
|
18976
|
+
const bufferInfo = this.getMainFwdBufferInfo();
|
18977
|
+
if (bufferInfo === null || bufferInfo.len === 0) {
|
18978
|
+
this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
|
18979
|
+
return;
|
18980
|
+
}
|
18981
|
+
|
18982
|
+
// tick to speed up FRAG_CHANGED triggering
|
18983
|
+
this.tick();
|
18984
|
+
}
|
19370
18985
|
onManifestLoading() {
|
19371
18986
|
// reset buffer on manifest loading
|
19372
18987
|
this.log('Trigger BUFFER_RESET');
|
@@ -19658,10 +19273,8 @@ class StreamController extends BaseStreamController {
|
|
19658
19273
|
}
|
19659
19274
|
if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) {
|
19660
19275
|
// Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
|
19661
|
-
const
|
19662
|
-
|
19663
|
-
const levelDetails = this.getLevelDetails();
|
19664
|
-
gapController.poll(this.lastCurrentTime, activeFrag, levelDetails, state);
|
19276
|
+
const activeFrag = this.state !== State.IDLE ? this.fragCurrent : null;
|
19277
|
+
gapController.poll(this.lastCurrentTime, activeFrag);
|
19665
19278
|
}
|
19666
19279
|
this.lastCurrentTime = media.currentTime;
|
19667
19280
|
}
|
@@ -19994,17 +19607,6 @@ class StreamController extends BaseStreamController {
|
|
19994
19607
|
getMainFwdBufferInfo() {
|
19995
19608
|
return this.getFwdBufferInfo(this.mediaBuffer ? this.mediaBuffer : this.media, PlaylistLevelType.MAIN);
|
19996
19609
|
}
|
19997
|
-
get maxBufferLength() {
|
19998
|
-
const {
|
19999
|
-
levels,
|
20000
|
-
level
|
20001
|
-
} = this;
|
20002
|
-
const levelInfo = levels == null ? void 0 : levels[level];
|
20003
|
-
if (!levelInfo) {
|
20004
|
-
return this.config.maxBufferLength;
|
20005
|
-
}
|
20006
|
-
return this.getMaxBufferLength(levelInfo.maxBitrate);
|
20007
|
-
}
|
20008
19610
|
backtrack(frag) {
|
20009
19611
|
this.couldBacktrack = true;
|
20010
19612
|
// Causes findFragments to backtrack through fragments to find the keyframe
|
@@ -20110,7 +19712,7 @@ class Hls {
|
|
20110
19712
|
* Get the video-dev/hls.js package version.
|
20111
19713
|
*/
|
20112
19714
|
static get version() {
|
20113
|
-
return "1.5.7
|
19715
|
+
return "1.5.7";
|
20114
19716
|
}
|
20115
19717
|
|
20116
19718
|
/**
|
@@ -20173,12 +19775,9 @@ class Hls {
|
|
20173
19775
|
* The configuration object provided on player instantiation.
|
20174
19776
|
*/
|
20175
19777
|
this.userConfig = void 0;
|
20176
|
-
/**
|
20177
|
-
* The logger functions used by this player instance, configured on player instantiation.
|
20178
|
-
*/
|
20179
|
-
this.logger = void 0;
|
20180
19778
|
this.coreComponents = void 0;
|
20181
19779
|
this.networkControllers = void 0;
|
19780
|
+
this.started = false;
|
20182
19781
|
this._emitter = new EventEmitter();
|
20183
19782
|
this._autoLevelCapping = -1;
|
20184
19783
|
this._maxHdcpLevel = null;
|
@@ -20195,11 +19794,11 @@ class Hls {
|
|
20195
19794
|
this._media = null;
|
20196
19795
|
this.url = null;
|
20197
19796
|
this.triggeringException = void 0;
|
20198
|
-
|
20199
|
-
const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig
|
19797
|
+
enableLogs(userConfig.debug || false, 'Hls instance');
|
19798
|
+
const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig);
|
20200
19799
|
this.userConfig = userConfig;
|
20201
19800
|
if (config.progressive) {
|
20202
|
-
enableStreamingMode(config
|
19801
|
+
enableStreamingMode(config);
|
20203
19802
|
}
|
20204
19803
|
|
20205
19804
|
// core controllers and network loaders
|
@@ -20212,9 +19811,7 @@ class Hls {
|
|
20212
19811
|
} = config;
|
20213
19812
|
const errorController = new ConfigErrorController(this);
|
20214
19813
|
const abrController = this.abrController = new ConfigAbrController(this);
|
20215
|
-
|
20216
|
-
const fragmentTracker = new FragmentTracker(this);
|
20217
|
-
const bufferController = this.bufferController = new ConfigBufferController(this, fragmentTracker);
|
19814
|
+
const bufferController = this.bufferController = new ConfigBufferController(this);
|
20218
19815
|
const capLevelController = this.capLevelController = new ConfigCapLevelController(this);
|
20219
19816
|
const fpsController = new ConfigFpsController(this);
|
20220
19817
|
const playListLoader = new PlaylistLoader(this);
|
@@ -20223,6 +19820,8 @@ class Hls {
|
|
20223
19820
|
// ConentSteeringController is defined before LevelController to receive Multivariant Playlist events first
|
20224
19821
|
const contentSteering = ConfigContentSteeringController ? new ConfigContentSteeringController(this) : null;
|
20225
19822
|
const levelController = this.levelController = new LevelController(this, contentSteering);
|
19823
|
+
// FragmentTracker must be defined before StreamController because the order of event handling is important
|
19824
|
+
const fragmentTracker = new FragmentTracker(this);
|
20226
19825
|
const keyLoader = new KeyLoader(this.config);
|
20227
19826
|
const streamController = this.streamController = new StreamController(this, fragmentTracker, keyLoader);
|
20228
19827
|
|
@@ -20298,7 +19897,7 @@ class Hls {
|
|
20298
19897
|
try {
|
20299
19898
|
return this.emit(event, event, eventObject);
|
20300
19899
|
} catch (error) {
|
20301
|
-
|
19900
|
+
logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
|
20302
19901
|
// Prevent recursion in error event handlers that throw #5497
|
20303
19902
|
if (!this.triggeringException) {
|
20304
19903
|
this.triggeringException = true;
|
@@ -20324,7 +19923,7 @@ class Hls {
|
|
20324
19923
|
* Dispose of the instance
|
20325
19924
|
*/
|
20326
19925
|
destroy() {
|
20327
|
-
|
19926
|
+
logger.log('destroy');
|
20328
19927
|
this.trigger(Events.DESTROYING, undefined);
|
20329
19928
|
this.detachMedia();
|
20330
19929
|
this.removeAllListeners();
|
@@ -20345,7 +19944,7 @@ class Hls {
|
|
20345
19944
|
* Attaches Hls.js to a media element
|
20346
19945
|
*/
|
20347
19946
|
attachMedia(media) {
|
20348
|
-
|
19947
|
+
logger.log('attachMedia');
|
20349
19948
|
this._media = media;
|
20350
19949
|
this.trigger(Events.MEDIA_ATTACHING, {
|
20351
19950
|
media: media
|
@@ -20356,7 +19955,7 @@ class Hls {
|
|
20356
19955
|
* Detach Hls.js from the media
|
20357
19956
|
*/
|
20358
19957
|
detachMedia() {
|
20359
|
-
|
19958
|
+
logger.log('detachMedia');
|
20360
19959
|
this.trigger(Events.MEDIA_DETACHING, undefined);
|
20361
19960
|
this._media = null;
|
20362
19961
|
}
|
@@ -20373,7 +19972,7 @@ class Hls {
|
|
20373
19972
|
});
|
20374
19973
|
this._autoLevelCapping = -1;
|
20375
19974
|
this._maxHdcpLevel = null;
|
20376
|
-
|
19975
|
+
logger.log(`loadSource:${loadingSource}`);
|
20377
19976
|
if (media && loadedSource && (loadedSource !== loadingSource || this.bufferController.hasSourceTypes())) {
|
20378
19977
|
this.detachMedia();
|
20379
19978
|
this.attachMedia(media);
|
@@ -20392,7 +19991,8 @@ class Hls {
|
|
20392
19991
|
* Defaults to -1 (None: starts from earliest point)
|
20393
19992
|
*/
|
20394
19993
|
startLoad(startPosition = -1) {
|
20395
|
-
|
19994
|
+
logger.log(`startLoad(${startPosition})`);
|
19995
|
+
this.started = true;
|
20396
19996
|
this.networkControllers.forEach(controller => {
|
20397
19997
|
controller.startLoad(startPosition);
|
20398
19998
|
});
|
@@ -20402,31 +20002,34 @@ class Hls {
|
|
20402
20002
|
* Stop loading of any stream data.
|
20403
20003
|
*/
|
20404
20004
|
stopLoad() {
|
20405
|
-
|
20005
|
+
logger.log('stopLoad');
|
20006
|
+
this.started = false;
|
20406
20007
|
this.networkControllers.forEach(controller => {
|
20407
20008
|
controller.stopLoad();
|
20408
20009
|
});
|
20409
20010
|
}
|
20410
20011
|
|
20411
20012
|
/**
|
20412
|
-
* Resumes stream controller segment loading
|
20013
|
+
* Resumes stream controller segment loading if previously started.
|
20413
20014
|
*/
|
20414
20015
|
resumeBuffering() {
|
20415
|
-
this.
|
20416
|
-
|
20417
|
-
controller
|
20418
|
-
|
20419
|
-
|
20016
|
+
if (this.started) {
|
20017
|
+
this.networkControllers.forEach(controller => {
|
20018
|
+
if ('fragmentLoader' in controller) {
|
20019
|
+
controller.startLoad(-1);
|
20020
|
+
}
|
20021
|
+
});
|
20022
|
+
}
|
20420
20023
|
}
|
20421
20024
|
|
20422
20025
|
/**
|
20423
|
-
*
|
20026
|
+
* Stops stream controller segment loading without changing 'started' state like stopLoad().
|
20424
20027
|
* This allows for media buffering to be paused without interupting playlist loading.
|
20425
20028
|
*/
|
20426
20029
|
pauseBuffering() {
|
20427
20030
|
this.networkControllers.forEach(controller => {
|
20428
|
-
if (controller
|
20429
|
-
controller.
|
20031
|
+
if ('fragmentLoader' in controller) {
|
20032
|
+
controller.stopLoad();
|
20430
20033
|
}
|
20431
20034
|
});
|
20432
20035
|
}
|
@@ -20435,7 +20038,7 @@ class Hls {
|
|
20435
20038
|
* Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
|
20436
20039
|
*/
|
20437
20040
|
swapAudioCodec() {
|
20438
|
-
|
20041
|
+
logger.log('swapAudioCodec');
|
20439
20042
|
this.streamController.swapAudioCodec();
|
20440
20043
|
}
|
20441
20044
|
|
@@ -20446,7 +20049,7 @@ class Hls {
|
|
20446
20049
|
* Automatic recovery of media-errors by this process is configurable.
|
20447
20050
|
*/
|
20448
20051
|
recoverMediaError() {
|
20449
|
-
|
20052
|
+
logger.log('recoverMediaError');
|
20450
20053
|
const media = this._media;
|
20451
20054
|
this.detachMedia();
|
20452
20055
|
if (media) {
|
@@ -20476,7 +20079,7 @@ class Hls {
|
|
20476
20079
|
* Set quality level index immediately. This will flush the current buffer to replace the quality asap. That means playback will interrupt at least shortly to re-buffer and re-sync eventually. Set to -1 for automatic level selection.
|
20477
20080
|
*/
|
20478
20081
|
set currentLevel(newLevel) {
|
20479
|
-
|
20082
|
+
logger.log(`set currentLevel:${newLevel}`);
|
20480
20083
|
this.levelController.manualLevel = newLevel;
|
20481
20084
|
this.streamController.immediateLevelSwitch();
|
20482
20085
|
}
|
@@ -20495,7 +20098,7 @@ class Hls {
|
|
20495
20098
|
* @param newLevel - Pass -1 for automatic level selection
|
20496
20099
|
*/
|
20497
20100
|
set nextLevel(newLevel) {
|
20498
|
-
|
20101
|
+
logger.log(`set nextLevel:${newLevel}`);
|
20499
20102
|
this.levelController.manualLevel = newLevel;
|
20500
20103
|
this.streamController.nextLevelSwitch();
|
20501
20104
|
}
|
@@ -20514,7 +20117,7 @@ class Hls {
|
|
20514
20117
|
* @param newLevel - Pass -1 for automatic level selection
|
20515
20118
|
*/
|
20516
20119
|
set loadLevel(newLevel) {
|
20517
|
-
|
20120
|
+
logger.log(`set loadLevel:${newLevel}`);
|
20518
20121
|
this.levelController.manualLevel = newLevel;
|
20519
20122
|
}
|
20520
20123
|
|
@@ -20545,7 +20148,7 @@ class Hls {
|
|
20545
20148
|
* Sets "first-level", see getter.
|
20546
20149
|
*/
|
20547
20150
|
set firstLevel(newLevel) {
|
20548
|
-
|
20151
|
+
logger.log(`set firstLevel:${newLevel}`);
|
20549
20152
|
this.levelController.firstLevel = newLevel;
|
20550
20153
|
}
|
20551
20154
|
|
@@ -20570,7 +20173,7 @@ class Hls {
|
|
20570
20173
|
* (determined from download of first segment)
|
20571
20174
|
*/
|
20572
20175
|
set startLevel(newLevel) {
|
20573
|
-
|
20176
|
+
logger.log(`set startLevel:${newLevel}`);
|
20574
20177
|
// if not in automatic start level detection, ensure startLevel is greater than minAutoLevel
|
20575
20178
|
if (newLevel !== -1) {
|
20576
20179
|
newLevel = Math.max(newLevel, this.minAutoLevel);
|
@@ -20645,7 +20248,7 @@ class Hls {
|
|
20645
20248
|
*/
|
20646
20249
|
set autoLevelCapping(newLevel) {
|
20647
20250
|
if (this._autoLevelCapping !== newLevel) {
|
20648
|
-
|
20251
|
+
logger.log(`set autoLevelCapping:${newLevel}`);
|
20649
20252
|
this._autoLevelCapping = newLevel;
|
20650
20253
|
this.levelController.checkMaxAutoUpdated();
|
20651
20254
|
}
|
@@ -20750,9 +20353,6 @@ class Hls {
|
|
20750
20353
|
get mainForwardBufferInfo() {
|
20751
20354
|
return this.streamController.getMainFwdBufferInfo();
|
20752
20355
|
}
|
20753
|
-
get maxBufferLength() {
|
20754
|
-
return this.streamController.maxBufferLength;
|
20755
|
-
}
|
20756
20356
|
|
20757
20357
|
/**
|
20758
20358
|
* Find and select the best matching audio track, making a level switch when a Group change is necessary.
|
@@ -20927,5 +20527,5 @@ var KeySystemFormats = empty.KeySystemFormats;
|
|
20927
20527
|
var KeySystems = empty.KeySystems;
|
20928
20528
|
var SubtitleStreamController = empty.SubtitleStreamController;
|
20929
20529
|
var TimelineController = empty.TimelineController;
|
20930
|
-
export { AbrController, AttrList,
|
20530
|
+
export { AbrController, AttrList, Cues as AudioStreamController, Cues as AudioTrackController, BasePlaylistController, BaseSegment, BaseStreamController, BufferController, Cues as CMCDController, CapLevelController, ChunkMetadata, ContentSteeringController, DateRange, Cues as EMEController, ErrorActionFlags, ErrorController, ErrorDetails, ErrorTypes, Events, FPSController, Fragment, Hls, HlsSkip, HlsUrlParameters, KeySystemFormats, KeySystems, Level, LevelDetails, LevelKey, LoadStats, MetadataSchema, NetworkErrorAction, Part, PlaylistLevelType, SubtitleStreamController, Cues as SubtitleTrackController, TimelineController, Hls as default, getMediaSource, isMSESupported, isSupported };
|
20931
20531
|
//# sourceMappingURL=hls.light.mjs.map
|