hls.js 1.5.6-0.canary.9999 → 1.5.6
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 +0 -1
- package/dist/hls-demo.js +0 -10
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +1169 -2069
- package/dist/hls.js.d.ts +51 -65
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +875 -1158
- 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 +709 -993
- 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 +869 -1756
- 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 +20 -20
- package/src/config.ts +2 -3
- package/src/controller/abr-controller.ts +40 -31
- package/src/controller/audio-stream-controller.ts +16 -15
- 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 +33 -149
- package/src/controller/buffer-controller.ts +11 -11
- package/src/controller/cap-level-controller.ts +2 -1
- 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/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 +34 -27
- package/src/controller/subtitle-stream-controller.ts +14 -13
- 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 +34 -42
- 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/codecs.ts +4 -33
- package/src/utils/logger.ts +24 -54
- package/src/utils/mp4-tools.ts +6 -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.6"}`);
|
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.6-0.canary.9999"}`);
|
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.
|
@@ -1505,11 +1463,13 @@ function parseSegmentIndex(sidx) {
|
|
1505
1463
|
let earliestPresentationTime = 0;
|
1506
1464
|
let firstOffset = 0;
|
1507
1465
|
if (version === 0) {
|
1508
|
-
earliestPresentationTime = readUint32(sidx, index
|
1509
|
-
firstOffset = readUint32(sidx, index
|
1466
|
+
earliestPresentationTime = readUint32(sidx, index);
|
1467
|
+
firstOffset = readUint32(sidx, index + 4);
|
1468
|
+
index += 8;
|
1510
1469
|
} else {
|
1511
|
-
earliestPresentationTime = readUint64(sidx, index
|
1512
|
-
firstOffset = readUint64(sidx, index
|
1470
|
+
earliestPresentationTime = readUint64(sidx, index);
|
1471
|
+
firstOffset = readUint64(sidx, index + 8);
|
1472
|
+
index += 16;
|
1513
1473
|
}
|
1514
1474
|
|
1515
1475
|
// skip reserved
|
@@ -2473,12 +2433,12 @@ class LevelKey {
|
|
2473
2433
|
this.keyFormatVersions = formatversions;
|
2474
2434
|
this.iv = iv;
|
2475
2435
|
this.encrypted = method ? method !== 'NONE' : false;
|
2476
|
-
this.isCommonEncryption = this.encrypted &&
|
2436
|
+
this.isCommonEncryption = this.encrypted && method !== 'AES-128';
|
2477
2437
|
}
|
2478
2438
|
isSupported() {
|
2479
2439
|
// If it's Segment encryption or No encryption, just select that key system
|
2480
2440
|
if (this.method) {
|
2481
|
-
if (
|
2441
|
+
if (this.method === 'AES-128' || this.method === 'NONE') {
|
2482
2442
|
return true;
|
2483
2443
|
}
|
2484
2444
|
if (this.keyFormat === 'identity') {
|
@@ -2492,13 +2452,14 @@ class LevelKey {
|
|
2492
2452
|
if (!this.encrypted || !this.uri) {
|
2493
2453
|
return null;
|
2494
2454
|
}
|
2495
|
-
if (
|
2455
|
+
if (this.method === 'AES-128' && this.uri && !this.iv) {
|
2496
2456
|
if (typeof sn !== 'number') {
|
2497
2457
|
// We are fetching decryption data for a initialization segment
|
2498
|
-
// If the segment was encrypted with AES-128
|
2458
|
+
// If the segment was encrypted with AES-128
|
2499
2459
|
// It must have an IV defined. We cannot substitute the Segment Number in.
|
2500
|
-
|
2501
|
-
|
2460
|
+
if (this.method === 'AES-128' && !this.iv) {
|
2461
|
+
logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
|
2462
|
+
}
|
2502
2463
|
// Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
|
2503
2464
|
sn = 0;
|
2504
2465
|
}
|
@@ -2645,28 +2606,23 @@ function getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource =
|
|
2645
2606
|
if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) {
|
2646
2607
|
return CODEC_COMPATIBLE_NAMES[lowerCaseCodec];
|
2647
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
|
2648
2613
|
const codecsToCheck = {
|
2649
|
-
// Idealy fLaC and Opus would be first (spec-compliant) but
|
2650
|
-
// some browsers will report that fLaC is supported then fail.
|
2651
|
-
// see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
|
2652
2614
|
flac: ['flac', 'fLaC', 'FLAC'],
|
2653
|
-
opus: ['opus', 'Opus']
|
2654
|
-
// Replace audio codec info if browser does not support mp4a.40.34,
|
2655
|
-
// and demuxer can fallback to 'audio/mpeg' or 'audio/mp4;codecs="mp3"'
|
2656
|
-
'mp4a.40.34': ['mp3']
|
2615
|
+
opus: ['opus', 'Opus']
|
2657
2616
|
}[lowerCaseCodec];
|
2658
2617
|
for (let i = 0; i < codecsToCheck.length; i++) {
|
2659
|
-
var _getMediaSource;
|
2660
2618
|
if (isCodecMediaSourceSupported(codecsToCheck[i], 'audio', preferManagedMediaSource)) {
|
2661
2619
|
CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
|
2662
2620
|
return codecsToCheck[i];
|
2663
|
-
} else if (codecsToCheck[i] === 'mp3' && (_getMediaSource = getMediaSource(preferManagedMediaSource)) != null && _getMediaSource.isTypeSupported('audio/mpeg')) {
|
2664
|
-
return '';
|
2665
2621
|
}
|
2666
2622
|
}
|
2667
2623
|
return lowerCaseCodec;
|
2668
2624
|
}
|
2669
|
-
const AUDIO_CODEC_REGEXP = /flac|opus
|
2625
|
+
const AUDIO_CODEC_REGEXP = /flac|opus/i;
|
2670
2626
|
function getCodecCompatibleName(codec, preferManagedMediaSource = true) {
|
2671
2627
|
return codec.replace(AUDIO_CODEC_REGEXP, m => getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource));
|
2672
2628
|
}
|
@@ -2689,16 +2645,6 @@ function convertAVC1ToAVCOTI(codec) {
|
|
2689
2645
|
}
|
2690
2646
|
return codec;
|
2691
2647
|
}
|
2692
|
-
function getM2TSSupportedAudioTypes(preferManagedMediaSource) {
|
2693
|
-
const MediaSource = getMediaSource(preferManagedMediaSource) || {
|
2694
|
-
isTypeSupported: () => false
|
2695
|
-
};
|
2696
|
-
return {
|
2697
|
-
mpeg: MediaSource.isTypeSupported('audio/mpeg'),
|
2698
|
-
mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
|
2699
|
-
ac3: false
|
2700
|
-
};
|
2701
|
-
}
|
2702
2648
|
|
2703
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;
|
2704
2650
|
const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g;
|
@@ -3499,10 +3445,10 @@ class PlaylistLoader {
|
|
3499
3445
|
const loaderContext = loader.context;
|
3500
3446
|
if (loaderContext && loaderContext.url === context.url && loaderContext.level === context.level) {
|
3501
3447
|
// same URL can't overlap
|
3502
|
-
|
3448
|
+
logger.trace('[playlist-loader]: playlist request ongoing');
|
3503
3449
|
return;
|
3504
3450
|
}
|
3505
|
-
|
3451
|
+
logger.log(`[playlist-loader]: aborting previous loader for type: ${context.type}`);
|
3506
3452
|
loader.abort();
|
3507
3453
|
}
|
3508
3454
|
|
@@ -3612,7 +3558,7 @@ class PlaylistLoader {
|
|
3612
3558
|
// alt audio rendition in which quality levels (main)
|
3613
3559
|
// contains both audio+video. but with mixed audio track not signaled
|
3614
3560
|
if (!embeddedAudioFound && levels[0].audioCodec && !levels[0].attrs.AUDIO) {
|
3615
|
-
|
3561
|
+
logger.log('[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one');
|
3616
3562
|
audioTracks.unshift({
|
3617
3563
|
type: 'main',
|
3618
3564
|
name: 'main',
|
@@ -3711,7 +3657,7 @@ class PlaylistLoader {
|
|
3711
3657
|
message += ` id: ${context.id} group-id: "${context.groupId}"`;
|
3712
3658
|
}
|
3713
3659
|
const error = new Error(message);
|
3714
|
-
|
3660
|
+
logger.warn(`[playlist-loader]: ${message}`);
|
3715
3661
|
let details = ErrorDetails.UNKNOWN;
|
3716
3662
|
let fatal = false;
|
3717
3663
|
const loader = this.getInternalLoader(context);
|
@@ -4276,47 +4222,7 @@ class LatencyController {
|
|
4276
4222
|
this.currentTime = 0;
|
4277
4223
|
this.stallCount = 0;
|
4278
4224
|
this._latency = null;
|
4279
|
-
this.
|
4280
|
-
const {
|
4281
|
-
media,
|
4282
|
-
levelDetails
|
4283
|
-
} = this;
|
4284
|
-
if (!media || !levelDetails) {
|
4285
|
-
return;
|
4286
|
-
}
|
4287
|
-
this.currentTime = media.currentTime;
|
4288
|
-
const latency = this.computeLatency();
|
4289
|
-
if (latency === null) {
|
4290
|
-
return;
|
4291
|
-
}
|
4292
|
-
this._latency = latency;
|
4293
|
-
|
4294
|
-
// Adapt playbackRate to meet target latency in low-latency mode
|
4295
|
-
const {
|
4296
|
-
lowLatencyMode,
|
4297
|
-
maxLiveSyncPlaybackRate
|
4298
|
-
} = this.config;
|
4299
|
-
if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
|
4300
|
-
return;
|
4301
|
-
}
|
4302
|
-
const targetLatency = this.targetLatency;
|
4303
|
-
if (targetLatency === null) {
|
4304
|
-
return;
|
4305
|
-
}
|
4306
|
-
const distanceFromTarget = latency - targetLatency;
|
4307
|
-
// Only adjust playbackRate when within one target duration of targetLatency
|
4308
|
-
// and more than one second from under-buffering.
|
4309
|
-
// Playback further than one target duration from target can be considered DVR playback.
|
4310
|
-
const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
|
4311
|
-
const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
|
4312
|
-
if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
|
4313
|
-
const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
|
4314
|
-
const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
|
4315
|
-
media.playbackRate = Math.min(max, Math.max(1, rate));
|
4316
|
-
} else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
|
4317
|
-
media.playbackRate = 1;
|
4318
|
-
}
|
4319
|
-
};
|
4225
|
+
this.timeupdateHandler = () => this.timeupdate();
|
4320
4226
|
this.hls = hls;
|
4321
4227
|
this.config = hls.config;
|
4322
4228
|
this.registerListeners();
|
@@ -4408,7 +4314,7 @@ class LatencyController {
|
|
4408
4314
|
this.onMediaDetaching();
|
4409
4315
|
this.levelDetails = null;
|
4410
4316
|
// @ts-ignore
|
4411
|
-
this.hls = null;
|
4317
|
+
this.hls = this.timeupdateHandler = null;
|
4412
4318
|
}
|
4413
4319
|
registerListeners() {
|
4414
4320
|
this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
@@ -4426,11 +4332,11 @@ class LatencyController {
|
|
4426
4332
|
}
|
4427
4333
|
onMediaAttached(event, data) {
|
4428
4334
|
this.media = data.media;
|
4429
|
-
this.media.addEventListener('timeupdate', this.
|
4335
|
+
this.media.addEventListener('timeupdate', this.timeupdateHandler);
|
4430
4336
|
}
|
4431
4337
|
onMediaDetaching() {
|
4432
4338
|
if (this.media) {
|
4433
|
-
this.media.removeEventListener('timeupdate', this.
|
4339
|
+
this.media.removeEventListener('timeupdate', this.timeupdateHandler);
|
4434
4340
|
this.media = null;
|
4435
4341
|
}
|
4436
4342
|
}
|
@@ -4444,10 +4350,10 @@ class LatencyController {
|
|
4444
4350
|
}) {
|
4445
4351
|
this.levelDetails = details;
|
4446
4352
|
if (details.advanced) {
|
4447
|
-
this.
|
4353
|
+
this.timeupdate();
|
4448
4354
|
}
|
4449
4355
|
if (!details.live && this.media) {
|
4450
|
-
this.media.removeEventListener('timeupdate', this.
|
4356
|
+
this.media.removeEventListener('timeupdate', this.timeupdateHandler);
|
4451
4357
|
}
|
4452
4358
|
}
|
4453
4359
|
onError(event, data) {
|
@@ -4457,7 +4363,48 @@ class LatencyController {
|
|
4457
4363
|
}
|
4458
4364
|
this.stallCount++;
|
4459
4365
|
if ((_this$levelDetails = this.levelDetails) != null && _this$levelDetails.live) {
|
4460
|
-
|
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;
|
4461
4408
|
}
|
4462
4409
|
}
|
4463
4410
|
estimateLiveEdge() {
|
@@ -5229,13 +5176,18 @@ var ErrorActionFlags = {
|
|
5229
5176
|
MoveAllAlternatesMatchingHDCP: 2,
|
5230
5177
|
SwitchToSDR: 4
|
5231
5178
|
}; // Reserved for future use
|
5232
|
-
class ErrorController
|
5179
|
+
class ErrorController {
|
5233
5180
|
constructor(hls) {
|
5234
|
-
super('error-controller', hls.logger);
|
5235
5181
|
this.hls = void 0;
|
5236
5182
|
this.playlistError = 0;
|
5237
5183
|
this.penalizedRenditions = {};
|
5184
|
+
this.log = void 0;
|
5185
|
+
this.warn = void 0;
|
5186
|
+
this.error = void 0;
|
5238
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]:`);
|
5239
5191
|
this.registerListeners();
|
5240
5192
|
}
|
5241
5193
|
registerListeners() {
|
@@ -5587,13 +5539,16 @@ class ErrorController extends Logger {
|
|
5587
5539
|
}
|
5588
5540
|
}
|
5589
5541
|
|
5590
|
-
class BasePlaylistController
|
5542
|
+
class BasePlaylistController {
|
5591
5543
|
constructor(hls, logPrefix) {
|
5592
|
-
super(logPrefix, hls.logger);
|
5593
5544
|
this.hls = void 0;
|
5594
5545
|
this.timer = -1;
|
5595
5546
|
this.requestScheduled = -1;
|
5596
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}:`);
|
5597
5552
|
this.hls = hls;
|
5598
5553
|
}
|
5599
5554
|
destroy() {
|
@@ -5626,7 +5581,7 @@ class BasePlaylistController extends Logger {
|
|
5626
5581
|
try {
|
5627
5582
|
uri = new self.URL(attr.URI, previous.url).href;
|
5628
5583
|
} catch (error) {
|
5629
|
-
|
5584
|
+
logger.warn(`Could not construct new URL for Rendition Report: ${error}`);
|
5630
5585
|
uri = attr.URI || '';
|
5631
5586
|
}
|
5632
5587
|
// Use exact match. Otherwise, the last partial match, if any, will be used
|
@@ -5713,12 +5668,7 @@ class BasePlaylistController extends Logger {
|
|
5713
5668
|
const cdnAge = lastAdvanced + details.ageHeader;
|
5714
5669
|
let currentGoal = Math.min(cdnAge - details.partTarget, details.targetduration * 1.5);
|
5715
5670
|
if (currentGoal > 0) {
|
5716
|
-
if (
|
5717
|
-
// Omit segment and part directives when the last response was more than 3 target durations ago,
|
5718
|
-
this.log(`Playlist last advanced ${lastAdvanced.toFixed(2)}s ago. Omitting segment and part directives.`);
|
5719
|
-
msn = undefined;
|
5720
|
-
part = undefined;
|
5721
|
-
} else if (previousDetails != null && previousDetails.tuneInGoal && cdnAge - details.partTarget > previousDetails.tuneInGoal) {
|
5671
|
+
if (previousDetails && currentGoal > previousDetails.tuneInGoal) {
|
5722
5672
|
// If we attempted to get the next or latest playlist update, but currentGoal increased,
|
5723
5673
|
// then we either can't catchup, or the "age" header cannot be trusted.
|
5724
5674
|
this.warn(`CDN Tune-in goal increased from: ${previousDetails.tuneInGoal} to: ${currentGoal} with playlist age: ${details.age}`);
|
@@ -6177,9 +6127,8 @@ function getCodecTiers(levels, audioTracksByGroup, minAutoLevel, maxAutoLevel) {
|
|
6177
6127
|
}, {});
|
6178
6128
|
}
|
6179
6129
|
|
6180
|
-
class AbrController
|
6130
|
+
class AbrController {
|
6181
6131
|
constructor(_hls) {
|
6182
|
-
super('abr', _hls.logger);
|
6183
6132
|
this.hls = void 0;
|
6184
6133
|
this.lastLevelLoadSec = 0;
|
6185
6134
|
this.lastLoadedFragLevel = -1;
|
@@ -6293,7 +6242,7 @@ class AbrController extends Logger {
|
|
6293
6242
|
this.resetEstimator(nextLoadLevelBitrate);
|
6294
6243
|
}
|
6295
6244
|
this.clearTimer();
|
6296
|
-
|
6245
|
+
logger.warn(`[abr] Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
|
6297
6246
|
Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s
|
6298
6247
|
Estimated load time for current fragment: ${fragLoadedDelay.toFixed(3)} s
|
6299
6248
|
Estimated load time for down switch fragment: ${fragLevelNextLoadedDelay.toFixed(3)} s
|
@@ -6313,7 +6262,7 @@ class AbrController extends Logger {
|
|
6313
6262
|
}
|
6314
6263
|
resetEstimator(abrEwmaDefaultEstimate) {
|
6315
6264
|
if (abrEwmaDefaultEstimate) {
|
6316
|
-
|
6265
|
+
logger.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
|
6317
6266
|
this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate;
|
6318
6267
|
}
|
6319
6268
|
this.firstSelection = -1;
|
@@ -6545,7 +6494,7 @@ class AbrController extends Logger {
|
|
6545
6494
|
}
|
6546
6495
|
const firstLevel = this.hls.firstLevel;
|
6547
6496
|
const clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel);
|
6548
|
-
|
6497
|
+
logger.warn(`[abr] Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
|
6549
6498
|
return clamped;
|
6550
6499
|
}
|
6551
6500
|
get forcedAutoLevel() {
|
@@ -6583,8 +6532,7 @@ class AbrController extends Logger {
|
|
6583
6532
|
return nextABRAutoLevel;
|
6584
6533
|
}
|
6585
6534
|
getAutoLevelKey() {
|
6586
|
-
|
6587
|
-
return `${this.getBwEstimate()}_${(_this$hls$mainForward = this.hls.mainForwardBufferInfo) == null ? void 0 : _this$hls$mainForward.len}`;
|
6535
|
+
return `${this.getBwEstimate()}_${this.getStarvationDelay().toFixed(2)}`;
|
6588
6536
|
}
|
6589
6537
|
getNextABRAutoLevel() {
|
6590
6538
|
const {
|
@@ -6595,18 +6543,12 @@ class AbrController extends Logger {
|
|
6595
6543
|
const {
|
6596
6544
|
maxAutoLevel,
|
6597
6545
|
config,
|
6598
|
-
minAutoLevel
|
6599
|
-
media
|
6546
|
+
minAutoLevel
|
6600
6547
|
} = hls;
|
6601
6548
|
const currentFragDuration = partCurrent ? partCurrent.duration : fragCurrent ? fragCurrent.duration : 0;
|
6602
|
-
|
6603
|
-
// playbackRate is the absolute value of the playback rate; if media.playbackRate is 0, we use 1 to load as
|
6604
|
-
// if we're playing back at the normal rate.
|
6605
|
-
const playbackRate = media && media.playbackRate !== 0 ? Math.abs(media.playbackRate) : 1.0;
|
6606
6549
|
const avgbw = this.getBwEstimate();
|
6607
6550
|
// bufferStarvationDelay is the wall-clock time left until the playback buffer is exhausted.
|
6608
|
-
const
|
6609
|
-
const bufferStarvationDelay = (bufferInfo ? bufferInfo.len : 0) / playbackRate;
|
6551
|
+
const bufferStarvationDelay = this.getStarvationDelay();
|
6610
6552
|
let bwFactor = config.abrBandWidthFactor;
|
6611
6553
|
let bwUpFactor = config.abrBandWidthUpFactor;
|
6612
6554
|
|
@@ -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
|
}
|
@@ -6649,6 +6591,18 @@ class AbrController extends Logger {
|
|
6649
6591
|
// or if bitrate is not lower, continue to use loadLevel
|
6650
6592
|
return hls.loadLevel;
|
6651
6593
|
}
|
6594
|
+
getStarvationDelay() {
|
6595
|
+
const hls = this.hls;
|
6596
|
+
const media = hls.media;
|
6597
|
+
if (!media) {
|
6598
|
+
return Infinity;
|
6599
|
+
}
|
6600
|
+
// playbackRate is the absolute value of the playback rate; if media.playbackRate is 0, we use 1 to load as
|
6601
|
+
// if we're playing back at the normal rate.
|
6602
|
+
const playbackRate = media && media.playbackRate !== 0 ? Math.abs(media.playbackRate) : 1.0;
|
6603
|
+
const bufferInfo = hls.mainForwardBufferInfo;
|
6604
|
+
return (bufferInfo ? bufferInfo.len : 0) / playbackRate;
|
6605
|
+
}
|
6652
6606
|
getBwEstimate() {
|
6653
6607
|
return this.bwEstimator.canEstimate() ? this.bwEstimator.getEstimate() : this.hls.config.abrEwmaDefaultEstimate;
|
6654
6608
|
}
|
@@ -6698,7 +6652,7 @@ class AbrController extends Logger {
|
|
6698
6652
|
currentVideoRange = preferHDR ? videoRanges[videoRanges.length - 1] : videoRanges[0];
|
6699
6653
|
currentFrameRate = minFramerate;
|
6700
6654
|
currentBw = Math.max(currentBw, minBitrate);
|
6701
|
-
|
6655
|
+
logger.log(`[abr] picked start tier ${JSON.stringify(startTier)}`);
|
6702
6656
|
} else {
|
6703
6657
|
currentCodecSet = level == null ? void 0 : level.codecSet;
|
6704
6658
|
currentVideoRange = level == null ? void 0 : level.videoRange;
|
@@ -6751,9 +6705,9 @@ class AbrController extends Logger {
|
|
6751
6705
|
const forcedAutoLevel = this.forcedAutoLevel;
|
6752
6706
|
if (i !== loadLevel && (forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)) {
|
6753
6707
|
if (levelsSkipped.length) {
|
6754
|
-
|
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}`);
|
6755
6709
|
}
|
6756
|
-
|
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}`);
|
6757
6711
|
}
|
6758
6712
|
if (firstSelection) {
|
6759
6713
|
this.firstSelection = i;
|
@@ -6989,9 +6943,8 @@ class BufferOperationQueue {
|
|
6989
6943
|
}
|
6990
6944
|
|
6991
6945
|
const VIDEO_CODEC_PROFILE_REPLACE = /(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\.[^.,]+)+/;
|
6992
|
-
class BufferController
|
6946
|
+
class BufferController {
|
6993
6947
|
constructor(hls) {
|
6994
|
-
super('buffer-controller', hls.logger);
|
6995
6948
|
// The level details used to determine duration, target-duration and live
|
6996
6949
|
this.details = null;
|
6997
6950
|
// cache the self generated object url to detect hijack of video tag
|
@@ -7021,6 +6974,9 @@ class BufferController extends Logger {
|
|
7021
6974
|
this.tracks = {};
|
7022
6975
|
this.pendingTracks = {};
|
7023
6976
|
this.sourceBuffer = void 0;
|
6977
|
+
this.log = void 0;
|
6978
|
+
this.warn = void 0;
|
6979
|
+
this.error = void 0;
|
7024
6980
|
this._onEndStreaming = event => {
|
7025
6981
|
if (!this.hls) {
|
7026
6982
|
return;
|
@@ -7066,11 +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;
|
7029
|
+
const logPrefix = '[buffer-controller]';
|
7073
7030
|
this.appendSource = hls.config.preferManagedMediaSource;
|
7031
|
+
this.log = logger.log.bind(logger, logPrefix);
|
7032
|
+
this.warn = logger.warn.bind(logger, logPrefix);
|
7033
|
+
this.error = logger.error.bind(logger, logPrefix);
|
7074
7034
|
this._initSourceBuffer();
|
7075
7035
|
this.registerListeners();
|
7076
7036
|
}
|
@@ -7083,12 +7043,6 @@ class BufferController extends Logger {
|
|
7083
7043
|
this.lastMpegAudioChunk = null;
|
7084
7044
|
// @ts-ignore
|
7085
7045
|
this.hls = null;
|
7086
|
-
// @ts-ignore
|
7087
|
-
this._onMediaSourceOpen = this._onMediaSourceClose = null;
|
7088
|
-
// @ts-ignore
|
7089
|
-
this._onMediaSourceEnded = null;
|
7090
|
-
// @ts-ignore
|
7091
|
-
this._onStartStreaming = this._onEndStreaming = null;
|
7092
7046
|
}
|
7093
7047
|
registerListeners() {
|
7094
7048
|
const {
|
@@ -7251,7 +7205,6 @@ class BufferController extends Logger {
|
|
7251
7205
|
this.resetBuffer(type);
|
7252
7206
|
});
|
7253
7207
|
this._initSourceBuffer();
|
7254
|
-
this.hls.resumeBuffering();
|
7255
7208
|
}
|
7256
7209
|
resetBuffer(type) {
|
7257
7210
|
const sb = this.sourceBuffer[type];
|
@@ -8089,7 +8042,7 @@ class CapLevelController {
|
|
8089
8042
|
const hls = this.hls;
|
8090
8043
|
const maxLevel = this.getMaxLevel(levels.length - 1);
|
8091
8044
|
if (maxLevel !== this.autoLevelCapping) {
|
8092
|
-
|
8045
|
+
logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
|
8093
8046
|
}
|
8094
8047
|
hls.autoLevelCapping = maxLevel;
|
8095
8048
|
if (hls.autoLevelCapping > this.autoLevelCapping && this.streamController) {
|
@@ -8267,10 +8220,10 @@ class FPSController {
|
|
8267
8220
|
totalDroppedFrames: droppedFrames
|
8268
8221
|
});
|
8269
8222
|
if (droppedFPS > 0) {
|
8270
|
-
//
|
8223
|
+
// logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
|
8271
8224
|
if (currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
|
8272
8225
|
let currentLevel = hls.currentLevel;
|
8273
|
-
|
8226
|
+
logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
|
8274
8227
|
if (currentLevel > 0 && (hls.autoLevelCapping === -1 || hls.autoLevelCapping >= currentLevel)) {
|
8275
8228
|
currentLevel = currentLevel - 1;
|
8276
8229
|
hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, {
|
@@ -8303,10 +8256,10 @@ class FPSController {
|
|
8303
8256
|
}
|
8304
8257
|
|
8305
8258
|
const PATHWAY_PENALTY_DURATION_MS = 300000;
|
8306
|
-
class ContentSteeringController
|
8259
|
+
class ContentSteeringController {
|
8307
8260
|
constructor(hls) {
|
8308
|
-
super('content-steering', hls.logger);
|
8309
8261
|
this.hls = void 0;
|
8262
|
+
this.log = void 0;
|
8310
8263
|
this.loader = null;
|
8311
8264
|
this.uri = null;
|
8312
8265
|
this.pathwayId = '.';
|
@@ -8321,6 +8274,7 @@ class ContentSteeringController extends Logger {
|
|
8321
8274
|
this.subtitleTracks = null;
|
8322
8275
|
this.penalizedPathways = {};
|
8323
8276
|
this.hls = hls;
|
8277
|
+
this.log = logger.log.bind(logger, `[content-steering]:`);
|
8324
8278
|
this.registerListeners();
|
8325
8279
|
}
|
8326
8280
|
registerListeners() {
|
@@ -8444,7 +8398,7 @@ class ContentSteeringController extends Logger {
|
|
8444
8398
|
errorAction.resolved = this.pathwayId !== errorPathway;
|
8445
8399
|
}
|
8446
8400
|
if (!errorAction.resolved) {
|
8447
|
-
|
8401
|
+
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)}`);
|
8448
8402
|
}
|
8449
8403
|
}
|
8450
8404
|
}
|
@@ -8615,7 +8569,7 @@ class ContentSteeringController extends Logger {
|
|
8615
8569
|
onSuccess: (response, stats, context, networkDetails) => {
|
8616
8570
|
this.log(`Loaded steering manifest: "${url}"`);
|
8617
8571
|
const steeringData = response.data;
|
8618
|
-
if (
|
8572
|
+
if (steeringData.VERSION !== 1) {
|
8619
8573
|
this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
|
8620
8574
|
return;
|
8621
8575
|
}
|
@@ -9523,7 +9477,7 @@ const hlsDefaultConfig = _objectSpread2(_objectSpread2({
|
|
9523
9477
|
});
|
9524
9478
|
function timelineConfig() {
|
9525
9479
|
return {
|
9526
|
-
cueHandler:
|
9480
|
+
cueHandler: Cues,
|
9527
9481
|
// used by timeline-controller
|
9528
9482
|
enableWebVTT: false,
|
9529
9483
|
// used by timeline-controller
|
@@ -9554,7 +9508,7 @@ function timelineConfig() {
|
|
9554
9508
|
/**
|
9555
9509
|
* @ignore
|
9556
9510
|
*/
|
9557
|
-
function mergeConfig(defaultConfig, userConfig
|
9511
|
+
function mergeConfig(defaultConfig, userConfig) {
|
9558
9512
|
if ((userConfig.liveSyncDurationCount || userConfig.liveMaxLatencyDurationCount) && (userConfig.liveSyncDuration || userConfig.liveMaxLatencyDuration)) {
|
9559
9513
|
throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration");
|
9560
9514
|
}
|
@@ -9624,7 +9578,7 @@ function deepCpy(obj) {
|
|
9624
9578
|
/**
|
9625
9579
|
* @ignore
|
9626
9580
|
*/
|
9627
|
-
function enableStreamingMode(config
|
9581
|
+
function enableStreamingMode(config) {
|
9628
9582
|
const currentLoader = config.loader;
|
9629
9583
|
if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
|
9630
9584
|
// If a developer has configured their own loader, respect that choice
|
@@ -9641,9 +9595,10 @@ function enableStreamingMode(config, logger) {
|
|
9641
9595
|
}
|
9642
9596
|
}
|
9643
9597
|
|
9598
|
+
let chromeOrFirefox;
|
9644
9599
|
class LevelController extends BasePlaylistController {
|
9645
9600
|
constructor(hls, contentSteeringController) {
|
9646
|
-
super(hls, 'level-controller');
|
9601
|
+
super(hls, '[level-controller]');
|
9647
9602
|
this._levels = [];
|
9648
9603
|
this._firstLevel = -1;
|
9649
9604
|
this._maxAutoLevel = -1;
|
@@ -9714,15 +9669,23 @@ class LevelController extends BasePlaylistController {
|
|
9714
9669
|
let videoCodecFound = false;
|
9715
9670
|
let audioCodecFound = false;
|
9716
9671
|
data.levels.forEach(levelParsed => {
|
9717
|
-
var _videoCodec;
|
9672
|
+
var _audioCodec, _videoCodec;
|
9718
9673
|
const attributes = levelParsed.attrs;
|
9674
|
+
|
9675
|
+
// erase audio codec info if browser does not support mp4a.40.34.
|
9676
|
+
// demuxer will autodetect codec and fallback to mpeg/audio
|
9719
9677
|
let {
|
9720
9678
|
audioCodec,
|
9721
9679
|
videoCodec
|
9722
9680
|
} = levelParsed;
|
9681
|
+
if (((_audioCodec = audioCodec) == null ? void 0 : _audioCodec.indexOf('mp4a.40.34')) !== -1) {
|
9682
|
+
chromeOrFirefox || (chromeOrFirefox = /chrome|firefox/i.test(navigator.userAgent));
|
9683
|
+
if (chromeOrFirefox) {
|
9684
|
+
levelParsed.audioCodec = audioCodec = undefined;
|
9685
|
+
}
|
9686
|
+
}
|
9723
9687
|
if (audioCodec) {
|
9724
|
-
|
9725
|
-
levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource) || undefined;
|
9688
|
+
levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource);
|
9726
9689
|
}
|
9727
9690
|
if (((_videoCodec = videoCodec) == null ? void 0 : _videoCodec.indexOf('avc1')) === 0) {
|
9728
9691
|
videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
|
@@ -10064,12 +10027,7 @@ class LevelController extends BasePlaylistController {
|
|
10064
10027
|
if (curLevel.fragmentError === 0) {
|
10065
10028
|
curLevel.loadError = 0;
|
10066
10029
|
}
|
10067
|
-
|
10068
|
-
let previousDetails = curLevel.details;
|
10069
|
-
if (previousDetails === data.details && previousDetails.advanced) {
|
10070
|
-
previousDetails = undefined;
|
10071
|
-
}
|
10072
|
-
this.playlistLoaded(level, data, previousDetails);
|
10030
|
+
this.playlistLoaded(level, data, curLevel.details);
|
10073
10031
|
} else if ((_data$deliveryDirecti2 = data.deliveryDirectives) != null && _data$deliveryDirecti2.skip) {
|
10074
10032
|
// received a delta playlist update that cannot be merged
|
10075
10033
|
details.deltaUpdateFailed = true;
|
@@ -10855,8 +10813,8 @@ function createLoaderContext(frag, part = null) {
|
|
10855
10813
|
var _frag$decryptdata;
|
10856
10814
|
let byteRangeStart = start;
|
10857
10815
|
let byteRangeEnd = end;
|
10858
|
-
if (frag.sn === 'initSegment' &&
|
10859
|
-
// MAP segment encrypted with method 'AES-128'
|
10816
|
+
if (frag.sn === 'initSegment' && ((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method) === 'AES-128') {
|
10817
|
+
// MAP segment encrypted with method 'AES-128', when served with HTTP Range,
|
10860
10818
|
// has the unencrypted size specified in the range.
|
10861
10819
|
// Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
|
10862
10820
|
const fragmentLen = end - start;
|
@@ -10889,9 +10847,6 @@ function createGapLoadError(frag, part) {
|
|
10889
10847
|
(part ? part : frag).stats.aborted = true;
|
10890
10848
|
return new LoadError(errorData);
|
10891
10849
|
}
|
10892
|
-
function isMethodFullSegmentAesCbc(method) {
|
10893
|
-
return method === 'AES-128' || method === 'AES-256';
|
10894
|
-
}
|
10895
10850
|
class LoadError extends Error {
|
10896
10851
|
constructor(data) {
|
10897
10852
|
super(data.error.message);
|
@@ -11037,8 +10992,6 @@ class KeyLoader {
|
|
11037
10992
|
}
|
11038
10993
|
return this.loadKeyEME(keyInfo, frag);
|
11039
10994
|
case 'AES-128':
|
11040
|
-
case 'AES-256':
|
11041
|
-
case 'AES-256-CTR':
|
11042
10995
|
return this.loadKeyHTTP(keyInfo, frag);
|
11043
10996
|
default:
|
11044
10997
|
return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`)));
|
@@ -11174,9 +11127,8 @@ class KeyLoader {
|
|
11174
11127
|
* we are limiting the task execution per call stack to exactly one, but scheduling/post-poning further
|
11175
11128
|
* task processing on the next main loop iteration (also known as "next tick" in the Node/JS runtime lingo).
|
11176
11129
|
*/
|
11177
|
-
class TaskLoop
|
11178
|
-
constructor(
|
11179
|
-
super(label, logger);
|
11130
|
+
class TaskLoop {
|
11131
|
+
constructor() {
|
11180
11132
|
this._boundTick = void 0;
|
11181
11133
|
this._tickTimer = null;
|
11182
11134
|
this._tickInterval = null;
|
@@ -11444,61 +11396,33 @@ function alignMediaPlaylistByPDT(details, refDetails) {
|
|
11444
11396
|
}
|
11445
11397
|
|
11446
11398
|
class AESCrypto {
|
11447
|
-
constructor(subtle, iv
|
11399
|
+
constructor(subtle, iv) {
|
11448
11400
|
this.subtle = void 0;
|
11449
11401
|
this.aesIV = void 0;
|
11450
|
-
this.aesMode = void 0;
|
11451
11402
|
this.subtle = subtle;
|
11452
11403
|
this.aesIV = iv;
|
11453
|
-
this.aesMode = aesMode;
|
11454
11404
|
}
|
11455
11405
|
decrypt(data, key) {
|
11456
|
-
|
11457
|
-
|
11458
|
-
|
11459
|
-
|
11460
|
-
iv: this.aesIV
|
11461
|
-
}, key, data);
|
11462
|
-
case DecrypterAesMode.ctr:
|
11463
|
-
return this.subtle.decrypt({
|
11464
|
-
name: 'AES-CTR',
|
11465
|
-
counter: this.aesIV,
|
11466
|
-
length: 64
|
11467
|
-
},
|
11468
|
-
//64 : NIST SP800-38A standard suggests that the counter should occupy half of the counter block
|
11469
|
-
key, data);
|
11470
|
-
default:
|
11471
|
-
throw new Error(`[AESCrypto] invalid aes mode ${this.aesMode}`);
|
11472
|
-
}
|
11406
|
+
return this.subtle.decrypt({
|
11407
|
+
name: 'AES-CBC',
|
11408
|
+
iv: this.aesIV
|
11409
|
+
}, key, data);
|
11473
11410
|
}
|
11474
11411
|
}
|
11475
11412
|
|
11476
11413
|
class FastAESKey {
|
11477
|
-
constructor(subtle, key
|
11414
|
+
constructor(subtle, key) {
|
11478
11415
|
this.subtle = void 0;
|
11479
11416
|
this.key = void 0;
|
11480
|
-
this.aesMode = void 0;
|
11481
11417
|
this.subtle = subtle;
|
11482
11418
|
this.key = key;
|
11483
|
-
this.aesMode = aesMode;
|
11484
11419
|
}
|
11485
11420
|
expandKey() {
|
11486
|
-
const subtleAlgoName = getSubtleAlgoName(this.aesMode);
|
11487
11421
|
return this.subtle.importKey('raw', this.key, {
|
11488
|
-
name:
|
11422
|
+
name: 'AES-CBC'
|
11489
11423
|
}, false, ['encrypt', 'decrypt']);
|
11490
11424
|
}
|
11491
11425
|
}
|
11492
|
-
function getSubtleAlgoName(aesMode) {
|
11493
|
-
switch (aesMode) {
|
11494
|
-
case DecrypterAesMode.cbc:
|
11495
|
-
return 'AES-CBC';
|
11496
|
-
case DecrypterAesMode.ctr:
|
11497
|
-
return 'AES-CTR';
|
11498
|
-
default:
|
11499
|
-
throw new Error(`[FastAESKey] invalid aes mode ${aesMode}`);
|
11500
|
-
}
|
11501
|
-
}
|
11502
11426
|
|
11503
11427
|
// PKCS7
|
11504
11428
|
function removePadding(array) {
|
@@ -11748,8 +11672,7 @@ class Decrypter {
|
|
11748
11672
|
this.currentIV = null;
|
11749
11673
|
this.currentResult = null;
|
11750
11674
|
this.useSoftware = void 0;
|
11751
|
-
this.
|
11752
|
-
this.enableSoftwareAES = config.enableSoftwareAES;
|
11675
|
+
this.useSoftware = config.enableSoftwareAES;
|
11753
11676
|
this.removePKCS7Padding = removePKCS7Padding;
|
11754
11677
|
// built in decryptor expects PKCS7 padding
|
11755
11678
|
if (removePKCS7Padding) {
|
@@ -11762,7 +11685,9 @@ class Decrypter {
|
|
11762
11685
|
/* no-op */
|
11763
11686
|
}
|
11764
11687
|
}
|
11765
|
-
|
11688
|
+
if (this.subtle === null) {
|
11689
|
+
this.useSoftware = true;
|
11690
|
+
}
|
11766
11691
|
}
|
11767
11692
|
destroy() {
|
11768
11693
|
this.subtle = null;
|
@@ -11800,10 +11725,10 @@ class Decrypter {
|
|
11800
11725
|
this.softwareDecrypter = null;
|
11801
11726
|
}
|
11802
11727
|
}
|
11803
|
-
decrypt(data, key, iv
|
11728
|
+
decrypt(data, key, iv) {
|
11804
11729
|
if (this.useSoftware) {
|
11805
11730
|
return new Promise((resolve, reject) => {
|
11806
|
-
this.softwareDecrypt(new Uint8Array(data), key, iv
|
11731
|
+
this.softwareDecrypt(new Uint8Array(data), key, iv);
|
11807
11732
|
const decryptResult = this.flush();
|
11808
11733
|
if (decryptResult) {
|
11809
11734
|
resolve(decryptResult.buffer);
|
@@ -11812,21 +11737,17 @@ class Decrypter {
|
|
11812
11737
|
}
|
11813
11738
|
});
|
11814
11739
|
}
|
11815
|
-
return this.webCryptoDecrypt(new Uint8Array(data), key, iv
|
11740
|
+
return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
|
11816
11741
|
}
|
11817
11742
|
|
11818
11743
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
11819
11744
|
// data is handled in the flush() call
|
11820
|
-
softwareDecrypt(data, key, iv
|
11745
|
+
softwareDecrypt(data, key, iv) {
|
11821
11746
|
const {
|
11822
11747
|
currentIV,
|
11823
11748
|
currentResult,
|
11824
11749
|
remainderData
|
11825
11750
|
} = this;
|
11826
|
-
if (aesMode !== DecrypterAesMode.cbc || key.byteLength !== 16) {
|
11827
|
-
logger.warn('SoftwareDecrypt: can only handle AES-128-CBC');
|
11828
|
-
return null;
|
11829
|
-
}
|
11830
11751
|
this.logOnce('JS AES decrypt');
|
11831
11752
|
// The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
|
11832
11753
|
// This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached
|
@@ -11859,11 +11780,11 @@ class Decrypter {
|
|
11859
11780
|
}
|
11860
11781
|
return result;
|
11861
11782
|
}
|
11862
|
-
webCryptoDecrypt(data, key, iv
|
11783
|
+
webCryptoDecrypt(data, key, iv) {
|
11863
11784
|
const subtle = this.subtle;
|
11864
11785
|
if (this.key !== key || !this.fastAesKey) {
|
11865
11786
|
this.key = key;
|
11866
|
-
this.fastAesKey = new FastAESKey(subtle, key
|
11787
|
+
this.fastAesKey = new FastAESKey(subtle, key);
|
11867
11788
|
}
|
11868
11789
|
return this.fastAesKey.expandKey().then(aesKey => {
|
11869
11790
|
// decrypt using web crypto
|
@@ -11871,25 +11792,22 @@ class Decrypter {
|
|
11871
11792
|
return Promise.reject(new Error('web crypto not initialized'));
|
11872
11793
|
}
|
11873
11794
|
this.logOnce('WebCrypto AES decrypt');
|
11874
|
-
const crypto = new AESCrypto(subtle, new Uint8Array(iv)
|
11795
|
+
const crypto = new AESCrypto(subtle, new Uint8Array(iv));
|
11875
11796
|
return crypto.decrypt(data.buffer, aesKey);
|
11876
11797
|
}).catch(err => {
|
11877
11798
|
logger.warn(`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`);
|
11878
|
-
return this.onWebCryptoError(data, key, iv
|
11799
|
+
return this.onWebCryptoError(data, key, iv);
|
11879
11800
|
});
|
11880
11801
|
}
|
11881
|
-
onWebCryptoError(data, key, iv
|
11882
|
-
|
11883
|
-
|
11884
|
-
|
11885
|
-
|
11886
|
-
|
11887
|
-
|
11888
|
-
if (decryptResult) {
|
11889
|
-
return decryptResult.buffer;
|
11890
|
-
}
|
11802
|
+
onWebCryptoError(data, key, iv) {
|
11803
|
+
this.useSoftware = true;
|
11804
|
+
this.logEnabled = true;
|
11805
|
+
this.softwareDecrypt(data, key, iv);
|
11806
|
+
const decryptResult = this.flush();
|
11807
|
+
if (decryptResult) {
|
11808
|
+
return decryptResult.buffer;
|
11891
11809
|
}
|
11892
|
-
throw new Error('WebCrypto
|
11810
|
+
throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data');
|
11893
11811
|
}
|
11894
11812
|
getValidChunk(data) {
|
11895
11813
|
let currentChunk = data;
|
@@ -11940,7 +11858,7 @@ const State = {
|
|
11940
11858
|
};
|
11941
11859
|
class BaseStreamController extends TaskLoop {
|
11942
11860
|
constructor(hls, fragmentTracker, keyLoader, logPrefix, playlistType) {
|
11943
|
-
super(
|
11861
|
+
super();
|
11944
11862
|
this.hls = void 0;
|
11945
11863
|
this.fragPrevious = null;
|
11946
11864
|
this.fragCurrent = null;
|
@@ -11965,98 +11883,22 @@ class BaseStreamController extends TaskLoop {
|
|
11965
11883
|
this.startFragRequested = false;
|
11966
11884
|
this.decrypter = void 0;
|
11967
11885
|
this.initPTS = [];
|
11968
|
-
this.
|
11969
|
-
this.
|
11970
|
-
this.
|
11971
|
-
|
11972
|
-
|
11973
|
-
fragCurrent,
|
11974
|
-
media,
|
11975
|
-
mediaBuffer,
|
11976
|
-
state
|
11977
|
-
} = this;
|
11978
|
-
const currentTime = media ? media.currentTime : 0;
|
11979
|
-
const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
|
11980
|
-
this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
|
11981
|
-
if (this.state === State.ENDED) {
|
11982
|
-
this.resetLoadingState();
|
11983
|
-
} else if (fragCurrent) {
|
11984
|
-
// Seeking while frag load is in progress
|
11985
|
-
const tolerance = config.maxFragLookUpTolerance;
|
11986
|
-
const fragStartOffset = fragCurrent.start - tolerance;
|
11987
|
-
const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
|
11988
|
-
// if seeking out of buffered range or into new one
|
11989
|
-
if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
|
11990
|
-
const pastFragment = currentTime > fragEndOffset;
|
11991
|
-
// if the seek position is outside the current fragment range
|
11992
|
-
if (currentTime < fragStartOffset || pastFragment) {
|
11993
|
-
if (pastFragment && fragCurrent.loader) {
|
11994
|
-
this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
|
11995
|
-
fragCurrent.abortRequests();
|
11996
|
-
this.resetLoadingState();
|
11997
|
-
}
|
11998
|
-
this.fragPrevious = null;
|
11999
|
-
}
|
12000
|
-
}
|
12001
|
-
}
|
12002
|
-
if (media) {
|
12003
|
-
// Remove gap fragments
|
12004
|
-
this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
|
12005
|
-
this.lastCurrentTime = currentTime;
|
12006
|
-
if (!this.loadingParts) {
|
12007
|
-
const bufferEnd = Math.max(bufferInfo.end, currentTime);
|
12008
|
-
const shouldLoadParts = this.shouldLoadParts(this.getLevelDetails(), bufferEnd);
|
12009
|
-
if (shouldLoadParts) {
|
12010
|
-
this.log(`LL-Part loading ON after seeking to ${currentTime.toFixed(2)} with buffer @${bufferEnd.toFixed(2)}`);
|
12011
|
-
this.loadingParts = shouldLoadParts;
|
12012
|
-
}
|
12013
|
-
}
|
12014
|
-
}
|
12015
|
-
|
12016
|
-
// in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
|
12017
|
-
if (!this.loadedmetadata && !bufferInfo.len) {
|
12018
|
-
this.nextLoadPosition = this.startPosition = currentTime;
|
12019
|
-
}
|
12020
|
-
|
12021
|
-
// Async tick to speed up processing
|
12022
|
-
this.tickImmediate();
|
12023
|
-
};
|
12024
|
-
this.onMediaEnded = () => {
|
12025
|
-
// reset startPosition and lastCurrentTime to restart playback @ stream beginning
|
12026
|
-
this.startPosition = this.lastCurrentTime = 0;
|
12027
|
-
if (this.playlistType === PlaylistLevelType.MAIN) {
|
12028
|
-
this.hls.trigger(Events.MEDIA_ENDED, {
|
12029
|
-
stalled: false
|
12030
|
-
});
|
12031
|
-
}
|
12032
|
-
};
|
11886
|
+
this.onvseeking = null;
|
11887
|
+
this.onvended = null;
|
11888
|
+
this.logPrefix = '';
|
11889
|
+
this.log = void 0;
|
11890
|
+
this.warn = void 0;
|
12033
11891
|
this.playlistType = playlistType;
|
11892
|
+
this.logPrefix = logPrefix;
|
11893
|
+
this.log = logger.log.bind(logger, `${logPrefix}:`);
|
11894
|
+
this.warn = logger.warn.bind(logger, `${logPrefix}:`);
|
12034
11895
|
this.hls = hls;
|
12035
11896
|
this.fragmentLoader = new FragmentLoader(hls.config);
|
12036
11897
|
this.keyLoader = keyLoader;
|
12037
11898
|
this.fragmentTracker = fragmentTracker;
|
12038
11899
|
this.config = hls.config;
|
12039
11900
|
this.decrypter = new Decrypter(hls.config);
|
12040
|
-
}
|
12041
|
-
registerListeners() {
|
12042
|
-
const {
|
12043
|
-
hls
|
12044
|
-
} = this;
|
12045
|
-
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
12046
|
-
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
12047
|
-
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
12048
11901
|
hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
12049
|
-
hls.on(Events.ERROR, this.onError, this);
|
12050
|
-
}
|
12051
|
-
unregisterListeners() {
|
12052
|
-
const {
|
12053
|
-
hls
|
12054
|
-
} = this;
|
12055
|
-
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
12056
|
-
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
12057
|
-
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
12058
|
-
hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
12059
|
-
hls.off(Events.ERROR, this.onError, this);
|
12060
11902
|
}
|
12061
11903
|
doTick() {
|
12062
11904
|
this.onTickEnd();
|
@@ -12080,12 +11922,6 @@ class BaseStreamController extends TaskLoop {
|
|
12080
11922
|
this.clearNextTick();
|
12081
11923
|
this.state = State.STOPPED;
|
12082
11924
|
}
|
12083
|
-
pauseBuffering() {
|
12084
|
-
this.buffering = false;
|
12085
|
-
}
|
12086
|
-
resumeBuffering() {
|
12087
|
-
this.buffering = true;
|
12088
|
-
}
|
12089
11925
|
_streamEnded(bufferInfo, levelDetails) {
|
12090
11926
|
// If playlist is live, there is another buffered range after the current range, nothing buffered, media is detached,
|
12091
11927
|
// of nothing loading/loaded return false
|
@@ -12116,8 +11952,10 @@ class BaseStreamController extends TaskLoop {
|
|
12116
11952
|
}
|
12117
11953
|
onMediaAttached(event, data) {
|
12118
11954
|
const media = this.media = this.mediaBuffer = data.media;
|
12119
|
-
|
12120
|
-
|
11955
|
+
this.onvseeking = this.onMediaSeeking.bind(this);
|
11956
|
+
this.onvended = this.onMediaEnded.bind(this);
|
11957
|
+
media.addEventListener('seeking', this.onvseeking);
|
11958
|
+
media.addEventListener('ended', this.onvended);
|
12121
11959
|
const config = this.config;
|
12122
11960
|
if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
|
12123
11961
|
this.startLoad(config.startPosition);
|
@@ -12131,9 +11969,10 @@ class BaseStreamController extends TaskLoop {
|
|
12131
11969
|
}
|
12132
11970
|
|
12133
11971
|
// remove video listeners
|
12134
|
-
if (media) {
|
12135
|
-
media.removeEventListener('seeking', this.
|
12136
|
-
media.removeEventListener('ended', this.
|
11972
|
+
if (media && this.onvseeking && this.onvended) {
|
11973
|
+
media.removeEventListener('seeking', this.onvseeking);
|
11974
|
+
media.removeEventListener('ended', this.onvended);
|
11975
|
+
this.onvseeking = this.onvended = null;
|
12137
11976
|
}
|
12138
11977
|
if (this.keyLoader) {
|
12139
11978
|
this.keyLoader.detach();
|
@@ -12143,8 +11982,56 @@ class BaseStreamController extends TaskLoop {
|
|
12143
11982
|
this.fragmentTracker.removeAllFragments();
|
12144
11983
|
this.stopLoad();
|
12145
11984
|
}
|
12146
|
-
|
12147
|
-
|
11985
|
+
onMediaSeeking() {
|
11986
|
+
const {
|
11987
|
+
config,
|
11988
|
+
fragCurrent,
|
11989
|
+
media,
|
11990
|
+
mediaBuffer,
|
11991
|
+
state
|
11992
|
+
} = this;
|
11993
|
+
const currentTime = media ? media.currentTime : 0;
|
11994
|
+
const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
|
11995
|
+
this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
|
11996
|
+
if (this.state === State.ENDED) {
|
11997
|
+
this.resetLoadingState();
|
11998
|
+
} else if (fragCurrent) {
|
11999
|
+
// Seeking while frag load is in progress
|
12000
|
+
const tolerance = config.maxFragLookUpTolerance;
|
12001
|
+
const fragStartOffset = fragCurrent.start - tolerance;
|
12002
|
+
const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
|
12003
|
+
// if seeking out of buffered range or into new one
|
12004
|
+
if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
|
12005
|
+
const pastFragment = currentTime > fragEndOffset;
|
12006
|
+
// if the seek position is outside the current fragment range
|
12007
|
+
if (currentTime < fragStartOffset || pastFragment) {
|
12008
|
+
if (pastFragment && fragCurrent.loader) {
|
12009
|
+
this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
|
12010
|
+
fragCurrent.abortRequests();
|
12011
|
+
this.resetLoadingState();
|
12012
|
+
}
|
12013
|
+
this.fragPrevious = null;
|
12014
|
+
}
|
12015
|
+
}
|
12016
|
+
}
|
12017
|
+
if (media) {
|
12018
|
+
// Remove gap fragments
|
12019
|
+
this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
|
12020
|
+
this.lastCurrentTime = currentTime;
|
12021
|
+
}
|
12022
|
+
|
12023
|
+
// in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
|
12024
|
+
if (!this.loadedmetadata && !bufferInfo.len) {
|
12025
|
+
this.nextLoadPosition = this.startPosition = currentTime;
|
12026
|
+
}
|
12027
|
+
|
12028
|
+
// Async tick to speed up processing
|
12029
|
+
this.tickImmediate();
|
12030
|
+
}
|
12031
|
+
onMediaEnded() {
|
12032
|
+
// reset startPosition and lastCurrentTime to restart playback @ stream beginning
|
12033
|
+
this.startPosition = this.lastCurrentTime = 0;
|
12034
|
+
}
|
12148
12035
|
onManifestLoaded(event, data) {
|
12149
12036
|
this.startTimeOffset = data.startTimeOffset;
|
12150
12037
|
this.initPTS = [];
|
@@ -12154,7 +12041,7 @@ class BaseStreamController extends TaskLoop {
|
|
12154
12041
|
this.stopLoad();
|
12155
12042
|
super.onHandlerDestroying();
|
12156
12043
|
// @ts-ignore
|
12157
|
-
this.hls =
|
12044
|
+
this.hls = null;
|
12158
12045
|
}
|
12159
12046
|
onHandlerDestroyed() {
|
12160
12047
|
this.state = State.STOPPED;
|
@@ -12285,10 +12172,10 @@ class BaseStreamController extends TaskLoop {
|
|
12285
12172
|
const decryptData = frag.decryptdata;
|
12286
12173
|
|
12287
12174
|
// check to see if the payload needs to be decrypted
|
12288
|
-
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv &&
|
12175
|
+
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
|
12289
12176
|
const startTime = self.performance.now();
|
12290
12177
|
// decrypt init segment data
|
12291
|
-
return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer
|
12178
|
+
return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
|
12292
12179
|
hls.trigger(Events.ERROR, {
|
12293
12180
|
type: ErrorTypes.MEDIA_ERROR,
|
12294
12181
|
details: ErrorDetails.FRAG_DECRYPT_ERROR,
|
@@ -12400,7 +12287,7 @@ class BaseStreamController extends TaskLoop {
|
|
12400
12287
|
}
|
12401
12288
|
let keyLoadingPromise = null;
|
12402
12289
|
if (frag.encrypted && !((_frag$decryptdata = frag.decryptdata) != null && _frag$decryptdata.key)) {
|
12403
|
-
this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.
|
12290
|
+
this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.logPrefix === '[stream-controller]' ? 'level' : 'track'} ${frag.level}`);
|
12404
12291
|
this.state = State.KEY_LOADING;
|
12405
12292
|
this.fragCurrent = frag;
|
12406
12293
|
keyLoadingPromise = this.keyLoader.load(frag).then(keyLoadedData => {
|
@@ -12421,16 +12308,8 @@ class BaseStreamController extends TaskLoop {
|
|
12421
12308
|
} else if (!frag.encrypted && details.encryptedFragments.length) {
|
12422
12309
|
this.keyLoader.loadClear(frag, details.encryptedFragments);
|
12423
12310
|
}
|
12424
|
-
const fragPrevious = this.fragPrevious;
|
12425
|
-
if (frag.sn !== 'initSegment' && (!fragPrevious || frag.sn !== fragPrevious.sn)) {
|
12426
|
-
const shouldLoadParts = this.shouldLoadParts(level.details, frag.end);
|
12427
|
-
if (shouldLoadParts !== this.loadingParts) {
|
12428
|
-
this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} loading sn ${fragPrevious == null ? void 0 : fragPrevious.sn}->${frag.sn}`);
|
12429
|
-
this.loadingParts = shouldLoadParts;
|
12430
|
-
}
|
12431
|
-
}
|
12432
12311
|
targetBufferTime = Math.max(frag.start, targetBufferTime || 0);
|
12433
|
-
if (this.
|
12312
|
+
if (this.config.lowLatencyMode && frag.sn !== 'initSegment') {
|
12434
12313
|
const partList = details.partList;
|
12435
12314
|
if (partList && progressCallback) {
|
12436
12315
|
if (targetBufferTime > frag.end && details.fragmentHint) {
|
@@ -12439,7 +12318,7 @@ class BaseStreamController extends TaskLoop {
|
|
12439
12318
|
const partIndex = this.getNextPart(partList, frag, targetBufferTime);
|
12440
12319
|
if (partIndex > -1) {
|
12441
12320
|
const part = partList[partIndex];
|
12442
|
-
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.
|
12321
|
+
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))}`);
|
12443
12322
|
this.nextLoadPosition = part.start + part.duration;
|
12444
12323
|
this.state = State.FRAG_LOADING;
|
12445
12324
|
let _result;
|
@@ -12468,14 +12347,7 @@ class BaseStreamController extends TaskLoop {
|
|
12468
12347
|
}
|
12469
12348
|
}
|
12470
12349
|
}
|
12471
|
-
|
12472
|
-
this.log(`LL-Part loading OFF after next part miss @${targetBufferTime.toFixed(2)}`);
|
12473
|
-
this.loadingParts = false;
|
12474
|
-
} else if (!frag.url) {
|
12475
|
-
// Selected fragment hint for part but not loading parts
|
12476
|
-
return Promise.resolve(null);
|
12477
|
-
}
|
12478
|
-
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))}`);
|
12350
|
+
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))}`);
|
12479
12351
|
// Don't update nextLoadPosition for fragments which are not buffered
|
12480
12352
|
if (isFiniteNumber(frag.sn) && !this.bitrateTest) {
|
12481
12353
|
this.nextLoadPosition = frag.start + frag.duration;
|
@@ -12573,36 +12445,8 @@ class BaseStreamController extends TaskLoop {
|
|
12573
12445
|
if (part) {
|
12574
12446
|
part.stats.parsing.end = now;
|
12575
12447
|
}
|
12576
|
-
// See if part loading should be disabled/enabled based on buffer and playback position.
|
12577
|
-
if (frag.sn !== 'initSegment') {
|
12578
|
-
const levelDetails = this.getLevelDetails();
|
12579
|
-
const loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
|
12580
|
-
const shouldLoadParts = loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
|
12581
|
-
if (shouldLoadParts !== this.loadingParts) {
|
12582
|
-
this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} after parsing segment ending @${frag.end.toFixed(2)}`);
|
12583
|
-
this.loadingParts = shouldLoadParts;
|
12584
|
-
}
|
12585
|
-
}
|
12586
12448
|
this.updateLevelTiming(frag, part, level, chunkMeta.partial);
|
12587
12449
|
}
|
12588
|
-
shouldLoadParts(details, bufferEnd) {
|
12589
|
-
if (this.config.lowLatencyMode) {
|
12590
|
-
if (!details) {
|
12591
|
-
return this.loadingParts;
|
12592
|
-
}
|
12593
|
-
if (details != null && details.partList) {
|
12594
|
-
var _details$fragmentHint;
|
12595
|
-
// Buffer must be ahead of first part + duration of parts after last segment
|
12596
|
-
// and playback must be at or past segment adjacent to part list
|
12597
|
-
const firstPart = details.partList[0];
|
12598
|
-
const safePartStart = firstPart.end + (((_details$fragmentHint = details.fragmentHint) == null ? void 0 : _details$fragmentHint.duration) || 0);
|
12599
|
-
if (bufferEnd >= safePartStart && this.lastCurrentTime > firstPart.start - firstPart.fragment.duration) {
|
12600
|
-
return true;
|
12601
|
-
}
|
12602
|
-
}
|
12603
|
-
}
|
12604
|
-
return false;
|
12605
|
-
}
|
12606
12450
|
getCurrentContext(chunkMeta) {
|
12607
12451
|
const {
|
12608
12452
|
levels,
|
@@ -12751,8 +12595,7 @@ class BaseStreamController extends TaskLoop {
|
|
12751
12595
|
config
|
12752
12596
|
} = this;
|
12753
12597
|
const start = fragments[0].start;
|
12754
|
-
|
12755
|
-
let frag = null;
|
12598
|
+
let frag;
|
12756
12599
|
if (levelDetails.live) {
|
12757
12600
|
const initialLiveManifestSize = config.initialLiveManifestSize;
|
12758
12601
|
if (fragLen < initialLiveManifestSize) {
|
@@ -12764,10 +12607,6 @@ class BaseStreamController extends TaskLoop {
|
|
12764
12607
|
// Do not load using live logic if the starting frag is requested - we want to use getFragmentAtPosition() so that
|
12765
12608
|
// we get the fragment matching that start time
|
12766
12609
|
if (!levelDetails.PTSKnown && !this.startFragRequested && this.startPosition === -1 || pos < start) {
|
12767
|
-
if (canLoadParts && !this.loadingParts) {
|
12768
|
-
this.log(`LL-Part loading ON for initial live fragment`);
|
12769
|
-
this.loadingParts = true;
|
12770
|
-
}
|
12771
12610
|
frag = this.getInitialLiveFragment(levelDetails, fragments);
|
12772
12611
|
this.startPosition = this.nextLoadPosition = frag ? this.hls.liveSyncPosition || frag.start : pos;
|
12773
12612
|
}
|
@@ -12778,7 +12617,7 @@ class BaseStreamController extends TaskLoop {
|
|
12778
12617
|
|
12779
12618
|
// If we haven't run into any special cases already, just load the fragment most closely matching the requested position
|
12780
12619
|
if (!frag) {
|
12781
|
-
const end =
|
12620
|
+
const end = config.lowLatencyMode ? levelDetails.partEnd : levelDetails.fragmentEnd;
|
12782
12621
|
frag = this.getFragmentAtPosition(pos, end, levelDetails);
|
12783
12622
|
}
|
12784
12623
|
return this.mapToInitFragWhenRequired(frag);
|
@@ -12900,7 +12739,7 @@ class BaseStreamController extends TaskLoop {
|
|
12900
12739
|
} = levelDetails;
|
12901
12740
|
const tolerance = config.maxFragLookUpTolerance;
|
12902
12741
|
const partList = levelDetails.partList;
|
12903
|
-
const loadingParts = !!(
|
12742
|
+
const loadingParts = !!(config.lowLatencyMode && partList != null && partList.length && fragmentHint);
|
12904
12743
|
if (loadingParts && fragmentHint && !this.bitrateTest) {
|
12905
12744
|
// Include incomplete fragment with parts at end
|
12906
12745
|
fragments = fragments.concat(fragmentHint);
|
@@ -13093,7 +12932,7 @@ class BaseStreamController extends TaskLoop {
|
|
13093
12932
|
errorAction.resolved = true;
|
13094
12933
|
}
|
13095
12934
|
} else {
|
13096
|
-
|
12935
|
+
logger.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
|
13097
12936
|
return;
|
13098
12937
|
}
|
13099
12938
|
} else if ((errorAction == null ? void 0 : errorAction.action) === NetworkErrorAction.SendAlternateToPenaltyBox) {
|
@@ -13488,7 +13327,6 @@ const initPTSFn = (timestamp, timeOffset, initPTS) => {
|
|
13488
13327
|
*/
|
13489
13328
|
function getAudioConfig(observer, data, offset, audioCodec) {
|
13490
13329
|
let adtsObjectType;
|
13491
|
-
let originalAdtsObjectType;
|
13492
13330
|
let adtsExtensionSamplingIndex;
|
13493
13331
|
let adtsChannelConfig;
|
13494
13332
|
let config;
|
@@ -13496,7 +13334,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
13496
13334
|
const manifestCodec = audioCodec;
|
13497
13335
|
const adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
|
13498
13336
|
// byte 2
|
13499
|
-
adtsObjectType =
|
13337
|
+
adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
|
13500
13338
|
const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
|
13501
13339
|
if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
|
13502
13340
|
const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
|
@@ -13513,8 +13351,8 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
13513
13351
|
// byte 3
|
13514
13352
|
adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6;
|
13515
13353
|
logger.log(`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`);
|
13516
|
-
//
|
13517
|
-
if (/firefox
|
13354
|
+
// firefox: freq less than 24kHz = AAC SBR (HE-AAC)
|
13355
|
+
if (/firefox/i.test(userAgent)) {
|
13518
13356
|
if (adtsSamplingIndex >= 6) {
|
13519
13357
|
adtsObjectType = 5;
|
13520
13358
|
config = new Array(4);
|
@@ -13608,7 +13446,6 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
13608
13446
|
samplerate: adtsSamplingRates[adtsSamplingIndex],
|
13609
13447
|
channelCount: adtsChannelConfig,
|
13610
13448
|
codec: 'mp4a.40.' + adtsObjectType,
|
13611
|
-
parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
|
13612
13449
|
manifestCodec
|
13613
13450
|
};
|
13614
13451
|
}
|
@@ -13663,8 +13500,7 @@ function initTrackConfig(track, observer, data, offset, audioCodec) {
|
|
13663
13500
|
track.channelCount = config.channelCount;
|
13664
13501
|
track.codec = config.codec;
|
13665
13502
|
track.manifestCodec = config.manifestCodec;
|
13666
|
-
track.
|
13667
|
-
logger.log(`parsed codec:${track.parsedCodec}, codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
|
13503
|
+
logger.log(`parsed codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
|
13668
13504
|
}
|
13669
13505
|
}
|
13670
13506
|
function getFrameDuration(samplerate) {
|
@@ -14142,110 +13978,6 @@ class BaseVideoParser {
|
|
14142
13978
|
logger.log(VideoSample.pts + '/' + VideoSample.dts + ':' + VideoSample.debug);
|
14143
13979
|
}
|
14144
13980
|
}
|
14145
|
-
parseNALu(track, array) {
|
14146
|
-
const len = array.byteLength;
|
14147
|
-
let state = track.naluState || 0;
|
14148
|
-
const lastState = state;
|
14149
|
-
const units = [];
|
14150
|
-
let i = 0;
|
14151
|
-
let value;
|
14152
|
-
let overflow;
|
14153
|
-
let unitType;
|
14154
|
-
let lastUnitStart = -1;
|
14155
|
-
let lastUnitType = 0;
|
14156
|
-
// logger.log('PES:' + Hex.hexDump(array));
|
14157
|
-
|
14158
|
-
if (state === -1) {
|
14159
|
-
// special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
|
14160
|
-
lastUnitStart = 0;
|
14161
|
-
// NALu type is value read from offset 0
|
14162
|
-
lastUnitType = this.getNALuType(array, 0);
|
14163
|
-
state = 0;
|
14164
|
-
i = 1;
|
14165
|
-
}
|
14166
|
-
while (i < len) {
|
14167
|
-
value = array[i++];
|
14168
|
-
// optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
|
14169
|
-
if (!state) {
|
14170
|
-
state = value ? 0 : 1;
|
14171
|
-
continue;
|
14172
|
-
}
|
14173
|
-
if (state === 1) {
|
14174
|
-
state = value ? 0 : 2;
|
14175
|
-
continue;
|
14176
|
-
}
|
14177
|
-
// here we have state either equal to 2 or 3
|
14178
|
-
if (!value) {
|
14179
|
-
state = 3;
|
14180
|
-
} else if (value === 1) {
|
14181
|
-
overflow = i - state - 1;
|
14182
|
-
if (lastUnitStart >= 0) {
|
14183
|
-
const unit = {
|
14184
|
-
data: array.subarray(lastUnitStart, overflow),
|
14185
|
-
type: lastUnitType
|
14186
|
-
};
|
14187
|
-
// logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
|
14188
|
-
units.push(unit);
|
14189
|
-
} else {
|
14190
|
-
// lastUnitStart is undefined => this is the first start code found in this PES packet
|
14191
|
-
// first check if start code delimiter is overlapping between 2 PES packets,
|
14192
|
-
// ie it started in last packet (lastState not zero)
|
14193
|
-
// and ended at the beginning of this PES packet (i <= 4 - lastState)
|
14194
|
-
const lastUnit = this.getLastNalUnit(track.samples);
|
14195
|
-
if (lastUnit) {
|
14196
|
-
if (lastState && i <= 4 - lastState) {
|
14197
|
-
// start delimiter overlapping between PES packets
|
14198
|
-
// strip start delimiter bytes from the end of last NAL unit
|
14199
|
-
// check if lastUnit had a state different from zero
|
14200
|
-
if (lastUnit.state) {
|
14201
|
-
// strip last bytes
|
14202
|
-
lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
|
14203
|
-
}
|
14204
|
-
}
|
14205
|
-
// If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
|
14206
|
-
|
14207
|
-
if (overflow > 0) {
|
14208
|
-
// logger.log('first NALU found with overflow:' + overflow);
|
14209
|
-
lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
|
14210
|
-
lastUnit.state = 0;
|
14211
|
-
}
|
14212
|
-
}
|
14213
|
-
}
|
14214
|
-
// check if we can read unit type
|
14215
|
-
if (i < len) {
|
14216
|
-
unitType = this.getNALuType(array, i);
|
14217
|
-
// logger.log('find NALU @ offset:' + i + ',type:' + unitType);
|
14218
|
-
lastUnitStart = i;
|
14219
|
-
lastUnitType = unitType;
|
14220
|
-
state = 0;
|
14221
|
-
} else {
|
14222
|
-
// not enough byte to read unit type. let's read it on next PES parsing
|
14223
|
-
state = -1;
|
14224
|
-
}
|
14225
|
-
} else {
|
14226
|
-
state = 0;
|
14227
|
-
}
|
14228
|
-
}
|
14229
|
-
if (lastUnitStart >= 0 && state >= 0) {
|
14230
|
-
const unit = {
|
14231
|
-
data: array.subarray(lastUnitStart, len),
|
14232
|
-
type: lastUnitType,
|
14233
|
-
state: state
|
14234
|
-
};
|
14235
|
-
units.push(unit);
|
14236
|
-
// logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
|
14237
|
-
}
|
14238
|
-
// no NALu found
|
14239
|
-
if (units.length === 0) {
|
14240
|
-
// append pes.data to previous NAL unit
|
14241
|
-
const lastUnit = this.getLastNalUnit(track.samples);
|
14242
|
-
if (lastUnit) {
|
14243
|
-
lastUnit.data = appendUint8Array(lastUnit.data, array);
|
14244
|
-
}
|
14245
|
-
}
|
14246
|
-
track.naluState = state;
|
14247
|
-
return units;
|
14248
|
-
}
|
14249
13981
|
}
|
14250
13982
|
|
14251
13983
|
/**
|
@@ -14388,11 +14120,194 @@ class ExpGolomb {
|
|
14388
14120
|
readUInt() {
|
14389
14121
|
return this.readBits(32);
|
14390
14122
|
}
|
14123
|
+
|
14124
|
+
/**
|
14125
|
+
* Advance the ExpGolomb decoder past a scaling list. The scaling
|
14126
|
+
* list is optionally transmitted as part of a sequence parameter
|
14127
|
+
* set and is not relevant to transmuxing.
|
14128
|
+
* @param count the number of entries in this scaling list
|
14129
|
+
* @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
|
14130
|
+
*/
|
14131
|
+
skipScalingList(count) {
|
14132
|
+
let lastScale = 8;
|
14133
|
+
let nextScale = 8;
|
14134
|
+
let deltaScale;
|
14135
|
+
for (let j = 0; j < count; j++) {
|
14136
|
+
if (nextScale !== 0) {
|
14137
|
+
deltaScale = this.readEG();
|
14138
|
+
nextScale = (lastScale + deltaScale + 256) % 256;
|
14139
|
+
}
|
14140
|
+
lastScale = nextScale === 0 ? lastScale : nextScale;
|
14141
|
+
}
|
14142
|
+
}
|
14143
|
+
|
14144
|
+
/**
|
14145
|
+
* Read a sequence parameter set and return some interesting video
|
14146
|
+
* properties. A sequence parameter set is the H264 metadata that
|
14147
|
+
* describes the properties of upcoming video frames.
|
14148
|
+
* @returns an object with configuration parsed from the
|
14149
|
+
* sequence parameter set, including the dimensions of the
|
14150
|
+
* associated video frames.
|
14151
|
+
*/
|
14152
|
+
readSPS() {
|
14153
|
+
let frameCropLeftOffset = 0;
|
14154
|
+
let frameCropRightOffset = 0;
|
14155
|
+
let frameCropTopOffset = 0;
|
14156
|
+
let frameCropBottomOffset = 0;
|
14157
|
+
let numRefFramesInPicOrderCntCycle;
|
14158
|
+
let scalingListCount;
|
14159
|
+
let i;
|
14160
|
+
const readUByte = this.readUByte.bind(this);
|
14161
|
+
const readBits = this.readBits.bind(this);
|
14162
|
+
const readUEG = this.readUEG.bind(this);
|
14163
|
+
const readBoolean = this.readBoolean.bind(this);
|
14164
|
+
const skipBits = this.skipBits.bind(this);
|
14165
|
+
const skipEG = this.skipEG.bind(this);
|
14166
|
+
const skipUEG = this.skipUEG.bind(this);
|
14167
|
+
const skipScalingList = this.skipScalingList.bind(this);
|
14168
|
+
readUByte();
|
14169
|
+
const profileIdc = readUByte(); // profile_idc
|
14170
|
+
readBits(5); // profileCompat constraint_set[0-4]_flag, u(5)
|
14171
|
+
skipBits(3); // reserved_zero_3bits u(3),
|
14172
|
+
readUByte(); // level_idc u(8)
|
14173
|
+
skipUEG(); // seq_parameter_set_id
|
14174
|
+
// some profiles have more optional data we don't need
|
14175
|
+
if (profileIdc === 100 || profileIdc === 110 || profileIdc === 122 || profileIdc === 244 || profileIdc === 44 || profileIdc === 83 || profileIdc === 86 || profileIdc === 118 || profileIdc === 128) {
|
14176
|
+
const chromaFormatIdc = readUEG();
|
14177
|
+
if (chromaFormatIdc === 3) {
|
14178
|
+
skipBits(1);
|
14179
|
+
} // separate_colour_plane_flag
|
14180
|
+
|
14181
|
+
skipUEG(); // bit_depth_luma_minus8
|
14182
|
+
skipUEG(); // bit_depth_chroma_minus8
|
14183
|
+
skipBits(1); // qpprime_y_zero_transform_bypass_flag
|
14184
|
+
if (readBoolean()) {
|
14185
|
+
// seq_scaling_matrix_present_flag
|
14186
|
+
scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
|
14187
|
+
for (i = 0; i < scalingListCount; i++) {
|
14188
|
+
if (readBoolean()) {
|
14189
|
+
// seq_scaling_list_present_flag[ i ]
|
14190
|
+
if (i < 6) {
|
14191
|
+
skipScalingList(16);
|
14192
|
+
} else {
|
14193
|
+
skipScalingList(64);
|
14194
|
+
}
|
14195
|
+
}
|
14196
|
+
}
|
14197
|
+
}
|
14198
|
+
}
|
14199
|
+
skipUEG(); // log2_max_frame_num_minus4
|
14200
|
+
const picOrderCntType = readUEG();
|
14201
|
+
if (picOrderCntType === 0) {
|
14202
|
+
readUEG(); // log2_max_pic_order_cnt_lsb_minus4
|
14203
|
+
} else if (picOrderCntType === 1) {
|
14204
|
+
skipBits(1); // delta_pic_order_always_zero_flag
|
14205
|
+
skipEG(); // offset_for_non_ref_pic
|
14206
|
+
skipEG(); // offset_for_top_to_bottom_field
|
14207
|
+
numRefFramesInPicOrderCntCycle = readUEG();
|
14208
|
+
for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
|
14209
|
+
skipEG();
|
14210
|
+
} // offset_for_ref_frame[ i ]
|
14211
|
+
}
|
14212
|
+
skipUEG(); // max_num_ref_frames
|
14213
|
+
skipBits(1); // gaps_in_frame_num_value_allowed_flag
|
14214
|
+
const picWidthInMbsMinus1 = readUEG();
|
14215
|
+
const picHeightInMapUnitsMinus1 = readUEG();
|
14216
|
+
const frameMbsOnlyFlag = readBits(1);
|
14217
|
+
if (frameMbsOnlyFlag === 0) {
|
14218
|
+
skipBits(1);
|
14219
|
+
} // mb_adaptive_frame_field_flag
|
14220
|
+
|
14221
|
+
skipBits(1); // direct_8x8_inference_flag
|
14222
|
+
if (readBoolean()) {
|
14223
|
+
// frame_cropping_flag
|
14224
|
+
frameCropLeftOffset = readUEG();
|
14225
|
+
frameCropRightOffset = readUEG();
|
14226
|
+
frameCropTopOffset = readUEG();
|
14227
|
+
frameCropBottomOffset = readUEG();
|
14228
|
+
}
|
14229
|
+
let pixelRatio = [1, 1];
|
14230
|
+
if (readBoolean()) {
|
14231
|
+
// vui_parameters_present_flag
|
14232
|
+
if (readBoolean()) {
|
14233
|
+
// aspect_ratio_info_present_flag
|
14234
|
+
const aspectRatioIdc = readUByte();
|
14235
|
+
switch (aspectRatioIdc) {
|
14236
|
+
case 1:
|
14237
|
+
pixelRatio = [1, 1];
|
14238
|
+
break;
|
14239
|
+
case 2:
|
14240
|
+
pixelRatio = [12, 11];
|
14241
|
+
break;
|
14242
|
+
case 3:
|
14243
|
+
pixelRatio = [10, 11];
|
14244
|
+
break;
|
14245
|
+
case 4:
|
14246
|
+
pixelRatio = [16, 11];
|
14247
|
+
break;
|
14248
|
+
case 5:
|
14249
|
+
pixelRatio = [40, 33];
|
14250
|
+
break;
|
14251
|
+
case 6:
|
14252
|
+
pixelRatio = [24, 11];
|
14253
|
+
break;
|
14254
|
+
case 7:
|
14255
|
+
pixelRatio = [20, 11];
|
14256
|
+
break;
|
14257
|
+
case 8:
|
14258
|
+
pixelRatio = [32, 11];
|
14259
|
+
break;
|
14260
|
+
case 9:
|
14261
|
+
pixelRatio = [80, 33];
|
14262
|
+
break;
|
14263
|
+
case 10:
|
14264
|
+
pixelRatio = [18, 11];
|
14265
|
+
break;
|
14266
|
+
case 11:
|
14267
|
+
pixelRatio = [15, 11];
|
14268
|
+
break;
|
14269
|
+
case 12:
|
14270
|
+
pixelRatio = [64, 33];
|
14271
|
+
break;
|
14272
|
+
case 13:
|
14273
|
+
pixelRatio = [160, 99];
|
14274
|
+
break;
|
14275
|
+
case 14:
|
14276
|
+
pixelRatio = [4, 3];
|
14277
|
+
break;
|
14278
|
+
case 15:
|
14279
|
+
pixelRatio = [3, 2];
|
14280
|
+
break;
|
14281
|
+
case 16:
|
14282
|
+
pixelRatio = [2, 1];
|
14283
|
+
break;
|
14284
|
+
case 255:
|
14285
|
+
{
|
14286
|
+
pixelRatio = [readUByte() << 8 | readUByte(), readUByte() << 8 | readUByte()];
|
14287
|
+
break;
|
14288
|
+
}
|
14289
|
+
}
|
14290
|
+
}
|
14291
|
+
}
|
14292
|
+
return {
|
14293
|
+
width: Math.ceil((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2),
|
14294
|
+
height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - (frameMbsOnlyFlag ? 2 : 4) * (frameCropTopOffset + frameCropBottomOffset),
|
14295
|
+
pixelRatio: pixelRatio
|
14296
|
+
};
|
14297
|
+
}
|
14298
|
+
readSliceType() {
|
14299
|
+
// skip NALu type
|
14300
|
+
this.readUByte();
|
14301
|
+
// discard first_mb_in_slice
|
14302
|
+
this.readUEG();
|
14303
|
+
// return slice_type
|
14304
|
+
return this.readUEG();
|
14305
|
+
}
|
14391
14306
|
}
|
14392
14307
|
|
14393
14308
|
class AvcVideoParser extends BaseVideoParser {
|
14394
|
-
|
14395
|
-
const units = this.
|
14309
|
+
parseAVCPES(track, textTrack, pes, last, duration) {
|
14310
|
+
const units = this.parseAVCNALu(track, pes.data);
|
14396
14311
|
let VideoSample = this.VideoSample;
|
14397
14312
|
let push;
|
14398
14313
|
let spsfound = false;
|
@@ -14417,7 +14332,7 @@ class AvcVideoParser extends BaseVideoParser {
|
|
14417
14332
|
// only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
|
14418
14333
|
if (spsfound && data.length > 4) {
|
14419
14334
|
// retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
|
14420
|
-
const sliceType =
|
14335
|
+
const sliceType = new ExpGolomb(data).readSliceType();
|
14421
14336
|
// 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
|
14422
14337
|
// SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
|
14423
14338
|
// An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
|
@@ -14471,7 +14386,8 @@ class AvcVideoParser extends BaseVideoParser {
|
|
14471
14386
|
push = true;
|
14472
14387
|
spsfound = true;
|
14473
14388
|
const sps = unit.data;
|
14474
|
-
const
|
14389
|
+
const expGolombDecoder = new ExpGolomb(sps);
|
14390
|
+
const config = expGolombDecoder.readSPS();
|
14475
14391
|
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]) {
|
14476
14392
|
track.width = config.width;
|
14477
14393
|
track.height = config.height;
|
@@ -14527,192 +14443,109 @@ class AvcVideoParser extends BaseVideoParser {
|
|
14527
14443
|
this.VideoSample = null;
|
14528
14444
|
}
|
14529
14445
|
}
|
14530
|
-
|
14531
|
-
|
14532
|
-
|
14533
|
-
|
14534
|
-
const
|
14535
|
-
|
14536
|
-
|
14537
|
-
|
14538
|
-
|
14539
|
-
|
14540
|
-
|
14541
|
-
|
14446
|
+
parseAVCNALu(track, array) {
|
14447
|
+
const len = array.byteLength;
|
14448
|
+
let state = track.naluState || 0;
|
14449
|
+
const lastState = state;
|
14450
|
+
const units = [];
|
14451
|
+
let i = 0;
|
14452
|
+
let value;
|
14453
|
+
let overflow;
|
14454
|
+
let unitType;
|
14455
|
+
let lastUnitStart = -1;
|
14456
|
+
let lastUnitType = 0;
|
14457
|
+
// logger.log('PES:' + Hex.hexDump(array));
|
14542
14458
|
|
14543
|
-
|
14544
|
-
|
14545
|
-
|
14546
|
-
|
14547
|
-
|
14548
|
-
|
14549
|
-
|
14550
|
-
let lastScale = 8;
|
14551
|
-
let nextScale = 8;
|
14552
|
-
let deltaScale;
|
14553
|
-
for (let j = 0; j < count; j++) {
|
14554
|
-
if (nextScale !== 0) {
|
14555
|
-
deltaScale = reader.readEG();
|
14556
|
-
nextScale = (lastScale + deltaScale + 256) % 256;
|
14557
|
-
}
|
14558
|
-
lastScale = nextScale === 0 ? lastScale : nextScale;
|
14459
|
+
if (state === -1) {
|
14460
|
+
// special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
|
14461
|
+
lastUnitStart = 0;
|
14462
|
+
// NALu type is value read from offset 0
|
14463
|
+
lastUnitType = array[0] & 0x1f;
|
14464
|
+
state = 0;
|
14465
|
+
i = 1;
|
14559
14466
|
}
|
14560
|
-
|
14561
|
-
|
14562
|
-
|
14563
|
-
|
14564
|
-
|
14565
|
-
|
14566
|
-
|
14567
|
-
|
14568
|
-
|
14569
|
-
|
14570
|
-
|
14571
|
-
|
14572
|
-
|
14573
|
-
|
14574
|
-
|
14575
|
-
|
14576
|
-
|
14577
|
-
|
14578
|
-
|
14579
|
-
|
14580
|
-
|
14581
|
-
|
14582
|
-
|
14583
|
-
|
14584
|
-
|
14585
|
-
|
14586
|
-
|
14587
|
-
|
14588
|
-
|
14589
|
-
|
14590
|
-
|
14591
|
-
|
14592
|
-
|
14593
|
-
|
14594
|
-
|
14595
|
-
|
14596
|
-
|
14597
|
-
|
14598
|
-
|
14599
|
-
|
14600
|
-
|
14601
|
-
|
14602
|
-
|
14603
|
-
|
14604
|
-
|
14605
|
-
scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
|
14606
|
-
for (i = 0; i < scalingListCount; i++) {
|
14607
|
-
if (readBoolean()) {
|
14608
|
-
// seq_scaling_list_present_flag[ i ]
|
14609
|
-
if (i < 6) {
|
14610
|
-
skipScalingList(16, eg);
|
14611
|
-
} else {
|
14612
|
-
skipScalingList(64, eg);
|
14467
|
+
while (i < len) {
|
14468
|
+
value = array[i++];
|
14469
|
+
// optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
|
14470
|
+
if (!state) {
|
14471
|
+
state = value ? 0 : 1;
|
14472
|
+
continue;
|
14473
|
+
}
|
14474
|
+
if (state === 1) {
|
14475
|
+
state = value ? 0 : 2;
|
14476
|
+
continue;
|
14477
|
+
}
|
14478
|
+
// here we have state either equal to 2 or 3
|
14479
|
+
if (!value) {
|
14480
|
+
state = 3;
|
14481
|
+
} else if (value === 1) {
|
14482
|
+
overflow = i - state - 1;
|
14483
|
+
if (lastUnitStart >= 0) {
|
14484
|
+
const unit = {
|
14485
|
+
data: array.subarray(lastUnitStart, overflow),
|
14486
|
+
type: lastUnitType
|
14487
|
+
};
|
14488
|
+
// logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
|
14489
|
+
units.push(unit);
|
14490
|
+
} else {
|
14491
|
+
// lastUnitStart is undefined => this is the first start code found in this PES packet
|
14492
|
+
// first check if start code delimiter is overlapping between 2 PES packets,
|
14493
|
+
// ie it started in last packet (lastState not zero)
|
14494
|
+
// and ended at the beginning of this PES packet (i <= 4 - lastState)
|
14495
|
+
const lastUnit = this.getLastNalUnit(track.samples);
|
14496
|
+
if (lastUnit) {
|
14497
|
+
if (lastState && i <= 4 - lastState) {
|
14498
|
+
// start delimiter overlapping between PES packets
|
14499
|
+
// strip start delimiter bytes from the end of last NAL unit
|
14500
|
+
// check if lastUnit had a state different from zero
|
14501
|
+
if (lastUnit.state) {
|
14502
|
+
// strip last bytes
|
14503
|
+
lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
|
14504
|
+
}
|
14505
|
+
}
|
14506
|
+
// If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
|
14507
|
+
|
14508
|
+
if (overflow > 0) {
|
14509
|
+
// logger.log('first NALU found with overflow:' + overflow);
|
14510
|
+
lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
|
14511
|
+
lastUnit.state = 0;
|
14613
14512
|
}
|
14614
14513
|
}
|
14615
14514
|
}
|
14515
|
+
// check if we can read unit type
|
14516
|
+
if (i < len) {
|
14517
|
+
unitType = array[i] & 0x1f;
|
14518
|
+
// logger.log('find NALU @ offset:' + i + ',type:' + unitType);
|
14519
|
+
lastUnitStart = i;
|
14520
|
+
lastUnitType = unitType;
|
14521
|
+
state = 0;
|
14522
|
+
} else {
|
14523
|
+
// not enough byte to read unit type. let's read it on next PES parsing
|
14524
|
+
state = -1;
|
14525
|
+
}
|
14526
|
+
} else {
|
14527
|
+
state = 0;
|
14616
14528
|
}
|
14617
14529
|
}
|
14618
|
-
|
14619
|
-
|
14620
|
-
|
14621
|
-
|
14622
|
-
|
14623
|
-
|
14624
|
-
|
14625
|
-
|
14626
|
-
numRefFramesInPicOrderCntCycle = readUEG();
|
14627
|
-
for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
|
14628
|
-
skipEG();
|
14629
|
-
} // offset_for_ref_frame[ i ]
|
14630
|
-
}
|
14631
|
-
skipUEG(); // max_num_ref_frames
|
14632
|
-
skipBits(1); // gaps_in_frame_num_value_allowed_flag
|
14633
|
-
const picWidthInMbsMinus1 = readUEG();
|
14634
|
-
const picHeightInMapUnitsMinus1 = readUEG();
|
14635
|
-
const frameMbsOnlyFlag = readBits(1);
|
14636
|
-
if (frameMbsOnlyFlag === 0) {
|
14637
|
-
skipBits(1);
|
14638
|
-
} // mb_adaptive_frame_field_flag
|
14639
|
-
|
14640
|
-
skipBits(1); // direct_8x8_inference_flag
|
14641
|
-
if (readBoolean()) {
|
14642
|
-
// frame_cropping_flag
|
14643
|
-
frameCropLeftOffset = readUEG();
|
14644
|
-
frameCropRightOffset = readUEG();
|
14645
|
-
frameCropTopOffset = readUEG();
|
14646
|
-
frameCropBottomOffset = readUEG();
|
14530
|
+
if (lastUnitStart >= 0 && state >= 0) {
|
14531
|
+
const unit = {
|
14532
|
+
data: array.subarray(lastUnitStart, len),
|
14533
|
+
type: lastUnitType,
|
14534
|
+
state: state
|
14535
|
+
};
|
14536
|
+
units.push(unit);
|
14537
|
+
// logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
|
14647
14538
|
}
|
14648
|
-
|
14649
|
-
if (
|
14650
|
-
//
|
14651
|
-
|
14652
|
-
|
14653
|
-
|
14654
|
-
switch (aspectRatioIdc) {
|
14655
|
-
case 1:
|
14656
|
-
pixelRatio = [1, 1];
|
14657
|
-
break;
|
14658
|
-
case 2:
|
14659
|
-
pixelRatio = [12, 11];
|
14660
|
-
break;
|
14661
|
-
case 3:
|
14662
|
-
pixelRatio = [10, 11];
|
14663
|
-
break;
|
14664
|
-
case 4:
|
14665
|
-
pixelRatio = [16, 11];
|
14666
|
-
break;
|
14667
|
-
case 5:
|
14668
|
-
pixelRatio = [40, 33];
|
14669
|
-
break;
|
14670
|
-
case 6:
|
14671
|
-
pixelRatio = [24, 11];
|
14672
|
-
break;
|
14673
|
-
case 7:
|
14674
|
-
pixelRatio = [20, 11];
|
14675
|
-
break;
|
14676
|
-
case 8:
|
14677
|
-
pixelRatio = [32, 11];
|
14678
|
-
break;
|
14679
|
-
case 9:
|
14680
|
-
pixelRatio = [80, 33];
|
14681
|
-
break;
|
14682
|
-
case 10:
|
14683
|
-
pixelRatio = [18, 11];
|
14684
|
-
break;
|
14685
|
-
case 11:
|
14686
|
-
pixelRatio = [15, 11];
|
14687
|
-
break;
|
14688
|
-
case 12:
|
14689
|
-
pixelRatio = [64, 33];
|
14690
|
-
break;
|
14691
|
-
case 13:
|
14692
|
-
pixelRatio = [160, 99];
|
14693
|
-
break;
|
14694
|
-
case 14:
|
14695
|
-
pixelRatio = [4, 3];
|
14696
|
-
break;
|
14697
|
-
case 15:
|
14698
|
-
pixelRatio = [3, 2];
|
14699
|
-
break;
|
14700
|
-
case 16:
|
14701
|
-
pixelRatio = [2, 1];
|
14702
|
-
break;
|
14703
|
-
case 255:
|
14704
|
-
{
|
14705
|
-
pixelRatio = [readUByte() << 8 | readUByte(), readUByte() << 8 | readUByte()];
|
14706
|
-
break;
|
14707
|
-
}
|
14708
|
-
}
|
14539
|
+
// no NALu found
|
14540
|
+
if (units.length === 0) {
|
14541
|
+
// append pes.data to previous NAL unit
|
14542
|
+
const lastUnit = this.getLastNalUnit(track.samples);
|
14543
|
+
if (lastUnit) {
|
14544
|
+
lastUnit.data = appendUint8Array(lastUnit.data, array);
|
14709
14545
|
}
|
14710
14546
|
}
|
14711
|
-
|
14712
|
-
|
14713
|
-
height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - (frameMbsOnlyFlag ? 2 : 4) * (frameCropTopOffset + frameCropBottomOffset),
|
14714
|
-
pixelRatio: pixelRatio
|
14715
|
-
};
|
14547
|
+
track.naluState = state;
|
14548
|
+
return units;
|
14716
14549
|
}
|
14717
14550
|
}
|
14718
14551
|
|
@@ -14730,7 +14563,7 @@ class SampleAesDecrypter {
|
|
14730
14563
|
});
|
14731
14564
|
}
|
14732
14565
|
decryptBuffer(encryptedData) {
|
14733
|
-
return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer
|
14566
|
+
return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer);
|
14734
14567
|
}
|
14735
14568
|
|
14736
14569
|
// AAC - encrypt all full 16 bytes blocks starting from offset 16
|
@@ -14844,7 +14677,7 @@ class TSDemuxer {
|
|
14844
14677
|
this.observer = observer;
|
14845
14678
|
this.config = config;
|
14846
14679
|
this.typeSupported = typeSupported;
|
14847
|
-
this.videoParser =
|
14680
|
+
this.videoParser = new AvcVideoParser();
|
14848
14681
|
}
|
14849
14682
|
static probe(data) {
|
14850
14683
|
const syncOffset = TSDemuxer.syncOffset(data);
|
@@ -15009,16 +14842,7 @@ class TSDemuxer {
|
|
15009
14842
|
case videoPid:
|
15010
14843
|
if (stt) {
|
15011
14844
|
if (videoData && (pes = parsePES(videoData))) {
|
15012
|
-
|
15013
|
-
switch (videoTrack.segmentCodec) {
|
15014
|
-
case 'avc':
|
15015
|
-
this.videoParser = new AvcVideoParser();
|
15016
|
-
break;
|
15017
|
-
}
|
15018
|
-
}
|
15019
|
-
if (this.videoParser !== null) {
|
15020
|
-
this.videoParser.parsePES(videoTrack, textTrack, pes, false, this._duration);
|
15021
|
-
}
|
14845
|
+
this.videoParser.parseAVCPES(videoTrack, textTrack, pes, false, this._duration);
|
15022
14846
|
}
|
15023
14847
|
videoData = {
|
15024
14848
|
data: [],
|
@@ -15180,17 +15004,8 @@ class TSDemuxer {
|
|
15180
15004
|
// try to parse last PES packets
|
15181
15005
|
let pes;
|
15182
15006
|
if (videoData && (pes = parsePES(videoData))) {
|
15183
|
-
|
15184
|
-
|
15185
|
-
case 'avc':
|
15186
|
-
this.videoParser = new AvcVideoParser();
|
15187
|
-
break;
|
15188
|
-
}
|
15189
|
-
}
|
15190
|
-
if (this.videoParser !== null) {
|
15191
|
-
this.videoParser.parsePES(videoTrack, textTrack, pes, true, this._duration);
|
15192
|
-
videoTrack.pesData = null;
|
15193
|
-
}
|
15007
|
+
this.videoParser.parseAVCPES(videoTrack, textTrack, pes, true, this._duration);
|
15008
|
+
videoTrack.pesData = null;
|
15194
15009
|
} else {
|
15195
15010
|
// either avcData null or PES truncated, keep it for next frag parsing
|
15196
15011
|
videoTrack.pesData = videoData;
|
@@ -15493,10 +15308,7 @@ function parsePMT(data, offset, typeSupported, isSampleAes) {
|
|
15493
15308
|
logger.warn('Unsupported EC-3 in M2TS found');
|
15494
15309
|
break;
|
15495
15310
|
case 0x24:
|
15496
|
-
|
15497
|
-
{
|
15498
|
-
logger.warn('Unsupported HEVC in M2TS found');
|
15499
|
-
}
|
15311
|
+
logger.warn('Unsupported HEVC in M2TS found');
|
15500
15312
|
break;
|
15501
15313
|
}
|
15502
15314
|
// move to the next table entry
|
@@ -15719,8 +15531,6 @@ class MP4 {
|
|
15719
15531
|
avc1: [],
|
15720
15532
|
// codingname
|
15721
15533
|
avcC: [],
|
15722
|
-
hvc1: [],
|
15723
|
-
hvcC: [],
|
15724
15534
|
btrt: [],
|
15725
15535
|
dinf: [],
|
15726
15536
|
dref: [],
|
@@ -16145,10 +15955,8 @@ class MP4 {
|
|
16145
15955
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.ac3(track));
|
16146
15956
|
}
|
16147
15957
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track));
|
16148
|
-
} else if (track.segmentCodec === 'avc') {
|
16149
|
-
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
|
16150
15958
|
} else {
|
16151
|
-
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.
|
15959
|
+
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
|
16152
15960
|
}
|
16153
15961
|
}
|
16154
15962
|
static tkhd(track) {
|
@@ -16286,84 +16094,6 @@ class MP4 {
|
|
16286
16094
|
const result = appendUint8Array(MP4.FTYP, movie);
|
16287
16095
|
return result;
|
16288
16096
|
}
|
16289
|
-
static hvc1(track) {
|
16290
|
-
const ps = track.params;
|
16291
|
-
const units = [track.vps, track.sps, track.pps];
|
16292
|
-
const NALuLengthSize = 4;
|
16293
|
-
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]);
|
16294
|
-
|
16295
|
-
// compute hvcC size in bytes
|
16296
|
-
let length = config.length;
|
16297
|
-
for (let i = 0; i < units.length; i += 1) {
|
16298
|
-
length += 3;
|
16299
|
-
for (let j = 0; j < units[i].length; j += 1) {
|
16300
|
-
length += 2 + units[i][j].length;
|
16301
|
-
}
|
16302
|
-
}
|
16303
|
-
const hvcC = new Uint8Array(length);
|
16304
|
-
hvcC.set(config, 0);
|
16305
|
-
length = config.length;
|
16306
|
-
// append parameter set units: one vps, one or more sps and pps
|
16307
|
-
const iMax = units.length - 1;
|
16308
|
-
for (let i = 0; i < units.length; i += 1) {
|
16309
|
-
hvcC.set(new Uint8Array([32 + i | (i === iMax ? 128 : 0), 0x00, units[i].length]), length);
|
16310
|
-
length += 3;
|
16311
|
-
for (let j = 0; j < units[i].length; j += 1) {
|
16312
|
-
hvcC.set(new Uint8Array([units[i][j].length >> 8, units[i][j].length & 255]), length);
|
16313
|
-
length += 2;
|
16314
|
-
hvcC.set(units[i][j], length);
|
16315
|
-
length += units[i][j].length;
|
16316
|
-
}
|
16317
|
-
}
|
16318
|
-
const hvcc = MP4.box(MP4.types.hvcC, hvcC);
|
16319
|
-
const width = track.width;
|
16320
|
-
const height = track.height;
|
16321
|
-
const hSpacing = track.pixelRatio[0];
|
16322
|
-
const vSpacing = track.pixelRatio[1];
|
16323
|
-
return MP4.box(MP4.types.hvc1, new Uint8Array([0x00, 0x00, 0x00,
|
16324
|
-
// reserved
|
16325
|
-
0x00, 0x00, 0x00,
|
16326
|
-
// reserved
|
16327
|
-
0x00, 0x01,
|
16328
|
-
// data_reference_index
|
16329
|
-
0x00, 0x00,
|
16330
|
-
// pre_defined
|
16331
|
-
0x00, 0x00,
|
16332
|
-
// reserved
|
16333
|
-
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
16334
|
-
// pre_defined
|
16335
|
-
width >> 8 & 0xff, width & 0xff,
|
16336
|
-
// width
|
16337
|
-
height >> 8 & 0xff, height & 0xff,
|
16338
|
-
// height
|
16339
|
-
0x00, 0x48, 0x00, 0x00,
|
16340
|
-
// horizresolution
|
16341
|
-
0x00, 0x48, 0x00, 0x00,
|
16342
|
-
// vertresolution
|
16343
|
-
0x00, 0x00, 0x00, 0x00,
|
16344
|
-
// reserved
|
16345
|
-
0x00, 0x01,
|
16346
|
-
// frame_count
|
16347
|
-
0x12, 0x64, 0x61, 0x69, 0x6c,
|
16348
|
-
// dailymotion/hls.js
|
16349
|
-
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,
|
16350
|
-
// compressorname
|
16351
|
-
0x00, 0x18,
|
16352
|
-
// depth = 24
|
16353
|
-
0x11, 0x11]),
|
16354
|
-
// pre_defined = -1
|
16355
|
-
hvcc, MP4.box(MP4.types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80,
|
16356
|
-
// bufferSizeDB
|
16357
|
-
0x00, 0x2d, 0xc6, 0xc0,
|
16358
|
-
// maxBitrate
|
16359
|
-
0x00, 0x2d, 0xc6, 0xc0])),
|
16360
|
-
// avgBitrate
|
16361
|
-
MP4.box(MP4.types.pasp, new Uint8Array([hSpacing >> 24,
|
16362
|
-
// hSpacing
|
16363
|
-
hSpacing >> 16 & 0xff, hSpacing >> 8 & 0xff, hSpacing & 0xff, vSpacing >> 24,
|
16364
|
-
// vSpacing
|
16365
|
-
vSpacing >> 16 & 0xff, vSpacing >> 8 & 0xff, vSpacing & 0xff])));
|
16366
|
-
}
|
16367
16097
|
}
|
16368
16098
|
MP4.types = void 0;
|
16369
16099
|
MP4.HDLR_TYPES = void 0;
|
@@ -16739,9 +16469,9 @@ class MP4Remuxer {
|
|
16739
16469
|
const foundOverlap = delta < -1;
|
16740
16470
|
if (foundHole || foundOverlap) {
|
16741
16471
|
if (foundHole) {
|
16742
|
-
logger.warn(
|
16472
|
+
logger.warn(`AVC: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
|
16743
16473
|
} else {
|
16744
|
-
logger.warn(
|
16474
|
+
logger.warn(`AVC: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
|
16745
16475
|
}
|
16746
16476
|
if (!foundOverlap || nextAvcDts >= inputSamples[0].pts || chromeVersion) {
|
16747
16477
|
firstDTS = nextAvcDts;
|
@@ -16750,24 +16480,12 @@ class MP4Remuxer {
|
|
16750
16480
|
inputSamples[0].dts = firstDTS;
|
16751
16481
|
inputSamples[0].pts = firstPTS;
|
16752
16482
|
} else {
|
16753
|
-
let isPTSOrderRetained = true;
|
16754
16483
|
for (let i = 0; i < inputSamples.length; i++) {
|
16755
|
-
if (inputSamples[i].dts > firstPTS
|
16484
|
+
if (inputSamples[i].dts > firstPTS) {
|
16756
16485
|
break;
|
16757
16486
|
}
|
16758
|
-
const prevPTS = inputSamples[i].pts;
|
16759
16487
|
inputSamples[i].dts -= delta;
|
16760
16488
|
inputSamples[i].pts -= delta;
|
16761
|
-
|
16762
|
-
// check to see if this sample's PTS order has changed
|
16763
|
-
// relative to the next one
|
16764
|
-
if (i < inputSamples.length - 1) {
|
16765
|
-
const nextSamplePTS = inputSamples[i + 1].pts;
|
16766
|
-
const currentSamplePTS = inputSamples[i].pts;
|
16767
|
-
const currentOrder = nextSamplePTS <= currentSamplePTS;
|
16768
|
-
const prevOrder = nextSamplePTS <= prevPTS;
|
16769
|
-
isPTSOrderRetained = currentOrder == prevOrder;
|
16770
|
-
}
|
16771
16489
|
}
|
16772
16490
|
}
|
16773
16491
|
logger.log(`Video: Initial PTS/DTS adjusted: ${toMsFromMpegTsClock(firstPTS, true)}/${toMsFromMpegTsClock(firstDTS, true)}, delta: ${toMsFromMpegTsClock(delta, true)} ms`);
|
@@ -16915,7 +16633,7 @@ class MP4Remuxer {
|
|
16915
16633
|
}
|
16916
16634
|
}
|
16917
16635
|
}
|
16918
|
-
// next AVC
|
16636
|
+
// next AVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
|
16919
16637
|
mp4SampleDuration = stretchedLastFrame || !mp4SampleDuration ? averageSampleDuration : mp4SampleDuration;
|
16920
16638
|
this.nextAvcDts = nextAvcDts = lastDTS + mp4SampleDuration;
|
16921
16639
|
this.videoSampleDuration = mp4SampleDuration;
|
@@ -17048,7 +16766,7 @@ class MP4Remuxer {
|
|
17048
16766
|
logger.warn(`[mp4-remuxer]: Injecting ${missing} audio frame @ ${(nextPts / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`);
|
17049
16767
|
for (let j = 0; j < missing; j++) {
|
17050
16768
|
const newStamp = Math.max(nextPts, 0);
|
17051
|
-
let fillFrame = AAC.getSilentFrame(track.
|
16769
|
+
let fillFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
|
17052
16770
|
if (!fillFrame) {
|
17053
16771
|
logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.');
|
17054
16772
|
fillFrame = sample.unit.subarray();
|
@@ -17176,7 +16894,7 @@ class MP4Remuxer {
|
|
17176
16894
|
// samples count of this segment's duration
|
17177
16895
|
const nbSamples = Math.ceil((endDTS - startDTS) / frameDuration);
|
17178
16896
|
// silent frame
|
17179
|
-
const silentFrame = AAC.getSilentFrame(track.
|
16897
|
+
const silentFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
|
17180
16898
|
logger.warn('[mp4-remuxer]: remux empty Audio');
|
17181
16899
|
// Can't remux if we can't generate a silent frame...
|
17182
16900
|
if (!silentFrame) {
|
@@ -17567,15 +17285,13 @@ class Transmuxer {
|
|
17567
17285
|
initSegmentData
|
17568
17286
|
} = transmuxConfig;
|
17569
17287
|
const keyData = getEncryptionType(uintData, decryptdata);
|
17570
|
-
if (keyData &&
|
17288
|
+
if (keyData && keyData.method === 'AES-128') {
|
17571
17289
|
const decrypter = this.getDecrypter();
|
17572
|
-
const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
|
17573
|
-
|
17574
17290
|
// Software decryption is synchronous; webCrypto is not
|
17575
17291
|
if (decrypter.isSync()) {
|
17576
17292
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
17577
17293
|
// data is handled in the flush() call
|
17578
|
-
let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer
|
17294
|
+
let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer);
|
17579
17295
|
// For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
|
17580
17296
|
const loadingParts = chunkMeta.part > -1;
|
17581
17297
|
if (loadingParts) {
|
@@ -17587,7 +17303,7 @@ class Transmuxer {
|
|
17587
17303
|
}
|
17588
17304
|
uintData = new Uint8Array(decryptedData);
|
17589
17305
|
} else {
|
17590
|
-
this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer
|
17306
|
+
this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer).then(decryptedData => {
|
17591
17307
|
// Calling push here is important; if flush() is called while this is still resolving, this ensures that
|
17592
17308
|
// the decrypted data has been transmuxed
|
17593
17309
|
const result = this.push(decryptedData, null, chunkMeta);
|
@@ -18241,7 +17957,14 @@ class TransmuxerInterface {
|
|
18241
17957
|
this.observer = new EventEmitter();
|
18242
17958
|
this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
|
18243
17959
|
this.observer.on(Events.ERROR, forwardMessage);
|
18244
|
-
const
|
17960
|
+
const MediaSource = getMediaSource(config.preferManagedMediaSource) || {
|
17961
|
+
isTypeSupported: () => false
|
17962
|
+
};
|
17963
|
+
const m2tsTypeSupported = {
|
17964
|
+
mpeg: MediaSource.isTypeSupported('audio/mpeg'),
|
17965
|
+
mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
|
17966
|
+
ac3: false
|
17967
|
+
};
|
18245
17968
|
|
18246
17969
|
// navigator.vendor is not always available in Web Worker
|
18247
17970
|
// refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
|
@@ -18505,9 +18228,8 @@ const STALL_MINIMUM_DURATION_MS = 250;
|
|
18505
18228
|
const MAX_START_GAP_JUMP = 2.0;
|
18506
18229
|
const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
|
18507
18230
|
const SKIP_BUFFER_RANGE_START = 0.05;
|
18508
|
-
class GapController
|
18231
|
+
class GapController {
|
18509
18232
|
constructor(config, media, fragmentTracker, hls) {
|
18510
|
-
super('gap-controller', hls.logger);
|
18511
18233
|
this.config = void 0;
|
18512
18234
|
this.media = null;
|
18513
18235
|
this.fragmentTracker = void 0;
|
@@ -18517,7 +18239,6 @@ class GapController extends Logger {
|
|
18517
18239
|
this.stalled = null;
|
18518
18240
|
this.moved = false;
|
18519
18241
|
this.seeking = false;
|
18520
|
-
this.ended = 0;
|
18521
18242
|
this.config = config;
|
18522
18243
|
this.media = media;
|
18523
18244
|
this.fragmentTracker = fragmentTracker;
|
@@ -18535,7 +18256,7 @@ class GapController extends Logger {
|
|
18535
18256
|
*
|
18536
18257
|
* @param lastCurrentTime - Previously read playhead position
|
18537
18258
|
*/
|
18538
|
-
poll(lastCurrentTime, activeFrag
|
18259
|
+
poll(lastCurrentTime, activeFrag) {
|
18539
18260
|
const {
|
18540
18261
|
config,
|
18541
18262
|
media,
|
@@ -18554,7 +18275,6 @@ class GapController extends Logger {
|
|
18554
18275
|
|
18555
18276
|
// The playhead is moving, no-op
|
18556
18277
|
if (currentTime !== lastCurrentTime) {
|
18557
|
-
this.ended = 0;
|
18558
18278
|
this.moved = true;
|
18559
18279
|
if (!seeking) {
|
18560
18280
|
this.nudgeRetry = 0;
|
@@ -18563,7 +18283,7 @@ class GapController extends Logger {
|
|
18563
18283
|
// The playhead is now moving, but was previously stalled
|
18564
18284
|
if (this.stallReported) {
|
18565
18285
|
const _stalledDuration = self.performance.now() - stalled;
|
18566
|
-
|
18286
|
+
logger.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
|
18567
18287
|
this.stallReported = false;
|
18568
18288
|
}
|
18569
18289
|
this.stalled = null;
|
@@ -18599,6 +18319,7 @@ class GapController extends Logger {
|
|
18599
18319
|
// Skip start gaps if we haven't played, but the last poll detected the start of a stall
|
18600
18320
|
// The addition poll gives the browser a chance to jump the gap for us
|
18601
18321
|
if (!this.moved && this.stalled !== null) {
|
18322
|
+
var _level$details;
|
18602
18323
|
// There is no playable buffer (seeked, waiting for buffer)
|
18603
18324
|
const isBuffered = bufferInfo.len > 0;
|
18604
18325
|
if (!isBuffered && !nextStart) {
|
@@ -18610,8 +18331,9 @@ class GapController extends Logger {
|
|
18610
18331
|
// When joining a live stream with audio tracks, account for live playlist window sliding by allowing
|
18611
18332
|
// a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
|
18612
18333
|
// that begins over 1 target duration after the video start position.
|
18613
|
-
const
|
18614
|
-
const
|
18334
|
+
const level = this.hls.levels ? this.hls.levels[this.hls.currentLevel] : null;
|
18335
|
+
const isLive = level == null ? void 0 : (_level$details = level.details) == null ? void 0 : _level$details.live;
|
18336
|
+
const maxStartGapJump = isLive ? level.details.targetduration * 2 : MAX_START_GAP_JUMP;
|
18615
18337
|
const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
|
18616
18338
|
if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
|
18617
18339
|
if (!media.paused) {
|
@@ -18629,17 +18351,6 @@ class GapController extends Logger {
|
|
18629
18351
|
}
|
18630
18352
|
const stalledDuration = tnow - stalled;
|
18631
18353
|
if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) {
|
18632
|
-
// Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
|
18633
|
-
if (state === State.ENDED && !(levelDetails && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? void 0 : levelDetails.edge) || 0)) < 1) {
|
18634
|
-
if (stalledDuration < 1000 || this.ended) {
|
18635
|
-
return;
|
18636
|
-
}
|
18637
|
-
this.ended = currentTime;
|
18638
|
-
this.hls.trigger(Events.MEDIA_ENDED, {
|
18639
|
-
stalled: true
|
18640
|
-
});
|
18641
|
-
return;
|
18642
|
-
}
|
18643
18354
|
// Report stalling after trying to fix
|
18644
18355
|
this._reportStall(bufferInfo);
|
18645
18356
|
if (!this.media) {
|
@@ -18683,7 +18394,7 @@ class GapController extends Logger {
|
|
18683
18394
|
// needs to cross some sort of threshold covering all source-buffers content
|
18684
18395
|
// to start playing properly.
|
18685
18396
|
if ((bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
|
18686
|
-
|
18397
|
+
logger.warn('Trying to nudge playhead over buffer-hole');
|
18687
18398
|
// Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
|
18688
18399
|
// We only try to jump the hole if it's under the configured size
|
18689
18400
|
// Reset stalled so to rearm watchdog timer
|
@@ -18707,7 +18418,7 @@ class GapController extends Logger {
|
|
18707
18418
|
// Report stalled error once
|
18708
18419
|
this.stallReported = true;
|
18709
18420
|
const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${JSON.stringify(bufferInfo)})`);
|
18710
|
-
|
18421
|
+
logger.warn(error.message);
|
18711
18422
|
hls.trigger(Events.ERROR, {
|
18712
18423
|
type: ErrorTypes.MEDIA_ERROR,
|
18713
18424
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
@@ -18775,7 +18486,7 @@ class GapController extends Logger {
|
|
18775
18486
|
}
|
18776
18487
|
}
|
18777
18488
|
const targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
|
18778
|
-
|
18489
|
+
logger.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
|
18779
18490
|
this.moved = true;
|
18780
18491
|
this.stalled = null;
|
18781
18492
|
media.currentTime = targetTime;
|
@@ -18816,7 +18527,7 @@ class GapController extends Logger {
|
|
18816
18527
|
const targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset;
|
18817
18528
|
// playback stalled in buffered area ... let's nudge currentTime to try to overcome this
|
18818
18529
|
const error = new Error(`Nudging 'currentTime' from ${currentTime} to ${targetTime}`);
|
18819
|
-
|
18530
|
+
logger.warn(error.message);
|
18820
18531
|
media.currentTime = targetTime;
|
18821
18532
|
hls.trigger(Events.ERROR, {
|
18822
18533
|
type: ErrorTypes.MEDIA_ERROR,
|
@@ -18826,7 +18537,7 @@ class GapController extends Logger {
|
|
18826
18537
|
});
|
18827
18538
|
} else {
|
18828
18539
|
const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`);
|
18829
|
-
|
18540
|
+
logger.error(error.message);
|
18830
18541
|
hls.trigger(Events.ERROR, {
|
18831
18542
|
type: ErrorTypes.MEDIA_ERROR,
|
18832
18543
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
@@ -18841,7 +18552,7 @@ const TICK_INTERVAL = 100; // how often to tick in ms
|
|
18841
18552
|
|
18842
18553
|
class StreamController extends BaseStreamController {
|
18843
18554
|
constructor(hls, fragmentTracker, keyLoader) {
|
18844
|
-
super(hls, fragmentTracker, keyLoader, 'stream-controller', PlaylistLevelType.MAIN);
|
18555
|
+
super(hls, fragmentTracker, keyLoader, '[stream-controller]', PlaylistLevelType.MAIN);
|
18845
18556
|
this.audioCodecSwap = false;
|
18846
18557
|
this.gapController = null;
|
18847
18558
|
this.level = -1;
|
@@ -18849,43 +18560,27 @@ class StreamController extends BaseStreamController {
|
|
18849
18560
|
this.altAudio = false;
|
18850
18561
|
this.audioOnly = false;
|
18851
18562
|
this.fragPlaying = null;
|
18563
|
+
this.onvplaying = null;
|
18564
|
+
this.onvseeked = null;
|
18852
18565
|
this.fragLastKbps = 0;
|
18853
18566
|
this.couldBacktrack = false;
|
18854
18567
|
this.backtrackFragment = null;
|
18855
18568
|
this.audioCodecSwitch = false;
|
18856
18569
|
this.videoBuffer = null;
|
18857
|
-
this.
|
18858
|
-
// tick to speed up FRAG_CHANGED triggering
|
18859
|
-
this.tick();
|
18860
|
-
};
|
18861
|
-
this.onMediaSeeked = () => {
|
18862
|
-
const media = this.media;
|
18863
|
-
const currentTime = media ? media.currentTime : null;
|
18864
|
-
if (isFiniteNumber(currentTime)) {
|
18865
|
-
this.log(`Media seeked to ${currentTime.toFixed(3)}`);
|
18866
|
-
}
|
18867
|
-
|
18868
|
-
// If seeked was issued before buffer was appended do not tick immediately
|
18869
|
-
const bufferInfo = this.getMainFwdBufferInfo();
|
18870
|
-
if (bufferInfo === null || bufferInfo.len === 0) {
|
18871
|
-
this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
|
18872
|
-
return;
|
18873
|
-
}
|
18874
|
-
|
18875
|
-
// tick to speed up FRAG_CHANGED triggering
|
18876
|
-
this.tick();
|
18877
|
-
};
|
18878
|
-
this.registerListeners();
|
18570
|
+
this._registerListeners();
|
18879
18571
|
}
|
18880
|
-
|
18881
|
-
super.registerListeners();
|
18572
|
+
_registerListeners() {
|
18882
18573
|
const {
|
18883
18574
|
hls
|
18884
18575
|
} = this;
|
18576
|
+
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
18577
|
+
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
18578
|
+
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
18885
18579
|
hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
18886
18580
|
hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
|
18887
18581
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
18888
18582
|
hls.on(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
|
18583
|
+
hls.on(Events.ERROR, this.onError, this);
|
18889
18584
|
hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
18890
18585
|
hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
18891
18586
|
hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -18893,14 +18588,17 @@ class StreamController extends BaseStreamController {
|
|
18893
18588
|
hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
|
18894
18589
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
18895
18590
|
}
|
18896
|
-
|
18897
|
-
super.unregisterListeners();
|
18591
|
+
_unregisterListeners() {
|
18898
18592
|
const {
|
18899
18593
|
hls
|
18900
18594
|
} = this;
|
18595
|
+
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
18596
|
+
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
18597
|
+
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
18901
18598
|
hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
18902
18599
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
18903
18600
|
hls.off(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
|
18601
|
+
hls.off(Events.ERROR, this.onError, this);
|
18904
18602
|
hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
18905
18603
|
hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
18906
18604
|
hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -18909,9 +18607,7 @@ class StreamController extends BaseStreamController {
|
|
18909
18607
|
hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
18910
18608
|
}
|
18911
18609
|
onHandlerDestroying() {
|
18912
|
-
|
18913
|
-
this.onMediaPlaying = this.onMediaSeeked = null;
|
18914
|
-
this.unregisterListeners();
|
18610
|
+
this._unregisterListeners();
|
18915
18611
|
super.onHandlerDestroying();
|
18916
18612
|
}
|
18917
18613
|
startLoad(startPosition) {
|
@@ -19015,15 +18711,11 @@ class StreamController extends BaseStreamController {
|
|
19015
18711
|
levels,
|
19016
18712
|
media
|
19017
18713
|
} = this;
|
19018
|
-
const {
|
19019
|
-
config,
|
19020
|
-
nextLoadLevel: level
|
19021
|
-
} = hls;
|
19022
18714
|
|
19023
18715
|
// if start level not parsed yet OR
|
19024
18716
|
// if video not attached AND start fragment already requested OR start frag prefetch not enabled
|
19025
18717
|
// exit loop, as we either need more info (level not parsed) or we need media to be attached to load new fragment
|
19026
|
-
if (levelLastLoaded === null || !media && (this.startFragRequested || !config.startFragPrefetch)) {
|
18718
|
+
if (levelLastLoaded === null || !media && (this.startFragRequested || !hls.config.startFragPrefetch)) {
|
19027
18719
|
return;
|
19028
18720
|
}
|
19029
18721
|
|
@@ -19031,7 +18723,8 @@ class StreamController extends BaseStreamController {
|
|
19031
18723
|
if (this.altAudio && this.audioOnly) {
|
19032
18724
|
return;
|
19033
18725
|
}
|
19034
|
-
|
18726
|
+
const level = hls.nextLoadLevel;
|
18727
|
+
if (!(levels != null && levels[level])) {
|
19035
18728
|
return;
|
19036
18729
|
}
|
19037
18730
|
const levelInfo = levels[level];
|
@@ -19239,17 +18932,20 @@ class StreamController extends BaseStreamController {
|
|
19239
18932
|
onMediaAttached(event, data) {
|
19240
18933
|
super.onMediaAttached(event, data);
|
19241
18934
|
const media = data.media;
|
19242
|
-
|
19243
|
-
|
18935
|
+
this.onvplaying = this.onMediaPlaying.bind(this);
|
18936
|
+
this.onvseeked = this.onMediaSeeked.bind(this);
|
18937
|
+
media.addEventListener('playing', this.onvplaying);
|
18938
|
+
media.addEventListener('seeked', this.onvseeked);
|
19244
18939
|
this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
|
19245
18940
|
}
|
19246
18941
|
onMediaDetaching() {
|
19247
18942
|
const {
|
19248
18943
|
media
|
19249
18944
|
} = this;
|
19250
|
-
if (media) {
|
19251
|
-
media.removeEventListener('playing', this.
|
19252
|
-
media.removeEventListener('seeked', this.
|
18945
|
+
if (media && this.onvplaying && this.onvseeked) {
|
18946
|
+
media.removeEventListener('playing', this.onvplaying);
|
18947
|
+
media.removeEventListener('seeked', this.onvseeked);
|
18948
|
+
this.onvplaying = this.onvseeked = null;
|
19253
18949
|
this.videoBuffer = null;
|
19254
18950
|
}
|
19255
18951
|
this.fragPlaying = null;
|
@@ -19259,6 +18955,27 @@ class StreamController extends BaseStreamController {
|
|
19259
18955
|
}
|
19260
18956
|
super.onMediaDetaching();
|
19261
18957
|
}
|
18958
|
+
onMediaPlaying() {
|
18959
|
+
// tick to speed up FRAG_CHANGED triggering
|
18960
|
+
this.tick();
|
18961
|
+
}
|
18962
|
+
onMediaSeeked() {
|
18963
|
+
const media = this.media;
|
18964
|
+
const currentTime = media ? media.currentTime : null;
|
18965
|
+
if (isFiniteNumber(currentTime)) {
|
18966
|
+
this.log(`Media seeked to ${currentTime.toFixed(3)}`);
|
18967
|
+
}
|
18968
|
+
|
18969
|
+
// If seeked was issued before buffer was appended do not tick immediately
|
18970
|
+
const bufferInfo = this.getMainFwdBufferInfo();
|
18971
|
+
if (bufferInfo === null || bufferInfo.len === 0) {
|
18972
|
+
this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
|
18973
|
+
return;
|
18974
|
+
}
|
18975
|
+
|
18976
|
+
// tick to speed up FRAG_CHANGED triggering
|
18977
|
+
this.tick();
|
18978
|
+
}
|
19262
18979
|
onManifestLoading() {
|
19263
18980
|
// reset buffer on manifest loading
|
19264
18981
|
this.log('Trigger BUFFER_RESET');
|
@@ -19550,10 +19267,8 @@ class StreamController extends BaseStreamController {
|
|
19550
19267
|
}
|
19551
19268
|
if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) {
|
19552
19269
|
// Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
|
19553
|
-
const
|
19554
|
-
|
19555
|
-
const levelDetails = this.getLevelDetails();
|
19556
|
-
gapController.poll(this.lastCurrentTime, activeFrag, levelDetails, state);
|
19270
|
+
const activeFrag = this.state !== State.IDLE ? this.fragCurrent : null;
|
19271
|
+
gapController.poll(this.lastCurrentTime, activeFrag);
|
19557
19272
|
}
|
19558
19273
|
this.lastCurrentTime = media.currentTime;
|
19559
19274
|
}
|
@@ -19991,7 +19706,7 @@ class Hls {
|
|
19991
19706
|
* Get the video-dev/hls.js package version.
|
19992
19707
|
*/
|
19993
19708
|
static get version() {
|
19994
|
-
return "1.5.6
|
19709
|
+
return "1.5.6";
|
19995
19710
|
}
|
19996
19711
|
|
19997
19712
|
/**
|
@@ -20054,12 +19769,9 @@ class Hls {
|
|
20054
19769
|
* The configuration object provided on player instantiation.
|
20055
19770
|
*/
|
20056
19771
|
this.userConfig = void 0;
|
20057
|
-
/**
|
20058
|
-
* The logger functions used by this player instance, configured on player instantiation.
|
20059
|
-
*/
|
20060
|
-
this.logger = void 0;
|
20061
19772
|
this.coreComponents = void 0;
|
20062
19773
|
this.networkControllers = void 0;
|
19774
|
+
this.started = false;
|
20063
19775
|
this._emitter = new EventEmitter();
|
20064
19776
|
this._autoLevelCapping = -1;
|
20065
19777
|
this._maxHdcpLevel = null;
|
@@ -20076,11 +19788,11 @@ class Hls {
|
|
20076
19788
|
this._media = null;
|
20077
19789
|
this.url = null;
|
20078
19790
|
this.triggeringException = void 0;
|
20079
|
-
|
20080
|
-
const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig
|
19791
|
+
enableLogs(userConfig.debug || false, 'Hls instance');
|
19792
|
+
const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig);
|
20081
19793
|
this.userConfig = userConfig;
|
20082
19794
|
if (config.progressive) {
|
20083
|
-
enableStreamingMode(config
|
19795
|
+
enableStreamingMode(config);
|
20084
19796
|
}
|
20085
19797
|
|
20086
19798
|
// core controllers and network loaders
|
@@ -20179,7 +19891,7 @@ class Hls {
|
|
20179
19891
|
try {
|
20180
19892
|
return this.emit(event, event, eventObject);
|
20181
19893
|
} catch (error) {
|
20182
|
-
|
19894
|
+
logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
|
20183
19895
|
// Prevent recursion in error event handlers that throw #5497
|
20184
19896
|
if (!this.triggeringException) {
|
20185
19897
|
this.triggeringException = true;
|
@@ -20205,7 +19917,7 @@ class Hls {
|
|
20205
19917
|
* Dispose of the instance
|
20206
19918
|
*/
|
20207
19919
|
destroy() {
|
20208
|
-
|
19920
|
+
logger.log('destroy');
|
20209
19921
|
this.trigger(Events.DESTROYING, undefined);
|
20210
19922
|
this.detachMedia();
|
20211
19923
|
this.removeAllListeners();
|
@@ -20226,7 +19938,7 @@ class Hls {
|
|
20226
19938
|
* Attaches Hls.js to a media element
|
20227
19939
|
*/
|
20228
19940
|
attachMedia(media) {
|
20229
|
-
|
19941
|
+
logger.log('attachMedia');
|
20230
19942
|
this._media = media;
|
20231
19943
|
this.trigger(Events.MEDIA_ATTACHING, {
|
20232
19944
|
media: media
|
@@ -20237,7 +19949,7 @@ class Hls {
|
|
20237
19949
|
* Detach Hls.js from the media
|
20238
19950
|
*/
|
20239
19951
|
detachMedia() {
|
20240
|
-
|
19952
|
+
logger.log('detachMedia');
|
20241
19953
|
this.trigger(Events.MEDIA_DETACHING, undefined);
|
20242
19954
|
this._media = null;
|
20243
19955
|
}
|
@@ -20254,7 +19966,7 @@ class Hls {
|
|
20254
19966
|
});
|
20255
19967
|
this._autoLevelCapping = -1;
|
20256
19968
|
this._maxHdcpLevel = null;
|
20257
|
-
|
19969
|
+
logger.log(`loadSource:${loadingSource}`);
|
20258
19970
|
if (media && loadedSource && (loadedSource !== loadingSource || this.bufferController.hasSourceTypes())) {
|
20259
19971
|
this.detachMedia();
|
20260
19972
|
this.attachMedia(media);
|
@@ -20273,7 +19985,8 @@ class Hls {
|
|
20273
19985
|
* Defaults to -1 (None: starts from earliest point)
|
20274
19986
|
*/
|
20275
19987
|
startLoad(startPosition = -1) {
|
20276
|
-
|
19988
|
+
logger.log(`startLoad(${startPosition})`);
|
19989
|
+
this.started = true;
|
20277
19990
|
this.networkControllers.forEach(controller => {
|
20278
19991
|
controller.startLoad(startPosition);
|
20279
19992
|
});
|
@@ -20283,31 +19996,34 @@ class Hls {
|
|
20283
19996
|
* Stop loading of any stream data.
|
20284
19997
|
*/
|
20285
19998
|
stopLoad() {
|
20286
|
-
|
19999
|
+
logger.log('stopLoad');
|
20000
|
+
this.started = false;
|
20287
20001
|
this.networkControllers.forEach(controller => {
|
20288
20002
|
controller.stopLoad();
|
20289
20003
|
});
|
20290
20004
|
}
|
20291
20005
|
|
20292
20006
|
/**
|
20293
|
-
* Resumes stream controller segment loading
|
20007
|
+
* Resumes stream controller segment loading if previously started.
|
20294
20008
|
*/
|
20295
20009
|
resumeBuffering() {
|
20296
|
-
this.
|
20297
|
-
|
20298
|
-
controller
|
20299
|
-
|
20300
|
-
|
20010
|
+
if (this.started) {
|
20011
|
+
this.networkControllers.forEach(controller => {
|
20012
|
+
if ('fragmentLoader' in controller) {
|
20013
|
+
controller.startLoad(-1);
|
20014
|
+
}
|
20015
|
+
});
|
20016
|
+
}
|
20301
20017
|
}
|
20302
20018
|
|
20303
20019
|
/**
|
20304
|
-
*
|
20020
|
+
* Stops stream controller segment loading without changing 'started' state like stopLoad().
|
20305
20021
|
* This allows for media buffering to be paused without interupting playlist loading.
|
20306
20022
|
*/
|
20307
20023
|
pauseBuffering() {
|
20308
20024
|
this.networkControllers.forEach(controller => {
|
20309
|
-
if (controller
|
20310
|
-
controller.
|
20025
|
+
if ('fragmentLoader' in controller) {
|
20026
|
+
controller.stopLoad();
|
20311
20027
|
}
|
20312
20028
|
});
|
20313
20029
|
}
|
@@ -20316,7 +20032,7 @@ class Hls {
|
|
20316
20032
|
* Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
|
20317
20033
|
*/
|
20318
20034
|
swapAudioCodec() {
|
20319
|
-
|
20035
|
+
logger.log('swapAudioCodec');
|
20320
20036
|
this.streamController.swapAudioCodec();
|
20321
20037
|
}
|
20322
20038
|
|
@@ -20327,7 +20043,7 @@ class Hls {
|
|
20327
20043
|
* Automatic recovery of media-errors by this process is configurable.
|
20328
20044
|
*/
|
20329
20045
|
recoverMediaError() {
|
20330
|
-
|
20046
|
+
logger.log('recoverMediaError');
|
20331
20047
|
const media = this._media;
|
20332
20048
|
this.detachMedia();
|
20333
20049
|
if (media) {
|
@@ -20357,7 +20073,7 @@ class Hls {
|
|
20357
20073
|
* 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.
|
20358
20074
|
*/
|
20359
20075
|
set currentLevel(newLevel) {
|
20360
|
-
|
20076
|
+
logger.log(`set currentLevel:${newLevel}`);
|
20361
20077
|
this.levelController.manualLevel = newLevel;
|
20362
20078
|
this.streamController.immediateLevelSwitch();
|
20363
20079
|
}
|
@@ -20376,7 +20092,7 @@ class Hls {
|
|
20376
20092
|
* @param newLevel - Pass -1 for automatic level selection
|
20377
20093
|
*/
|
20378
20094
|
set nextLevel(newLevel) {
|
20379
|
-
|
20095
|
+
logger.log(`set nextLevel:${newLevel}`);
|
20380
20096
|
this.levelController.manualLevel = newLevel;
|
20381
20097
|
this.streamController.nextLevelSwitch();
|
20382
20098
|
}
|
@@ -20395,7 +20111,7 @@ class Hls {
|
|
20395
20111
|
* @param newLevel - Pass -1 for automatic level selection
|
20396
20112
|
*/
|
20397
20113
|
set loadLevel(newLevel) {
|
20398
|
-
|
20114
|
+
logger.log(`set loadLevel:${newLevel}`);
|
20399
20115
|
this.levelController.manualLevel = newLevel;
|
20400
20116
|
}
|
20401
20117
|
|
@@ -20426,7 +20142,7 @@ class Hls {
|
|
20426
20142
|
* Sets "first-level", see getter.
|
20427
20143
|
*/
|
20428
20144
|
set firstLevel(newLevel) {
|
20429
|
-
|
20145
|
+
logger.log(`set firstLevel:${newLevel}`);
|
20430
20146
|
this.levelController.firstLevel = newLevel;
|
20431
20147
|
}
|
20432
20148
|
|
@@ -20451,7 +20167,7 @@ class Hls {
|
|
20451
20167
|
* (determined from download of first segment)
|
20452
20168
|
*/
|
20453
20169
|
set startLevel(newLevel) {
|
20454
|
-
|
20170
|
+
logger.log(`set startLevel:${newLevel}`);
|
20455
20171
|
// if not in automatic start level detection, ensure startLevel is greater than minAutoLevel
|
20456
20172
|
if (newLevel !== -1) {
|
20457
20173
|
newLevel = Math.max(newLevel, this.minAutoLevel);
|
@@ -20526,7 +20242,7 @@ class Hls {
|
|
20526
20242
|
*/
|
20527
20243
|
set autoLevelCapping(newLevel) {
|
20528
20244
|
if (this._autoLevelCapping !== newLevel) {
|
20529
|
-
|
20245
|
+
logger.log(`set autoLevelCapping:${newLevel}`);
|
20530
20246
|
this._autoLevelCapping = newLevel;
|
20531
20247
|
this.levelController.checkMaxAutoUpdated();
|
20532
20248
|
}
|
@@ -20805,5 +20521,5 @@ var KeySystemFormats = empty.KeySystemFormats;
|
|
20805
20521
|
var KeySystems = empty.KeySystems;
|
20806
20522
|
var SubtitleStreamController = empty.SubtitleStreamController;
|
20807
20523
|
var TimelineController = empty.TimelineController;
|
20808
|
-
export { AbrController, AttrList,
|
20524
|
+
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 };
|
20809
20525
|
//# sourceMappingURL=hls.light.mjs.map
|