hls.js 1.5.6 → 1.5.7-0.canary.10014
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/hls-demo.js +10 -0
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +2074 -1165
- package/dist/hls.js.d.ts +65 -50
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +1147 -858
- 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 +983 -695
- 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 +1756 -862
- 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 +21 -21
- package/src/config.ts +3 -2
- package/src/controller/abr-controller.ts +21 -20
- package/src/controller/audio-stream-controller.ts +15 -16
- package/src/controller/audio-track-controller.ts +1 -1
- package/src/controller/base-playlist-controller.ts +20 -8
- package/src/controller/base-stream-controller.ts +149 -33
- package/src/controller/buffer-controller.ts +11 -11
- package/src/controller/cap-level-controller.ts +1 -2
- package/src/controller/cmcd-controller.ts +27 -6
- package/src/controller/content-steering-controller.ts +8 -6
- package/src/controller/eme-controller.ts +9 -22
- package/src/controller/error-controller.ts +6 -8
- package/src/controller/fps-controller.ts +2 -3
- package/src/controller/gap-controller.ts +43 -16
- package/src/controller/latency-controller.ts +9 -11
- package/src/controller/level-controller.ts +12 -18
- package/src/controller/stream-controller.ts +24 -31
- package/src/controller/subtitle-stream-controller.ts +13 -14
- package/src/controller/subtitle-track-controller.ts +5 -3
- package/src/controller/timeline-controller.ts +23 -30
- package/src/crypt/aes-crypto.ts +21 -2
- package/src/crypt/decrypter-aes-mode.ts +4 -0
- package/src/crypt/decrypter.ts +32 -18
- package/src/crypt/fast-aes-key.ts +24 -5
- package/src/demux/audio/adts.ts +9 -4
- package/src/demux/sample-aes.ts +2 -0
- package/src/demux/transmuxer-interface.ts +4 -12
- package/src/demux/transmuxer-worker.ts +4 -4
- package/src/demux/transmuxer.ts +16 -3
- package/src/demux/tsdemuxer.ts +71 -37
- package/src/demux/video/avc-video-parser.ts +208 -119
- package/src/demux/video/base-video-parser.ts +134 -2
- package/src/demux/video/exp-golomb.ts +0 -208
- package/src/demux/video/hevc-video-parser.ts +746 -0
- package/src/events.ts +7 -0
- package/src/hls.ts +42 -34
- package/src/loader/fragment-loader.ts +9 -2
- package/src/loader/key-loader.ts +2 -0
- package/src/loader/level-key.ts +10 -9
- package/src/loader/playlist-loader.ts +4 -5
- package/src/remux/mp4-generator.ts +196 -1
- package/src/remux/mp4-remuxer.ts +23 -7
- package/src/task-loop.ts +5 -2
- package/src/types/component-api.ts +2 -0
- package/src/types/demuxer.ts +3 -0
- package/src/types/events.ts +4 -0
- package/src/utils/codecs.ts +33 -4
- package/src/utils/encryption-methods-util.ts +21 -0
- package/src/utils/logger.ts +54 -24
package/dist/hls.light.mjs
CHANGED
@@ -256,6 +256,7 @@ 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";
|
259
260
|
Events["BUFFER_RESET"] = "hlsBufferReset";
|
260
261
|
Events["BUFFER_CODECS"] = "hlsBufferCodecs";
|
261
262
|
Events["BUFFER_CREATED"] = "hlsBufferCreated";
|
@@ -369,58 +370,6 @@ let ErrorDetails = /*#__PURE__*/function (ErrorDetails) {
|
|
369
370
|
return ErrorDetails;
|
370
371
|
}({});
|
371
372
|
|
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
|
-
|
424
373
|
const DECIMAL_RESOLUTION_REGEX = /^(\d+)x(\d+)$/;
|
425
374
|
const ATTR_LIST_REGEX = /(.+?)=(".*?"|.*?)(?:,|$)/g;
|
426
375
|
|
@@ -502,6 +451,79 @@ class AttrList {
|
|
502
451
|
}
|
503
452
|
}
|
504
453
|
|
454
|
+
class Logger {
|
455
|
+
constructor(label, logger) {
|
456
|
+
this.trace = void 0;
|
457
|
+
this.debug = void 0;
|
458
|
+
this.log = void 0;
|
459
|
+
this.warn = void 0;
|
460
|
+
this.info = void 0;
|
461
|
+
this.error = void 0;
|
462
|
+
const lb = `[${label}]:`;
|
463
|
+
this.trace = noop;
|
464
|
+
this.debug = logger.debug.bind(null, lb);
|
465
|
+
this.log = logger.log.bind(null, lb);
|
466
|
+
this.warn = logger.warn.bind(null, lb);
|
467
|
+
this.info = logger.info.bind(null, lb);
|
468
|
+
this.error = logger.error.bind(null, lb);
|
469
|
+
}
|
470
|
+
}
|
471
|
+
const noop = function noop() {};
|
472
|
+
const fakeLogger = {
|
473
|
+
trace: noop,
|
474
|
+
debug: noop,
|
475
|
+
log: noop,
|
476
|
+
warn: noop,
|
477
|
+
info: noop,
|
478
|
+
error: noop
|
479
|
+
};
|
480
|
+
function createLogger() {
|
481
|
+
return _extends({}, fakeLogger);
|
482
|
+
}
|
483
|
+
|
484
|
+
// let lastCallTime;
|
485
|
+
// function formatMsgWithTimeInfo(type, msg) {
|
486
|
+
// const now = Date.now();
|
487
|
+
// const diff = lastCallTime ? '+' + (now - lastCallTime) : '0';
|
488
|
+
// lastCallTime = now;
|
489
|
+
// msg = (new Date(now)).toISOString() + ' | [' + type + '] > ' + msg + ' ( ' + diff + ' ms )';
|
490
|
+
// return msg;
|
491
|
+
// }
|
492
|
+
|
493
|
+
function consolePrintFn(type, id) {
|
494
|
+
const func = self.console[type];
|
495
|
+
return func ? func.bind(self.console, `${id ? '[' + id + '] ' : ''}[${type}] >`) : noop;
|
496
|
+
}
|
497
|
+
function getLoggerFn(key, debugConfig, id) {
|
498
|
+
return debugConfig[key] ? debugConfig[key].bind(debugConfig) : consolePrintFn(key, id);
|
499
|
+
}
|
500
|
+
const exportedLogger = createLogger();
|
501
|
+
function enableLogs(debugConfig, context, id) {
|
502
|
+
// check that console is available
|
503
|
+
const newLogger = createLogger();
|
504
|
+
if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
|
505
|
+
const keys = [
|
506
|
+
// Remove out from list here to hard-disable a log-level
|
507
|
+
// 'trace',
|
508
|
+
'debug', 'log', 'info', 'warn', 'error'];
|
509
|
+
keys.forEach(key => {
|
510
|
+
newLogger[key] = getLoggerFn(key, debugConfig, id);
|
511
|
+
});
|
512
|
+
// Some browsers don't allow to use bind on console object anyway
|
513
|
+
// fallback to default if needed
|
514
|
+
try {
|
515
|
+
newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.5.7-0.canary.10014"}`);
|
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
|
+
|
505
527
|
// Avoid exporting const enum so that these values can be inlined
|
506
528
|
|
507
529
|
function isDateRangeCueAttribute(attrName) {
|
@@ -991,10 +1013,30 @@ class LevelDetails {
|
|
991
1013
|
}
|
992
1014
|
}
|
993
1015
|
|
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
|
+
|
994
1036
|
// This file is inserted as a shim for modules which we do not want to include into the distro.
|
995
1037
|
// This replacement is done in the "alias" plugin of the rollup config.
|
996
1038
|
var empty = undefined;
|
997
|
-
var
|
1039
|
+
var HevcVideoParser = /*@__PURE__*/getDefaultExportFromCjs(empty);
|
998
1040
|
|
999
1041
|
function sliceUint8(array, start, end) {
|
1000
1042
|
// @ts-expect-error This polyfills IE11 usage of Uint8Array slice.
|
@@ -2433,12 +2475,12 @@ class LevelKey {
|
|
2433
2475
|
this.keyFormatVersions = formatversions;
|
2434
2476
|
this.iv = iv;
|
2435
2477
|
this.encrypted = method ? method !== 'NONE' : false;
|
2436
|
-
this.isCommonEncryption = this.encrypted && method
|
2478
|
+
this.isCommonEncryption = this.encrypted && !isFullSegmentEncryption(method);
|
2437
2479
|
}
|
2438
2480
|
isSupported() {
|
2439
2481
|
// If it's Segment encryption or No encryption, just select that key system
|
2440
2482
|
if (this.method) {
|
2441
|
-
if (this.method
|
2483
|
+
if (isFullSegmentEncryption(this.method) || this.method === 'NONE') {
|
2442
2484
|
return true;
|
2443
2485
|
}
|
2444
2486
|
if (this.keyFormat === 'identity') {
|
@@ -2452,14 +2494,13 @@ class LevelKey {
|
|
2452
2494
|
if (!this.encrypted || !this.uri) {
|
2453
2495
|
return null;
|
2454
2496
|
}
|
2455
|
-
if (this.method
|
2497
|
+
if (isFullSegmentEncryption(this.method) && this.uri && !this.iv) {
|
2456
2498
|
if (typeof sn !== 'number') {
|
2457
2499
|
// We are fetching decryption data for a initialization segment
|
2458
|
-
// If the segment was encrypted with AES-128
|
2500
|
+
// If the segment was encrypted with AES-128/256
|
2459
2501
|
// It must have an IV defined. We cannot substitute the Segment Number in.
|
2460
|
-
|
2461
|
-
|
2462
|
-
}
|
2502
|
+
logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
|
2503
|
+
|
2463
2504
|
// Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
|
2464
2505
|
sn = 0;
|
2465
2506
|
}
|
@@ -2606,23 +2647,28 @@ function getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource =
|
|
2606
2647
|
if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) {
|
2607
2648
|
return CODEC_COMPATIBLE_NAMES[lowerCaseCodec];
|
2608
2649
|
}
|
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
|
2613
2650
|
const codecsToCheck = {
|
2651
|
+
// Idealy fLaC and Opus would be first (spec-compliant) but
|
2652
|
+
// some browsers will report that fLaC is supported then fail.
|
2653
|
+
// see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
|
2614
2654
|
flac: ['flac', 'fLaC', 'FLAC'],
|
2615
|
-
opus: ['opus', 'Opus']
|
2655
|
+
opus: ['opus', 'Opus'],
|
2656
|
+
// Replace audio codec info if browser does not support mp4a.40.34,
|
2657
|
+
// and demuxer can fallback to 'audio/mpeg' or 'audio/mp4;codecs="mp3"'
|
2658
|
+
'mp4a.40.34': ['mp3']
|
2616
2659
|
}[lowerCaseCodec];
|
2617
2660
|
for (let i = 0; i < codecsToCheck.length; i++) {
|
2661
|
+
var _getMediaSource;
|
2618
2662
|
if (isCodecMediaSourceSupported(codecsToCheck[i], 'audio', preferManagedMediaSource)) {
|
2619
2663
|
CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
|
2620
2664
|
return codecsToCheck[i];
|
2665
|
+
} else if (codecsToCheck[i] === 'mp3' && (_getMediaSource = getMediaSource(preferManagedMediaSource)) != null && _getMediaSource.isTypeSupported('audio/mpeg')) {
|
2666
|
+
return '';
|
2621
2667
|
}
|
2622
2668
|
}
|
2623
2669
|
return lowerCaseCodec;
|
2624
2670
|
}
|
2625
|
-
const AUDIO_CODEC_REGEXP = /flac|opus/i;
|
2671
|
+
const AUDIO_CODEC_REGEXP = /flac|opus|mp4a\.40\.34/i;
|
2626
2672
|
function getCodecCompatibleName(codec, preferManagedMediaSource = true) {
|
2627
2673
|
return codec.replace(AUDIO_CODEC_REGEXP, m => getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource));
|
2628
2674
|
}
|
@@ -2645,6 +2691,16 @@ function convertAVC1ToAVCOTI(codec) {
|
|
2645
2691
|
}
|
2646
2692
|
return codec;
|
2647
2693
|
}
|
2694
|
+
function getM2TSSupportedAudioTypes(preferManagedMediaSource) {
|
2695
|
+
const MediaSource = getMediaSource(preferManagedMediaSource) || {
|
2696
|
+
isTypeSupported: () => false
|
2697
|
+
};
|
2698
|
+
return {
|
2699
|
+
mpeg: MediaSource.isTypeSupported('audio/mpeg'),
|
2700
|
+
mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
|
2701
|
+
ac3: false
|
2702
|
+
};
|
2703
|
+
}
|
2648
2704
|
|
2649
2705
|
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;
|
2650
2706
|
const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g;
|
@@ -3445,10 +3501,10 @@ class PlaylistLoader {
|
|
3445
3501
|
const loaderContext = loader.context;
|
3446
3502
|
if (loaderContext && loaderContext.url === context.url && loaderContext.level === context.level) {
|
3447
3503
|
// same URL can't overlap
|
3448
|
-
logger.trace('[playlist-loader]: playlist request ongoing');
|
3504
|
+
this.hls.logger.trace('[playlist-loader]: playlist request ongoing');
|
3449
3505
|
return;
|
3450
3506
|
}
|
3451
|
-
logger.log(`[playlist-loader]: aborting previous loader for type: ${context.type}`);
|
3507
|
+
this.hls.logger.log(`[playlist-loader]: aborting previous loader for type: ${context.type}`);
|
3452
3508
|
loader.abort();
|
3453
3509
|
}
|
3454
3510
|
|
@@ -3558,7 +3614,7 @@ class PlaylistLoader {
|
|
3558
3614
|
// alt audio rendition in which quality levels (main)
|
3559
3615
|
// contains both audio+video. but with mixed audio track not signaled
|
3560
3616
|
if (!embeddedAudioFound && levels[0].audioCodec && !levels[0].attrs.AUDIO) {
|
3561
|
-
logger.log('[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one');
|
3617
|
+
this.hls.logger.log('[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one');
|
3562
3618
|
audioTracks.unshift({
|
3563
3619
|
type: 'main',
|
3564
3620
|
name: 'main',
|
@@ -3657,7 +3713,7 @@ class PlaylistLoader {
|
|
3657
3713
|
message += ` id: ${context.id} group-id: "${context.groupId}"`;
|
3658
3714
|
}
|
3659
3715
|
const error = new Error(message);
|
3660
|
-
logger.warn(`[playlist-loader]: ${message}`);
|
3716
|
+
this.hls.logger.warn(`[playlist-loader]: ${message}`);
|
3661
3717
|
let details = ErrorDetails.UNKNOWN;
|
3662
3718
|
let fatal = false;
|
3663
3719
|
const loader = this.getInternalLoader(context);
|
@@ -4222,7 +4278,47 @@ class LatencyController {
|
|
4222
4278
|
this.currentTime = 0;
|
4223
4279
|
this.stallCount = 0;
|
4224
4280
|
this._latency = null;
|
4225
|
-
this.
|
4281
|
+
this.onTimeupdate = () => {
|
4282
|
+
const {
|
4283
|
+
media,
|
4284
|
+
levelDetails
|
4285
|
+
} = this;
|
4286
|
+
if (!media || !levelDetails) {
|
4287
|
+
return;
|
4288
|
+
}
|
4289
|
+
this.currentTime = media.currentTime;
|
4290
|
+
const latency = this.computeLatency();
|
4291
|
+
if (latency === null) {
|
4292
|
+
return;
|
4293
|
+
}
|
4294
|
+
this._latency = latency;
|
4295
|
+
|
4296
|
+
// Adapt playbackRate to meet target latency in low-latency mode
|
4297
|
+
const {
|
4298
|
+
lowLatencyMode,
|
4299
|
+
maxLiveSyncPlaybackRate
|
4300
|
+
} = this.config;
|
4301
|
+
if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
|
4302
|
+
return;
|
4303
|
+
}
|
4304
|
+
const targetLatency = this.targetLatency;
|
4305
|
+
if (targetLatency === null) {
|
4306
|
+
return;
|
4307
|
+
}
|
4308
|
+
const distanceFromTarget = latency - targetLatency;
|
4309
|
+
// Only adjust playbackRate when within one target duration of targetLatency
|
4310
|
+
// and more than one second from under-buffering.
|
4311
|
+
// Playback further than one target duration from target can be considered DVR playback.
|
4312
|
+
const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
|
4313
|
+
const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
|
4314
|
+
if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
|
4315
|
+
const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
|
4316
|
+
const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
|
4317
|
+
media.playbackRate = Math.min(max, Math.max(1, rate));
|
4318
|
+
} else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
|
4319
|
+
media.playbackRate = 1;
|
4320
|
+
}
|
4321
|
+
};
|
4226
4322
|
this.hls = hls;
|
4227
4323
|
this.config = hls.config;
|
4228
4324
|
this.registerListeners();
|
@@ -4314,7 +4410,7 @@ class LatencyController {
|
|
4314
4410
|
this.onMediaDetaching();
|
4315
4411
|
this.levelDetails = null;
|
4316
4412
|
// @ts-ignore
|
4317
|
-
this.hls =
|
4413
|
+
this.hls = null;
|
4318
4414
|
}
|
4319
4415
|
registerListeners() {
|
4320
4416
|
this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
@@ -4332,11 +4428,11 @@ class LatencyController {
|
|
4332
4428
|
}
|
4333
4429
|
onMediaAttached(event, data) {
|
4334
4430
|
this.media = data.media;
|
4335
|
-
this.media.addEventListener('timeupdate', this.
|
4431
|
+
this.media.addEventListener('timeupdate', this.onTimeupdate);
|
4336
4432
|
}
|
4337
4433
|
onMediaDetaching() {
|
4338
4434
|
if (this.media) {
|
4339
|
-
this.media.removeEventListener('timeupdate', this.
|
4435
|
+
this.media.removeEventListener('timeupdate', this.onTimeupdate);
|
4340
4436
|
this.media = null;
|
4341
4437
|
}
|
4342
4438
|
}
|
@@ -4350,10 +4446,10 @@ class LatencyController {
|
|
4350
4446
|
}) {
|
4351
4447
|
this.levelDetails = details;
|
4352
4448
|
if (details.advanced) {
|
4353
|
-
this.
|
4449
|
+
this.onTimeupdate();
|
4354
4450
|
}
|
4355
4451
|
if (!details.live && this.media) {
|
4356
|
-
this.media.removeEventListener('timeupdate', this.
|
4452
|
+
this.media.removeEventListener('timeupdate', this.onTimeupdate);
|
4357
4453
|
}
|
4358
4454
|
}
|
4359
4455
|
onError(event, data) {
|
@@ -4363,48 +4459,7 @@ class LatencyController {
|
|
4363
4459
|
}
|
4364
4460
|
this.stallCount++;
|
4365
4461
|
if ((_this$levelDetails = this.levelDetails) != null && _this$levelDetails.live) {
|
4366
|
-
logger.warn('[
|
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;
|
4462
|
+
this.hls.logger.warn('[latency-controller]: Stall detected, adjusting target latency');
|
4408
4463
|
}
|
4409
4464
|
}
|
4410
4465
|
estimateLiveEdge() {
|
@@ -5176,18 +5231,13 @@ var ErrorActionFlags = {
|
|
5176
5231
|
MoveAllAlternatesMatchingHDCP: 2,
|
5177
5232
|
SwitchToSDR: 4
|
5178
5233
|
}; // Reserved for future use
|
5179
|
-
class ErrorController {
|
5234
|
+
class ErrorController extends Logger {
|
5180
5235
|
constructor(hls) {
|
5236
|
+
super('error-controller', hls.logger);
|
5181
5237
|
this.hls = void 0;
|
5182
5238
|
this.playlistError = 0;
|
5183
5239
|
this.penalizedRenditions = {};
|
5184
|
-
this.log = void 0;
|
5185
|
-
this.warn = void 0;
|
5186
|
-
this.error = void 0;
|
5187
5240
|
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]:`);
|
5191
5241
|
this.registerListeners();
|
5192
5242
|
}
|
5193
5243
|
registerListeners() {
|
@@ -5539,16 +5589,13 @@ class ErrorController {
|
|
5539
5589
|
}
|
5540
5590
|
}
|
5541
5591
|
|
5542
|
-
class BasePlaylistController {
|
5592
|
+
class BasePlaylistController extends Logger {
|
5543
5593
|
constructor(hls, logPrefix) {
|
5594
|
+
super(logPrefix, hls.logger);
|
5544
5595
|
this.hls = void 0;
|
5545
5596
|
this.timer = -1;
|
5546
5597
|
this.requestScheduled = -1;
|
5547
5598
|
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}:`);
|
5552
5599
|
this.hls = hls;
|
5553
5600
|
}
|
5554
5601
|
destroy() {
|
@@ -5581,7 +5628,7 @@ class BasePlaylistController {
|
|
5581
5628
|
try {
|
5582
5629
|
uri = new self.URL(attr.URI, previous.url).href;
|
5583
5630
|
} catch (error) {
|
5584
|
-
|
5631
|
+
this.warn(`Could not construct new URL for Rendition Report: ${error}`);
|
5585
5632
|
uri = attr.URI || '';
|
5586
5633
|
}
|
5587
5634
|
// Use exact match. Otherwise, the last partial match, if any, will be used
|
@@ -5668,7 +5715,12 @@ class BasePlaylistController {
|
|
5668
5715
|
const cdnAge = lastAdvanced + details.ageHeader;
|
5669
5716
|
let currentGoal = Math.min(cdnAge - details.partTarget, details.targetduration * 1.5);
|
5670
5717
|
if (currentGoal > 0) {
|
5671
|
-
if (
|
5718
|
+
if (cdnAge > details.targetduration * 3) {
|
5719
|
+
// Omit segment and part directives when the last response was more than 3 target durations ago,
|
5720
|
+
this.log(`Playlist last advanced ${lastAdvanced.toFixed(2)}s ago. Omitting segment and part directives.`);
|
5721
|
+
msn = undefined;
|
5722
|
+
part = undefined;
|
5723
|
+
} else if (previousDetails != null && previousDetails.tuneInGoal && cdnAge - details.partTarget > previousDetails.tuneInGoal) {
|
5672
5724
|
// If we attempted to get the next or latest playlist update, but currentGoal increased,
|
5673
5725
|
// then we either can't catchup, or the "age" header cannot be trusted.
|
5674
5726
|
this.warn(`CDN Tune-in goal increased from: ${previousDetails.tuneInGoal} to: ${currentGoal} with playlist age: ${details.age}`);
|
@@ -6127,8 +6179,9 @@ function getCodecTiers(levels, audioTracksByGroup, minAutoLevel, maxAutoLevel) {
|
|
6127
6179
|
}, {});
|
6128
6180
|
}
|
6129
6181
|
|
6130
|
-
class AbrController {
|
6182
|
+
class AbrController extends Logger {
|
6131
6183
|
constructor(_hls) {
|
6184
|
+
super('abr', _hls.logger);
|
6132
6185
|
this.hls = void 0;
|
6133
6186
|
this.lastLevelLoadSec = 0;
|
6134
6187
|
this.lastLoadedFragLevel = -1;
|
@@ -6242,7 +6295,7 @@ class AbrController {
|
|
6242
6295
|
this.resetEstimator(nextLoadLevelBitrate);
|
6243
6296
|
}
|
6244
6297
|
this.clearTimer();
|
6245
|
-
|
6298
|
+
this.warn(`Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
|
6246
6299
|
Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s
|
6247
6300
|
Estimated load time for current fragment: ${fragLoadedDelay.toFixed(3)} s
|
6248
6301
|
Estimated load time for down switch fragment: ${fragLevelNextLoadedDelay.toFixed(3)} s
|
@@ -6262,7 +6315,7 @@ class AbrController {
|
|
6262
6315
|
}
|
6263
6316
|
resetEstimator(abrEwmaDefaultEstimate) {
|
6264
6317
|
if (abrEwmaDefaultEstimate) {
|
6265
|
-
|
6318
|
+
this.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
|
6266
6319
|
this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate;
|
6267
6320
|
}
|
6268
6321
|
this.firstSelection = -1;
|
@@ -6494,7 +6547,7 @@ class AbrController {
|
|
6494
6547
|
}
|
6495
6548
|
const firstLevel = this.hls.firstLevel;
|
6496
6549
|
const clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel);
|
6497
|
-
|
6550
|
+
this.warn(`Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
|
6498
6551
|
return clamped;
|
6499
6552
|
}
|
6500
6553
|
get forcedAutoLevel() {
|
@@ -6572,13 +6625,13 @@ class AbrController {
|
|
6572
6625
|
// cap maxLoadingDelay and ensure it is not bigger 'than bitrate test' frag duration
|
6573
6626
|
const maxLoadingDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxLoadingDelay) : config.maxLoadingDelay;
|
6574
6627
|
maxStarvationDelay = maxLoadingDelay - bitrateTestDelay;
|
6575
|
-
|
6628
|
+
this.info(`bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
|
6576
6629
|
// don't use conservative factor on bitrate test
|
6577
6630
|
bwFactor = bwUpFactor = 1;
|
6578
6631
|
}
|
6579
6632
|
}
|
6580
6633
|
const bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor);
|
6581
|
-
|
6634
|
+
this.info(`${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
|
6582
6635
|
if (bestLevel > -1) {
|
6583
6636
|
return bestLevel;
|
6584
6637
|
}
|
@@ -6652,7 +6705,7 @@ class AbrController {
|
|
6652
6705
|
currentVideoRange = preferHDR ? videoRanges[videoRanges.length - 1] : videoRanges[0];
|
6653
6706
|
currentFrameRate = minFramerate;
|
6654
6707
|
currentBw = Math.max(currentBw, minBitrate);
|
6655
|
-
|
6708
|
+
this.log(`picked start tier ${JSON.stringify(startTier)}`);
|
6656
6709
|
} else {
|
6657
6710
|
currentCodecSet = level == null ? void 0 : level.codecSet;
|
6658
6711
|
currentVideoRange = level == null ? void 0 : level.videoRange;
|
@@ -6705,9 +6758,9 @@ class AbrController {
|
|
6705
6758
|
const forcedAutoLevel = this.forcedAutoLevel;
|
6706
6759
|
if (i !== loadLevel && (forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)) {
|
6707
6760
|
if (levelsSkipped.length) {
|
6708
|
-
|
6761
|
+
this.trace(`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}`);
|
6709
6762
|
}
|
6710
|
-
|
6763
|
+
this.info(`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}`);
|
6711
6764
|
}
|
6712
6765
|
if (firstSelection) {
|
6713
6766
|
this.firstSelection = i;
|
@@ -6943,8 +6996,9 @@ class BufferOperationQueue {
|
|
6943
6996
|
}
|
6944
6997
|
|
6945
6998
|
const VIDEO_CODEC_PROFILE_REPLACE = /(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\.[^.,]+)+/;
|
6946
|
-
class BufferController {
|
6999
|
+
class BufferController extends Logger {
|
6947
7000
|
constructor(hls) {
|
7001
|
+
super('buffer-controller', hls.logger);
|
6948
7002
|
// The level details used to determine duration, target-duration and live
|
6949
7003
|
this.details = null;
|
6950
7004
|
// cache the self generated object url to detect hijack of video tag
|
@@ -6974,9 +7028,6 @@ class BufferController {
|
|
6974
7028
|
this.tracks = {};
|
6975
7029
|
this.pendingTracks = {};
|
6976
7030
|
this.sourceBuffer = void 0;
|
6977
|
-
this.log = void 0;
|
6978
|
-
this.warn = void 0;
|
6979
|
-
this.error = void 0;
|
6980
7031
|
this._onEndStreaming = event => {
|
6981
7032
|
if (!this.hls) {
|
6982
7033
|
return;
|
@@ -7022,15 +7073,11 @@ class BufferController {
|
|
7022
7073
|
_objectUrl
|
7023
7074
|
} = this;
|
7024
7075
|
if (mediaSrc !== _objectUrl) {
|
7025
|
-
|
7076
|
+
this.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
|
7026
7077
|
}
|
7027
7078
|
};
|
7028
7079
|
this.hls = hls;
|
7029
|
-
const logPrefix = '[buffer-controller]';
|
7030
7080
|
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);
|
7034
7081
|
this._initSourceBuffer();
|
7035
7082
|
this.registerListeners();
|
7036
7083
|
}
|
@@ -7043,6 +7090,12 @@ class BufferController {
|
|
7043
7090
|
this.lastMpegAudioChunk = null;
|
7044
7091
|
// @ts-ignore
|
7045
7092
|
this.hls = null;
|
7093
|
+
// @ts-ignore
|
7094
|
+
this._onMediaSourceOpen = this._onMediaSourceClose = null;
|
7095
|
+
// @ts-ignore
|
7096
|
+
this._onMediaSourceEnded = null;
|
7097
|
+
// @ts-ignore
|
7098
|
+
this._onStartStreaming = this._onEndStreaming = null;
|
7046
7099
|
}
|
7047
7100
|
registerListeners() {
|
7048
7101
|
const {
|
@@ -7205,6 +7258,7 @@ class BufferController {
|
|
7205
7258
|
this.resetBuffer(type);
|
7206
7259
|
});
|
7207
7260
|
this._initSourceBuffer();
|
7261
|
+
this.hls.resumeBuffering();
|
7208
7262
|
}
|
7209
7263
|
resetBuffer(type) {
|
7210
7264
|
const sb = this.sourceBuffer[type];
|
@@ -8042,7 +8096,7 @@ class CapLevelController {
|
|
8042
8096
|
const hls = this.hls;
|
8043
8097
|
const maxLevel = this.getMaxLevel(levels.length - 1);
|
8044
8098
|
if (maxLevel !== this.autoLevelCapping) {
|
8045
|
-
logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
|
8099
|
+
hls.logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
|
8046
8100
|
}
|
8047
8101
|
hls.autoLevelCapping = maxLevel;
|
8048
8102
|
if (hls.autoLevelCapping > this.autoLevelCapping && this.streamController) {
|
@@ -8220,10 +8274,10 @@ class FPSController {
|
|
8220
8274
|
totalDroppedFrames: droppedFrames
|
8221
8275
|
});
|
8222
8276
|
if (droppedFPS > 0) {
|
8223
|
-
// logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
|
8277
|
+
// hls.logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
|
8224
8278
|
if (currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
|
8225
8279
|
let currentLevel = hls.currentLevel;
|
8226
|
-
logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
|
8280
|
+
hls.logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
|
8227
8281
|
if (currentLevel > 0 && (hls.autoLevelCapping === -1 || hls.autoLevelCapping >= currentLevel)) {
|
8228
8282
|
currentLevel = currentLevel - 1;
|
8229
8283
|
hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, {
|
@@ -8256,10 +8310,10 @@ class FPSController {
|
|
8256
8310
|
}
|
8257
8311
|
|
8258
8312
|
const PATHWAY_PENALTY_DURATION_MS = 300000;
|
8259
|
-
class ContentSteeringController {
|
8313
|
+
class ContentSteeringController extends Logger {
|
8260
8314
|
constructor(hls) {
|
8315
|
+
super('content-steering', hls.logger);
|
8261
8316
|
this.hls = void 0;
|
8262
|
-
this.log = void 0;
|
8263
8317
|
this.loader = null;
|
8264
8318
|
this.uri = null;
|
8265
8319
|
this.pathwayId = '.';
|
@@ -8274,7 +8328,6 @@ class ContentSteeringController {
|
|
8274
8328
|
this.subtitleTracks = null;
|
8275
8329
|
this.penalizedPathways = {};
|
8276
8330
|
this.hls = hls;
|
8277
|
-
this.log = logger.log.bind(logger, `[content-steering]:`);
|
8278
8331
|
this.registerListeners();
|
8279
8332
|
}
|
8280
8333
|
registerListeners() {
|
@@ -8398,7 +8451,7 @@ class ContentSteeringController {
|
|
8398
8451
|
errorAction.resolved = this.pathwayId !== errorPathway;
|
8399
8452
|
}
|
8400
8453
|
if (!errorAction.resolved) {
|
8401
|
-
|
8454
|
+
this.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)}`);
|
8402
8455
|
}
|
8403
8456
|
}
|
8404
8457
|
}
|
@@ -8569,7 +8622,7 @@ class ContentSteeringController {
|
|
8569
8622
|
onSuccess: (response, stats, context, networkDetails) => {
|
8570
8623
|
this.log(`Loaded steering manifest: "${url}"`);
|
8571
8624
|
const steeringData = response.data;
|
8572
|
-
if (steeringData.VERSION !== 1) {
|
8625
|
+
if ((steeringData == null ? void 0 : steeringData.VERSION) !== 1) {
|
8573
8626
|
this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
|
8574
8627
|
return;
|
8575
8628
|
}
|
@@ -9477,7 +9530,7 @@ const hlsDefaultConfig = _objectSpread2(_objectSpread2({
|
|
9477
9530
|
});
|
9478
9531
|
function timelineConfig() {
|
9479
9532
|
return {
|
9480
|
-
cueHandler:
|
9533
|
+
cueHandler: HevcVideoParser,
|
9481
9534
|
// used by timeline-controller
|
9482
9535
|
enableWebVTT: false,
|
9483
9536
|
// used by timeline-controller
|
@@ -9508,7 +9561,7 @@ function timelineConfig() {
|
|
9508
9561
|
/**
|
9509
9562
|
* @ignore
|
9510
9563
|
*/
|
9511
|
-
function mergeConfig(defaultConfig, userConfig) {
|
9564
|
+
function mergeConfig(defaultConfig, userConfig, logger) {
|
9512
9565
|
if ((userConfig.liveSyncDurationCount || userConfig.liveMaxLatencyDurationCount) && (userConfig.liveSyncDuration || userConfig.liveMaxLatencyDuration)) {
|
9513
9566
|
throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration");
|
9514
9567
|
}
|
@@ -9578,7 +9631,7 @@ function deepCpy(obj) {
|
|
9578
9631
|
/**
|
9579
9632
|
* @ignore
|
9580
9633
|
*/
|
9581
|
-
function enableStreamingMode(config) {
|
9634
|
+
function enableStreamingMode(config, logger) {
|
9582
9635
|
const currentLoader = config.loader;
|
9583
9636
|
if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
|
9584
9637
|
// If a developer has configured their own loader, respect that choice
|
@@ -9595,10 +9648,9 @@ function enableStreamingMode(config) {
|
|
9595
9648
|
}
|
9596
9649
|
}
|
9597
9650
|
|
9598
|
-
let chromeOrFirefox;
|
9599
9651
|
class LevelController extends BasePlaylistController {
|
9600
9652
|
constructor(hls, contentSteeringController) {
|
9601
|
-
super(hls, '
|
9653
|
+
super(hls, 'level-controller');
|
9602
9654
|
this._levels = [];
|
9603
9655
|
this._firstLevel = -1;
|
9604
9656
|
this._maxAutoLevel = -1;
|
@@ -9669,23 +9721,15 @@ class LevelController extends BasePlaylistController {
|
|
9669
9721
|
let videoCodecFound = false;
|
9670
9722
|
let audioCodecFound = false;
|
9671
9723
|
data.levels.forEach(levelParsed => {
|
9672
|
-
var
|
9724
|
+
var _videoCodec;
|
9673
9725
|
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
|
9677
9726
|
let {
|
9678
9727
|
audioCodec,
|
9679
9728
|
videoCodec
|
9680
9729
|
} = 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
|
-
}
|
9687
9730
|
if (audioCodec) {
|
9688
|
-
|
9731
|
+
// Returns empty and set to undefined for 'mp4a.40.34' with fallback to 'audio/mpeg' SourceBuffer
|
9732
|
+
levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource) || undefined;
|
9689
9733
|
}
|
9690
9734
|
if (((_videoCodec = videoCodec) == null ? void 0 : _videoCodec.indexOf('avc1')) === 0) {
|
9691
9735
|
videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
|
@@ -10027,7 +10071,12 @@ class LevelController extends BasePlaylistController {
|
|
10027
10071
|
if (curLevel.fragmentError === 0) {
|
10028
10072
|
curLevel.loadError = 0;
|
10029
10073
|
}
|
10030
|
-
|
10074
|
+
// Ignore matching details populated by loading a Media Playlist directly
|
10075
|
+
let previousDetails = curLevel.details;
|
10076
|
+
if (previousDetails === data.details && previousDetails.advanced) {
|
10077
|
+
previousDetails = undefined;
|
10078
|
+
}
|
10079
|
+
this.playlistLoaded(level, data, previousDetails);
|
10031
10080
|
} else if ((_data$deliveryDirecti2 = data.deliveryDirectives) != null && _data$deliveryDirecti2.skip) {
|
10032
10081
|
// received a delta playlist update that cannot be merged
|
10033
10082
|
details.deltaUpdateFailed = true;
|
@@ -10813,8 +10862,8 @@ function createLoaderContext(frag, part = null) {
|
|
10813
10862
|
var _frag$decryptdata;
|
10814
10863
|
let byteRangeStart = start;
|
10815
10864
|
let byteRangeEnd = end;
|
10816
|
-
if (frag.sn === 'initSegment' && ((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method)
|
10817
|
-
// MAP segment encrypted with method 'AES-128', when served with HTTP Range,
|
10865
|
+
if (frag.sn === 'initSegment' && isMethodFullSegmentAesCbc((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method)) {
|
10866
|
+
// MAP segment encrypted with method 'AES-128' or 'AES-256' (cbc), when served with HTTP Range,
|
10818
10867
|
// has the unencrypted size specified in the range.
|
10819
10868
|
// Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
|
10820
10869
|
const fragmentLen = end - start;
|
@@ -10847,6 +10896,9 @@ function createGapLoadError(frag, part) {
|
|
10847
10896
|
(part ? part : frag).stats.aborted = true;
|
10848
10897
|
return new LoadError(errorData);
|
10849
10898
|
}
|
10899
|
+
function isMethodFullSegmentAesCbc(method) {
|
10900
|
+
return method === 'AES-128' || method === 'AES-256';
|
10901
|
+
}
|
10850
10902
|
class LoadError extends Error {
|
10851
10903
|
constructor(data) {
|
10852
10904
|
super(data.error.message);
|
@@ -10992,6 +11044,8 @@ class KeyLoader {
|
|
10992
11044
|
}
|
10993
11045
|
return this.loadKeyEME(keyInfo, frag);
|
10994
11046
|
case 'AES-128':
|
11047
|
+
case 'AES-256':
|
11048
|
+
case 'AES-256-CTR':
|
10995
11049
|
return this.loadKeyHTTP(keyInfo, frag);
|
10996
11050
|
default:
|
10997
11051
|
return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`)));
|
@@ -11127,8 +11181,9 @@ class KeyLoader {
|
|
11127
11181
|
* we are limiting the task execution per call stack to exactly one, but scheduling/post-poning further
|
11128
11182
|
* task processing on the next main loop iteration (also known as "next tick" in the Node/JS runtime lingo).
|
11129
11183
|
*/
|
11130
|
-
class TaskLoop {
|
11131
|
-
constructor() {
|
11184
|
+
class TaskLoop extends Logger {
|
11185
|
+
constructor(label, logger) {
|
11186
|
+
super(label, logger);
|
11132
11187
|
this._boundTick = void 0;
|
11133
11188
|
this._tickTimer = null;
|
11134
11189
|
this._tickInterval = null;
|
@@ -11396,33 +11451,61 @@ function alignMediaPlaylistByPDT(details, refDetails) {
|
|
11396
11451
|
}
|
11397
11452
|
|
11398
11453
|
class AESCrypto {
|
11399
|
-
constructor(subtle, iv) {
|
11454
|
+
constructor(subtle, iv, aesMode) {
|
11400
11455
|
this.subtle = void 0;
|
11401
11456
|
this.aesIV = void 0;
|
11457
|
+
this.aesMode = void 0;
|
11402
11458
|
this.subtle = subtle;
|
11403
11459
|
this.aesIV = iv;
|
11460
|
+
this.aesMode = aesMode;
|
11404
11461
|
}
|
11405
11462
|
decrypt(data, key) {
|
11406
|
-
|
11407
|
-
|
11408
|
-
|
11409
|
-
|
11463
|
+
switch (this.aesMode) {
|
11464
|
+
case DecrypterAesMode.cbc:
|
11465
|
+
return this.subtle.decrypt({
|
11466
|
+
name: 'AES-CBC',
|
11467
|
+
iv: this.aesIV
|
11468
|
+
}, key, data);
|
11469
|
+
case DecrypterAesMode.ctr:
|
11470
|
+
return this.subtle.decrypt({
|
11471
|
+
name: 'AES-CTR',
|
11472
|
+
counter: this.aesIV,
|
11473
|
+
length: 64
|
11474
|
+
},
|
11475
|
+
//64 : NIST SP800-38A standard suggests that the counter should occupy half of the counter block
|
11476
|
+
key, data);
|
11477
|
+
default:
|
11478
|
+
throw new Error(`[AESCrypto] invalid aes mode ${this.aesMode}`);
|
11479
|
+
}
|
11410
11480
|
}
|
11411
11481
|
}
|
11412
11482
|
|
11413
11483
|
class FastAESKey {
|
11414
|
-
constructor(subtle, key) {
|
11484
|
+
constructor(subtle, key, aesMode) {
|
11415
11485
|
this.subtle = void 0;
|
11416
11486
|
this.key = void 0;
|
11487
|
+
this.aesMode = void 0;
|
11417
11488
|
this.subtle = subtle;
|
11418
11489
|
this.key = key;
|
11490
|
+
this.aesMode = aesMode;
|
11419
11491
|
}
|
11420
11492
|
expandKey() {
|
11493
|
+
const subtleAlgoName = getSubtleAlgoName(this.aesMode);
|
11421
11494
|
return this.subtle.importKey('raw', this.key, {
|
11422
|
-
name:
|
11495
|
+
name: subtleAlgoName
|
11423
11496
|
}, false, ['encrypt', 'decrypt']);
|
11424
11497
|
}
|
11425
11498
|
}
|
11499
|
+
function getSubtleAlgoName(aesMode) {
|
11500
|
+
switch (aesMode) {
|
11501
|
+
case DecrypterAesMode.cbc:
|
11502
|
+
return 'AES-CBC';
|
11503
|
+
case DecrypterAesMode.ctr:
|
11504
|
+
return 'AES-CTR';
|
11505
|
+
default:
|
11506
|
+
throw new Error(`[FastAESKey] invalid aes mode ${aesMode}`);
|
11507
|
+
}
|
11508
|
+
}
|
11426
11509
|
|
11427
11510
|
// PKCS7
|
11428
11511
|
function removePadding(array) {
|
@@ -11672,7 +11755,8 @@ class Decrypter {
|
|
11672
11755
|
this.currentIV = null;
|
11673
11756
|
this.currentResult = null;
|
11674
11757
|
this.useSoftware = void 0;
|
11675
|
-
this.
|
11758
|
+
this.enableSoftwareAES = void 0;
|
11759
|
+
this.enableSoftwareAES = config.enableSoftwareAES;
|
11676
11760
|
this.removePKCS7Padding = removePKCS7Padding;
|
11677
11761
|
// built in decryptor expects PKCS7 padding
|
11678
11762
|
if (removePKCS7Padding) {
|
@@ -11685,9 +11769,7 @@ class Decrypter {
|
|
11685
11769
|
/* no-op */
|
11686
11770
|
}
|
11687
11771
|
}
|
11688
|
-
|
11689
|
-
this.useSoftware = true;
|
11690
|
-
}
|
11772
|
+
this.useSoftware = this.subtle === null;
|
11691
11773
|
}
|
11692
11774
|
destroy() {
|
11693
11775
|
this.subtle = null;
|
@@ -11725,10 +11807,10 @@ class Decrypter {
|
|
11725
11807
|
this.softwareDecrypter = null;
|
11726
11808
|
}
|
11727
11809
|
}
|
11728
|
-
decrypt(data, key, iv) {
|
11810
|
+
decrypt(data, key, iv, aesMode) {
|
11729
11811
|
if (this.useSoftware) {
|
11730
11812
|
return new Promise((resolve, reject) => {
|
11731
|
-
this.softwareDecrypt(new Uint8Array(data), key, iv);
|
11813
|
+
this.softwareDecrypt(new Uint8Array(data), key, iv, aesMode);
|
11732
11814
|
const decryptResult = this.flush();
|
11733
11815
|
if (decryptResult) {
|
11734
11816
|
resolve(decryptResult.buffer);
|
@@ -11737,17 +11819,21 @@ class Decrypter {
|
|
11737
11819
|
}
|
11738
11820
|
});
|
11739
11821
|
}
|
11740
|
-
return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
|
11822
|
+
return this.webCryptoDecrypt(new Uint8Array(data), key, iv, aesMode);
|
11741
11823
|
}
|
11742
11824
|
|
11743
11825
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
11744
11826
|
// data is handled in the flush() call
|
11745
|
-
softwareDecrypt(data, key, iv) {
|
11827
|
+
softwareDecrypt(data, key, iv, aesMode) {
|
11746
11828
|
const {
|
11747
11829
|
currentIV,
|
11748
11830
|
currentResult,
|
11749
11831
|
remainderData
|
11750
11832
|
} = this;
|
11833
|
+
if (aesMode !== DecrypterAesMode.cbc || key.byteLength !== 16) {
|
11834
|
+
logger.warn('SoftwareDecrypt: can only handle AES-128-CBC');
|
11835
|
+
return null;
|
11836
|
+
}
|
11751
11837
|
this.logOnce('JS AES decrypt');
|
11752
11838
|
// The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
|
11753
11839
|
// This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached
|
@@ -11780,11 +11866,11 @@ class Decrypter {
|
|
11780
11866
|
}
|
11781
11867
|
return result;
|
11782
11868
|
}
|
11783
|
-
webCryptoDecrypt(data, key, iv) {
|
11869
|
+
webCryptoDecrypt(data, key, iv, aesMode) {
|
11784
11870
|
const subtle = this.subtle;
|
11785
11871
|
if (this.key !== key || !this.fastAesKey) {
|
11786
11872
|
this.key = key;
|
11787
|
-
this.fastAesKey = new FastAESKey(subtle, key);
|
11873
|
+
this.fastAesKey = new FastAESKey(subtle, key, aesMode);
|
11788
11874
|
}
|
11789
11875
|
return this.fastAesKey.expandKey().then(aesKey => {
|
11790
11876
|
// decrypt using web crypto
|
@@ -11792,22 +11878,25 @@ class Decrypter {
|
|
11792
11878
|
return Promise.reject(new Error('web crypto not initialized'));
|
11793
11879
|
}
|
11794
11880
|
this.logOnce('WebCrypto AES decrypt');
|
11795
|
-
const crypto = new AESCrypto(subtle, new Uint8Array(iv));
|
11881
|
+
const crypto = new AESCrypto(subtle, new Uint8Array(iv), aesMode);
|
11796
11882
|
return crypto.decrypt(data.buffer, aesKey);
|
11797
11883
|
}).catch(err => {
|
11798
11884
|
logger.warn(`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`);
|
11799
|
-
return this.onWebCryptoError(data, key, iv);
|
11885
|
+
return this.onWebCryptoError(data, key, iv, aesMode);
|
11800
11886
|
});
|
11801
11887
|
}
|
11802
|
-
onWebCryptoError(data, key, iv) {
|
11803
|
-
|
11804
|
-
|
11805
|
-
|
11806
|
-
|
11807
|
-
|
11808
|
-
|
11888
|
+
onWebCryptoError(data, key, iv, aesMode) {
|
11889
|
+
const enableSoftwareAES = this.enableSoftwareAES;
|
11890
|
+
if (enableSoftwareAES) {
|
11891
|
+
this.useSoftware = true;
|
11892
|
+
this.logEnabled = true;
|
11893
|
+
this.softwareDecrypt(data, key, iv, aesMode);
|
11894
|
+
const decryptResult = this.flush();
|
11895
|
+
if (decryptResult) {
|
11896
|
+
return decryptResult.buffer;
|
11897
|
+
}
|
11809
11898
|
}
|
11810
|
-
throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data');
|
11899
|
+
throw new Error('WebCrypto' + (enableSoftwareAES ? ' and softwareDecrypt' : '') + ': failed to decrypt data');
|
11811
11900
|
}
|
11812
11901
|
getValidChunk(data) {
|
11813
11902
|
let currentChunk = data;
|
@@ -11858,7 +11947,7 @@ const State = {
|
|
11858
11947
|
};
|
11859
11948
|
class BaseStreamController extends TaskLoop {
|
11860
11949
|
constructor(hls, fragmentTracker, keyLoader, logPrefix, playlistType) {
|
11861
|
-
super();
|
11950
|
+
super(logPrefix, hls.logger);
|
11862
11951
|
this.hls = void 0;
|
11863
11952
|
this.fragPrevious = null;
|
11864
11953
|
this.fragCurrent = null;
|
@@ -11883,22 +11972,98 @@ class BaseStreamController extends TaskLoop {
|
|
11883
11972
|
this.startFragRequested = false;
|
11884
11973
|
this.decrypter = void 0;
|
11885
11974
|
this.initPTS = [];
|
11886
|
-
this.
|
11887
|
-
this.
|
11888
|
-
this.
|
11889
|
-
|
11890
|
-
|
11975
|
+
this.buffering = true;
|
11976
|
+
this.loadingParts = false;
|
11977
|
+
this.onMediaSeeking = () => {
|
11978
|
+
const {
|
11979
|
+
config,
|
11980
|
+
fragCurrent,
|
11981
|
+
media,
|
11982
|
+
mediaBuffer,
|
11983
|
+
state
|
11984
|
+
} = this;
|
11985
|
+
const currentTime = media ? media.currentTime : 0;
|
11986
|
+
const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
|
11987
|
+
this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
|
11988
|
+
if (this.state === State.ENDED) {
|
11989
|
+
this.resetLoadingState();
|
11990
|
+
} else if (fragCurrent) {
|
11991
|
+
// Seeking while frag load is in progress
|
11992
|
+
const tolerance = config.maxFragLookUpTolerance;
|
11993
|
+
const fragStartOffset = fragCurrent.start - tolerance;
|
11994
|
+
const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
|
11995
|
+
// if seeking out of buffered range or into new one
|
11996
|
+
if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
|
11997
|
+
const pastFragment = currentTime > fragEndOffset;
|
11998
|
+
// if the seek position is outside the current fragment range
|
11999
|
+
if (currentTime < fragStartOffset || pastFragment) {
|
12000
|
+
if (pastFragment && fragCurrent.loader) {
|
12001
|
+
this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
|
12002
|
+
fragCurrent.abortRequests();
|
12003
|
+
this.resetLoadingState();
|
12004
|
+
}
|
12005
|
+
this.fragPrevious = null;
|
12006
|
+
}
|
12007
|
+
}
|
12008
|
+
}
|
12009
|
+
if (media) {
|
12010
|
+
// Remove gap fragments
|
12011
|
+
this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
|
12012
|
+
this.lastCurrentTime = currentTime;
|
12013
|
+
if (!this.loadingParts) {
|
12014
|
+
const bufferEnd = Math.max(bufferInfo.end, currentTime);
|
12015
|
+
const shouldLoadParts = this.shouldLoadParts(this.getLevelDetails(), bufferEnd);
|
12016
|
+
if (shouldLoadParts) {
|
12017
|
+
this.log(`LL-Part loading ON after seeking to ${currentTime.toFixed(2)} with buffer @${bufferEnd.toFixed(2)}`);
|
12018
|
+
this.loadingParts = shouldLoadParts;
|
12019
|
+
}
|
12020
|
+
}
|
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
|
+
this.onMediaEnded = () => {
|
12032
|
+
// reset startPosition and lastCurrentTime to restart playback @ stream beginning
|
12033
|
+
this.startPosition = this.lastCurrentTime = 0;
|
12034
|
+
if (this.playlistType === PlaylistLevelType.MAIN) {
|
12035
|
+
this.hls.trigger(Events.MEDIA_ENDED, {
|
12036
|
+
stalled: false
|
12037
|
+
});
|
12038
|
+
}
|
12039
|
+
};
|
11891
12040
|
this.playlistType = playlistType;
|
11892
|
-
this.logPrefix = logPrefix;
|
11893
|
-
this.log = logger.log.bind(logger, `${logPrefix}:`);
|
11894
|
-
this.warn = logger.warn.bind(logger, `${logPrefix}:`);
|
11895
12041
|
this.hls = hls;
|
11896
12042
|
this.fragmentLoader = new FragmentLoader(hls.config);
|
11897
12043
|
this.keyLoader = keyLoader;
|
11898
12044
|
this.fragmentTracker = fragmentTracker;
|
11899
12045
|
this.config = hls.config;
|
11900
12046
|
this.decrypter = new Decrypter(hls.config);
|
12047
|
+
}
|
12048
|
+
registerListeners() {
|
12049
|
+
const {
|
12050
|
+
hls
|
12051
|
+
} = this;
|
12052
|
+
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
12053
|
+
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
12054
|
+
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
11901
12055
|
hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
12056
|
+
hls.on(Events.ERROR, this.onError, this);
|
12057
|
+
}
|
12058
|
+
unregisterListeners() {
|
12059
|
+
const {
|
12060
|
+
hls
|
12061
|
+
} = this;
|
12062
|
+
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
12063
|
+
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
12064
|
+
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
12065
|
+
hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
12066
|
+
hls.off(Events.ERROR, this.onError, this);
|
11902
12067
|
}
|
11903
12068
|
doTick() {
|
11904
12069
|
this.onTickEnd();
|
@@ -11922,6 +12087,12 @@ class BaseStreamController extends TaskLoop {
|
|
11922
12087
|
this.clearNextTick();
|
11923
12088
|
this.state = State.STOPPED;
|
11924
12089
|
}
|
12090
|
+
pauseBuffering() {
|
12091
|
+
this.buffering = false;
|
12092
|
+
}
|
12093
|
+
resumeBuffering() {
|
12094
|
+
this.buffering = true;
|
12095
|
+
}
|
11925
12096
|
_streamEnded(bufferInfo, levelDetails) {
|
11926
12097
|
// If playlist is live, there is another buffered range after the current range, nothing buffered, media is detached,
|
11927
12098
|
// of nothing loading/loaded return false
|
@@ -11952,10 +12123,8 @@ class BaseStreamController extends TaskLoop {
|
|
11952
12123
|
}
|
11953
12124
|
onMediaAttached(event, data) {
|
11954
12125
|
const media = this.media = this.mediaBuffer = data.media;
|
11955
|
-
|
11956
|
-
|
11957
|
-
media.addEventListener('seeking', this.onvseeking);
|
11958
|
-
media.addEventListener('ended', this.onvended);
|
12126
|
+
media.addEventListener('seeking', this.onMediaSeeking);
|
12127
|
+
media.addEventListener('ended', this.onMediaEnded);
|
11959
12128
|
const config = this.config;
|
11960
12129
|
if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
|
11961
12130
|
this.startLoad(config.startPosition);
|
@@ -11969,10 +12138,9 @@ class BaseStreamController extends TaskLoop {
|
|
11969
12138
|
}
|
11970
12139
|
|
11971
12140
|
// remove video listeners
|
11972
|
-
if (media
|
11973
|
-
media.removeEventListener('seeking', this.
|
11974
|
-
media.removeEventListener('ended', this.
|
11975
|
-
this.onvseeking = this.onvended = null;
|
12141
|
+
if (media) {
|
12142
|
+
media.removeEventListener('seeking', this.onMediaSeeking);
|
12143
|
+
media.removeEventListener('ended', this.onMediaEnded);
|
11976
12144
|
}
|
11977
12145
|
if (this.keyLoader) {
|
11978
12146
|
this.keyLoader.detach();
|
@@ -11982,56 +12150,8 @@ class BaseStreamController extends TaskLoop {
|
|
11982
12150
|
this.fragmentTracker.removeAllFragments();
|
11983
12151
|
this.stopLoad();
|
11984
12152
|
}
|
11985
|
-
|
11986
|
-
|
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
|
-
}
|
12153
|
+
onManifestLoading() {}
|
12154
|
+
onError(event, data) {}
|
12035
12155
|
onManifestLoaded(event, data) {
|
12036
12156
|
this.startTimeOffset = data.startTimeOffset;
|
12037
12157
|
this.initPTS = [];
|
@@ -12041,7 +12161,7 @@ class BaseStreamController extends TaskLoop {
|
|
12041
12161
|
this.stopLoad();
|
12042
12162
|
super.onHandlerDestroying();
|
12043
12163
|
// @ts-ignore
|
12044
|
-
this.hls = null;
|
12164
|
+
this.hls = this.onMediaSeeking = this.onMediaEnded = null;
|
12045
12165
|
}
|
12046
12166
|
onHandlerDestroyed() {
|
12047
12167
|
this.state = State.STOPPED;
|
@@ -12172,10 +12292,10 @@ class BaseStreamController extends TaskLoop {
|
|
12172
12292
|
const decryptData = frag.decryptdata;
|
12173
12293
|
|
12174
12294
|
// check to see if the payload needs to be decrypted
|
12175
|
-
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method
|
12295
|
+
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
|
12176
12296
|
const startTime = self.performance.now();
|
12177
12297
|
// decrypt init segment data
|
12178
|
-
return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
|
12298
|
+
return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch(err => {
|
12179
12299
|
hls.trigger(Events.ERROR, {
|
12180
12300
|
type: ErrorTypes.MEDIA_ERROR,
|
12181
12301
|
details: ErrorDetails.FRAG_DECRYPT_ERROR,
|
@@ -12287,7 +12407,7 @@ class BaseStreamController extends TaskLoop {
|
|
12287
12407
|
}
|
12288
12408
|
let keyLoadingPromise = null;
|
12289
12409
|
if (frag.encrypted && !((_frag$decryptdata = frag.decryptdata) != null && _frag$decryptdata.key)) {
|
12290
|
-
this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.
|
12410
|
+
this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'} ${frag.level}`);
|
12291
12411
|
this.state = State.KEY_LOADING;
|
12292
12412
|
this.fragCurrent = frag;
|
12293
12413
|
keyLoadingPromise = this.keyLoader.load(frag).then(keyLoadedData => {
|
@@ -12308,8 +12428,16 @@ class BaseStreamController extends TaskLoop {
|
|
12308
12428
|
} else if (!frag.encrypted && details.encryptedFragments.length) {
|
12309
12429
|
this.keyLoader.loadClear(frag, details.encryptedFragments);
|
12310
12430
|
}
|
12431
|
+
const fragPrevious = this.fragPrevious;
|
12432
|
+
if (frag.sn !== 'initSegment' && (!fragPrevious || frag.sn !== fragPrevious.sn)) {
|
12433
|
+
const shouldLoadParts = this.shouldLoadParts(level.details, frag.end);
|
12434
|
+
if (shouldLoadParts !== this.loadingParts) {
|
12435
|
+
this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} loading sn ${fragPrevious == null ? void 0 : fragPrevious.sn}->${frag.sn}`);
|
12436
|
+
this.loadingParts = shouldLoadParts;
|
12437
|
+
}
|
12438
|
+
}
|
12311
12439
|
targetBufferTime = Math.max(frag.start, targetBufferTime || 0);
|
12312
|
-
if (this.
|
12440
|
+
if (this.loadingParts && frag.sn !== 'initSegment') {
|
12313
12441
|
const partList = details.partList;
|
12314
12442
|
if (partList && progressCallback) {
|
12315
12443
|
if (targetBufferTime > frag.end && details.fragmentHint) {
|
@@ -12318,7 +12446,7 @@ class BaseStreamController extends TaskLoop {
|
|
12318
12446
|
const partIndex = this.getNextPart(partList, frag, targetBufferTime);
|
12319
12447
|
if (partIndex > -1) {
|
12320
12448
|
const part = partList[partIndex];
|
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.
|
12449
|
+
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.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
|
12322
12450
|
this.nextLoadPosition = part.start + part.duration;
|
12323
12451
|
this.state = State.FRAG_LOADING;
|
12324
12452
|
let _result;
|
@@ -12347,7 +12475,14 @@ class BaseStreamController extends TaskLoop {
|
|
12347
12475
|
}
|
12348
12476
|
}
|
12349
12477
|
}
|
12350
|
-
|
12478
|
+
if (frag.sn !== 'initSegment' && this.loadingParts) {
|
12479
|
+
this.log(`LL-Part loading OFF after next part miss @${targetBufferTime.toFixed(2)}`);
|
12480
|
+
this.loadingParts = false;
|
12481
|
+
} else if (!frag.url) {
|
12482
|
+
// Selected fragment hint for part but not loading parts
|
12483
|
+
return Promise.resolve(null);
|
12484
|
+
}
|
12485
|
+
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))}`);
|
12351
12486
|
// Don't update nextLoadPosition for fragments which are not buffered
|
12352
12487
|
if (isFiniteNumber(frag.sn) && !this.bitrateTest) {
|
12353
12488
|
this.nextLoadPosition = frag.start + frag.duration;
|
@@ -12445,8 +12580,36 @@ class BaseStreamController extends TaskLoop {
|
|
12445
12580
|
if (part) {
|
12446
12581
|
part.stats.parsing.end = now;
|
12447
12582
|
}
|
12583
|
+
// See if part loading should be disabled/enabled based on buffer and playback position.
|
12584
|
+
if (frag.sn !== 'initSegment') {
|
12585
|
+
const levelDetails = this.getLevelDetails();
|
12586
|
+
const loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
|
12587
|
+
const shouldLoadParts = loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
|
12588
|
+
if (shouldLoadParts !== this.loadingParts) {
|
12589
|
+
this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} after parsing segment ending @${frag.end.toFixed(2)}`);
|
12590
|
+
this.loadingParts = shouldLoadParts;
|
12591
|
+
}
|
12592
|
+
}
|
12448
12593
|
this.updateLevelTiming(frag, part, level, chunkMeta.partial);
|
12449
12594
|
}
|
12595
|
+
shouldLoadParts(details, bufferEnd) {
|
12596
|
+
if (this.config.lowLatencyMode) {
|
12597
|
+
if (!details) {
|
12598
|
+
return this.loadingParts;
|
12599
|
+
}
|
12600
|
+
if (details != null && details.partList) {
|
12601
|
+
var _details$fragmentHint;
|
12602
|
+
// Buffer must be ahead of first part + duration of parts after last segment
|
12603
|
+
// and playback must be at or past segment adjacent to part list
|
12604
|
+
const firstPart = details.partList[0];
|
12605
|
+
const safePartStart = firstPart.end + (((_details$fragmentHint = details.fragmentHint) == null ? void 0 : _details$fragmentHint.duration) || 0);
|
12606
|
+
if (bufferEnd >= safePartStart && this.lastCurrentTime > firstPart.start - firstPart.fragment.duration) {
|
12607
|
+
return true;
|
12608
|
+
}
|
12609
|
+
}
|
12610
|
+
}
|
12611
|
+
return false;
|
12612
|
+
}
|
12450
12613
|
getCurrentContext(chunkMeta) {
|
12451
12614
|
const {
|
12452
12615
|
levels,
|
@@ -12595,7 +12758,8 @@ class BaseStreamController extends TaskLoop {
|
|
12595
12758
|
config
|
12596
12759
|
} = this;
|
12597
12760
|
const start = fragments[0].start;
|
12598
|
-
|
12761
|
+
const canLoadParts = config.lowLatencyMode && !!levelDetails.partList;
|
12762
|
+
let frag = null;
|
12599
12763
|
if (levelDetails.live) {
|
12600
12764
|
const initialLiveManifestSize = config.initialLiveManifestSize;
|
12601
12765
|
if (fragLen < initialLiveManifestSize) {
|
@@ -12607,6 +12771,10 @@ class BaseStreamController extends TaskLoop {
|
|
12607
12771
|
// Do not load using live logic if the starting frag is requested - we want to use getFragmentAtPosition() so that
|
12608
12772
|
// we get the fragment matching that start time
|
12609
12773
|
if (!levelDetails.PTSKnown && !this.startFragRequested && this.startPosition === -1 || pos < start) {
|
12774
|
+
if (canLoadParts && !this.loadingParts) {
|
12775
|
+
this.log(`LL-Part loading ON for initial live fragment`);
|
12776
|
+
this.loadingParts = true;
|
12777
|
+
}
|
12610
12778
|
frag = this.getInitialLiveFragment(levelDetails, fragments);
|
12611
12779
|
this.startPosition = this.nextLoadPosition = frag ? this.hls.liveSyncPosition || frag.start : pos;
|
12612
12780
|
}
|
@@ -12617,7 +12785,7 @@ class BaseStreamController extends TaskLoop {
|
|
12617
12785
|
|
12618
12786
|
// If we haven't run into any special cases already, just load the fragment most closely matching the requested position
|
12619
12787
|
if (!frag) {
|
12620
|
-
const end =
|
12788
|
+
const end = this.loadingParts ? levelDetails.partEnd : levelDetails.fragmentEnd;
|
12621
12789
|
frag = this.getFragmentAtPosition(pos, end, levelDetails);
|
12622
12790
|
}
|
12623
12791
|
return this.mapToInitFragWhenRequired(frag);
|
@@ -12739,7 +12907,7 @@ class BaseStreamController extends TaskLoop {
|
|
12739
12907
|
} = levelDetails;
|
12740
12908
|
const tolerance = config.maxFragLookUpTolerance;
|
12741
12909
|
const partList = levelDetails.partList;
|
12742
|
-
const loadingParts = !!(
|
12910
|
+
const loadingParts = !!(this.loadingParts && partList != null && partList.length && fragmentHint);
|
12743
12911
|
if (loadingParts && fragmentHint && !this.bitrateTest) {
|
12744
12912
|
// Include incomplete fragment with parts at end
|
12745
12913
|
fragments = fragments.concat(fragmentHint);
|
@@ -12932,7 +13100,7 @@ class BaseStreamController extends TaskLoop {
|
|
12932
13100
|
errorAction.resolved = true;
|
12933
13101
|
}
|
12934
13102
|
} else {
|
12935
|
-
|
13103
|
+
this.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
|
12936
13104
|
return;
|
12937
13105
|
}
|
12938
13106
|
} else if ((errorAction == null ? void 0 : errorAction.action) === NetworkErrorAction.SendAlternateToPenaltyBox) {
|
@@ -13327,6 +13495,7 @@ const initPTSFn = (timestamp, timeOffset, initPTS) => {
|
|
13327
13495
|
*/
|
13328
13496
|
function getAudioConfig(observer, data, offset, audioCodec) {
|
13329
13497
|
let adtsObjectType;
|
13498
|
+
let originalAdtsObjectType;
|
13330
13499
|
let adtsExtensionSamplingIndex;
|
13331
13500
|
let adtsChannelConfig;
|
13332
13501
|
let config;
|
@@ -13334,7 +13503,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
13334
13503
|
const manifestCodec = audioCodec;
|
13335
13504
|
const adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
|
13336
13505
|
// byte 2
|
13337
|
-
adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
|
13506
|
+
adtsObjectType = originalAdtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
|
13338
13507
|
const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
|
13339
13508
|
if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
|
13340
13509
|
const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
|
@@ -13351,8 +13520,8 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
13351
13520
|
// byte 3
|
13352
13521
|
adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6;
|
13353
13522
|
logger.log(`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`);
|
13354
|
-
//
|
13355
|
-
if (/firefox/i.test(userAgent)) {
|
13523
|
+
// Firefox and Pale Moon: freq less than 24kHz = AAC SBR (HE-AAC)
|
13524
|
+
if (/firefox|palemoon/i.test(userAgent)) {
|
13356
13525
|
if (adtsSamplingIndex >= 6) {
|
13357
13526
|
adtsObjectType = 5;
|
13358
13527
|
config = new Array(4);
|
@@ -13446,6 +13615,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
13446
13615
|
samplerate: adtsSamplingRates[adtsSamplingIndex],
|
13447
13616
|
channelCount: adtsChannelConfig,
|
13448
13617
|
codec: 'mp4a.40.' + adtsObjectType,
|
13618
|
+
parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
|
13449
13619
|
manifestCodec
|
13450
13620
|
};
|
13451
13621
|
}
|
@@ -13500,7 +13670,8 @@ function initTrackConfig(track, observer, data, offset, audioCodec) {
|
|
13500
13670
|
track.channelCount = config.channelCount;
|
13501
13671
|
track.codec = config.codec;
|
13502
13672
|
track.manifestCodec = config.manifestCodec;
|
13503
|
-
|
13673
|
+
track.parsedCodec = config.parsedCodec;
|
13674
|
+
logger.log(`parsed codec:${track.parsedCodec}, codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
|
13504
13675
|
}
|
13505
13676
|
}
|
13506
13677
|
function getFrameDuration(samplerate) {
|
@@ -13978,17 +14149,121 @@ class BaseVideoParser {
|
|
13978
14149
|
logger.log(VideoSample.pts + '/' + VideoSample.dts + ':' + VideoSample.debug);
|
13979
14150
|
}
|
13980
14151
|
}
|
13981
|
-
|
13982
|
-
|
13983
|
-
|
13984
|
-
|
13985
|
-
|
13986
|
-
|
13987
|
-
|
13988
|
-
|
13989
|
-
|
13990
|
-
|
13991
|
-
|
14152
|
+
parseNALu(track, array) {
|
14153
|
+
const len = array.byteLength;
|
14154
|
+
let state = track.naluState || 0;
|
14155
|
+
const lastState = state;
|
14156
|
+
const units = [];
|
14157
|
+
let i = 0;
|
14158
|
+
let value;
|
14159
|
+
let overflow;
|
14160
|
+
let unitType;
|
14161
|
+
let lastUnitStart = -1;
|
14162
|
+
let lastUnitType = 0;
|
14163
|
+
// logger.log('PES:' + Hex.hexDump(array));
|
14164
|
+
|
14165
|
+
if (state === -1) {
|
14166
|
+
// special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
|
14167
|
+
lastUnitStart = 0;
|
14168
|
+
// NALu type is value read from offset 0
|
14169
|
+
lastUnitType = this.getNALuType(array, 0);
|
14170
|
+
state = 0;
|
14171
|
+
i = 1;
|
14172
|
+
}
|
14173
|
+
while (i < len) {
|
14174
|
+
value = array[i++];
|
14175
|
+
// optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
|
14176
|
+
if (!state) {
|
14177
|
+
state = value ? 0 : 1;
|
14178
|
+
continue;
|
14179
|
+
}
|
14180
|
+
if (state === 1) {
|
14181
|
+
state = value ? 0 : 2;
|
14182
|
+
continue;
|
14183
|
+
}
|
14184
|
+
// here we have state either equal to 2 or 3
|
14185
|
+
if (!value) {
|
14186
|
+
state = 3;
|
14187
|
+
} else if (value === 1) {
|
14188
|
+
overflow = i - state - 1;
|
14189
|
+
if (lastUnitStart >= 0) {
|
14190
|
+
const unit = {
|
14191
|
+
data: array.subarray(lastUnitStart, overflow),
|
14192
|
+
type: lastUnitType
|
14193
|
+
};
|
14194
|
+
// logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
|
14195
|
+
units.push(unit);
|
14196
|
+
} else {
|
14197
|
+
// lastUnitStart is undefined => this is the first start code found in this PES packet
|
14198
|
+
// first check if start code delimiter is overlapping between 2 PES packets,
|
14199
|
+
// ie it started in last packet (lastState not zero)
|
14200
|
+
// and ended at the beginning of this PES packet (i <= 4 - lastState)
|
14201
|
+
const lastUnit = this.getLastNalUnit(track.samples);
|
14202
|
+
if (lastUnit) {
|
14203
|
+
if (lastState && i <= 4 - lastState) {
|
14204
|
+
// start delimiter overlapping between PES packets
|
14205
|
+
// strip start delimiter bytes from the end of last NAL unit
|
14206
|
+
// check if lastUnit had a state different from zero
|
14207
|
+
if (lastUnit.state) {
|
14208
|
+
// strip last bytes
|
14209
|
+
lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
|
14210
|
+
}
|
14211
|
+
}
|
14212
|
+
// If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
|
14213
|
+
|
14214
|
+
if (overflow > 0) {
|
14215
|
+
// logger.log('first NALU found with overflow:' + overflow);
|
14216
|
+
lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
|
14217
|
+
lastUnit.state = 0;
|
14218
|
+
}
|
14219
|
+
}
|
14220
|
+
}
|
14221
|
+
// check if we can read unit type
|
14222
|
+
if (i < len) {
|
14223
|
+
unitType = this.getNALuType(array, i);
|
14224
|
+
// logger.log('find NALU @ offset:' + i + ',type:' + unitType);
|
14225
|
+
lastUnitStart = i;
|
14226
|
+
lastUnitType = unitType;
|
14227
|
+
state = 0;
|
14228
|
+
} else {
|
14229
|
+
// not enough byte to read unit type. let's read it on next PES parsing
|
14230
|
+
state = -1;
|
14231
|
+
}
|
14232
|
+
} else {
|
14233
|
+
state = 0;
|
14234
|
+
}
|
14235
|
+
}
|
14236
|
+
if (lastUnitStart >= 0 && state >= 0) {
|
14237
|
+
const unit = {
|
14238
|
+
data: array.subarray(lastUnitStart, len),
|
14239
|
+
type: lastUnitType,
|
14240
|
+
state: state
|
14241
|
+
};
|
14242
|
+
units.push(unit);
|
14243
|
+
// logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
|
14244
|
+
}
|
14245
|
+
// no NALu found
|
14246
|
+
if (units.length === 0) {
|
14247
|
+
// append pes.data to previous NAL unit
|
14248
|
+
const lastUnit = this.getLastNalUnit(track.samples);
|
14249
|
+
if (lastUnit) {
|
14250
|
+
lastUnit.data = appendUint8Array(lastUnit.data, array);
|
14251
|
+
}
|
14252
|
+
}
|
14253
|
+
track.naluState = state;
|
14254
|
+
return units;
|
14255
|
+
}
|
14256
|
+
}
|
14257
|
+
|
14258
|
+
/**
|
14259
|
+
* Parser for exponential Golomb codes, a variable-bitwidth number encoding scheme used by h264.
|
14260
|
+
*/
|
14261
|
+
|
14262
|
+
class ExpGolomb {
|
14263
|
+
constructor(data) {
|
14264
|
+
this.data = void 0;
|
14265
|
+
this.bytesAvailable = void 0;
|
14266
|
+
this.word = void 0;
|
13992
14267
|
this.bitsAvailable = void 0;
|
13993
14268
|
this.data = data;
|
13994
14269
|
// the number of bytes left to examine in this.data
|
@@ -14120,194 +14395,11 @@ class ExpGolomb {
|
|
14120
14395
|
readUInt() {
|
14121
14396
|
return this.readBits(32);
|
14122
14397
|
}
|
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
|
-
}
|
14306
14398
|
}
|
14307
14399
|
|
14308
14400
|
class AvcVideoParser extends BaseVideoParser {
|
14309
|
-
|
14310
|
-
const units = this.
|
14401
|
+
parsePES(track, textTrack, pes, last, duration) {
|
14402
|
+
const units = this.parseNALu(track, pes.data);
|
14311
14403
|
let VideoSample = this.VideoSample;
|
14312
14404
|
let push;
|
14313
14405
|
let spsfound = false;
|
@@ -14332,7 +14424,7 @@ class AvcVideoParser extends BaseVideoParser {
|
|
14332
14424
|
// only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
|
14333
14425
|
if (spsfound && data.length > 4) {
|
14334
14426
|
// retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
|
14335
|
-
const sliceType =
|
14427
|
+
const sliceType = this.readSliceType(data);
|
14336
14428
|
// 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
|
14337
14429
|
// SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
|
14338
14430
|
// An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
|
@@ -14386,8 +14478,7 @@ class AvcVideoParser extends BaseVideoParser {
|
|
14386
14478
|
push = true;
|
14387
14479
|
spsfound = true;
|
14388
14480
|
const sps = unit.data;
|
14389
|
-
const
|
14390
|
-
const config = expGolombDecoder.readSPS();
|
14481
|
+
const config = this.readSPS(sps);
|
14391
14482
|
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]) {
|
14392
14483
|
track.width = config.width;
|
14393
14484
|
track.height = config.height;
|
@@ -14443,109 +14534,192 @@ class AvcVideoParser extends BaseVideoParser {
|
|
14443
14534
|
this.VideoSample = null;
|
14444
14535
|
}
|
14445
14536
|
}
|
14446
|
-
|
14447
|
-
|
14448
|
-
|
14449
|
-
|
14450
|
-
const
|
14451
|
-
|
14452
|
-
|
14453
|
-
|
14454
|
-
|
14455
|
-
|
14456
|
-
|
14457
|
-
|
14537
|
+
getNALuType(data, offset) {
|
14538
|
+
return data[offset] & 0x1f;
|
14539
|
+
}
|
14540
|
+
readSliceType(data) {
|
14541
|
+
const eg = new ExpGolomb(data);
|
14542
|
+
// skip NALu type
|
14543
|
+
eg.readUByte();
|
14544
|
+
// discard first_mb_in_slice
|
14545
|
+
eg.readUEG();
|
14546
|
+
// return slice_type
|
14547
|
+
return eg.readUEG();
|
14548
|
+
}
|
14458
14549
|
|
14459
|
-
|
14460
|
-
|
14461
|
-
|
14462
|
-
|
14463
|
-
|
14464
|
-
|
14465
|
-
|
14466
|
-
|
14467
|
-
|
14468
|
-
|
14469
|
-
|
14470
|
-
if (
|
14471
|
-
|
14472
|
-
|
14473
|
-
}
|
14474
|
-
if (state === 1) {
|
14475
|
-
state = value ? 0 : 2;
|
14476
|
-
continue;
|
14550
|
+
/**
|
14551
|
+
* The scaling list is optionally transmitted as part of a sequence parameter
|
14552
|
+
* set and is not relevant to transmuxing.
|
14553
|
+
* @param count the number of entries in this scaling list
|
14554
|
+
* @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
|
14555
|
+
*/
|
14556
|
+
skipScalingList(count, reader) {
|
14557
|
+
let lastScale = 8;
|
14558
|
+
let nextScale = 8;
|
14559
|
+
let deltaScale;
|
14560
|
+
for (let j = 0; j < count; j++) {
|
14561
|
+
if (nextScale !== 0) {
|
14562
|
+
deltaScale = reader.readEG();
|
14563
|
+
nextScale = (lastScale + deltaScale + 256) % 256;
|
14477
14564
|
}
|
14478
|
-
|
14479
|
-
|
14480
|
-
|
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.
|
14565
|
+
lastScale = nextScale === 0 ? lastScale : nextScale;
|
14566
|
+
}
|
14567
|
+
}
|
14507
14568
|
|
14508
|
-
|
14509
|
-
|
14510
|
-
|
14511
|
-
|
14569
|
+
/**
|
14570
|
+
* Read a sequence parameter set and return some interesting video
|
14571
|
+
* properties. A sequence parameter set is the H264 metadata that
|
14572
|
+
* describes the properties of upcoming video frames.
|
14573
|
+
* @returns an object with configuration parsed from the
|
14574
|
+
* sequence parameter set, including the dimensions of the
|
14575
|
+
* associated video frames.
|
14576
|
+
*/
|
14577
|
+
readSPS(sps) {
|
14578
|
+
const eg = new ExpGolomb(sps);
|
14579
|
+
let frameCropLeftOffset = 0;
|
14580
|
+
let frameCropRightOffset = 0;
|
14581
|
+
let frameCropTopOffset = 0;
|
14582
|
+
let frameCropBottomOffset = 0;
|
14583
|
+
let numRefFramesInPicOrderCntCycle;
|
14584
|
+
let scalingListCount;
|
14585
|
+
let i;
|
14586
|
+
const readUByte = eg.readUByte.bind(eg);
|
14587
|
+
const readBits = eg.readBits.bind(eg);
|
14588
|
+
const readUEG = eg.readUEG.bind(eg);
|
14589
|
+
const readBoolean = eg.readBoolean.bind(eg);
|
14590
|
+
const skipBits = eg.skipBits.bind(eg);
|
14591
|
+
const skipEG = eg.skipEG.bind(eg);
|
14592
|
+
const skipUEG = eg.skipUEG.bind(eg);
|
14593
|
+
const skipScalingList = this.skipScalingList.bind(this);
|
14594
|
+
readUByte();
|
14595
|
+
const profileIdc = readUByte(); // profile_idc
|
14596
|
+
readBits(5); // profileCompat constraint_set[0-4]_flag, u(5)
|
14597
|
+
skipBits(3); // reserved_zero_3bits u(3),
|
14598
|
+
readUByte(); // level_idc u(8)
|
14599
|
+
skipUEG(); // seq_parameter_set_id
|
14600
|
+
// some profiles have more optional data we don't need
|
14601
|
+
if (profileIdc === 100 || profileIdc === 110 || profileIdc === 122 || profileIdc === 244 || profileIdc === 44 || profileIdc === 83 || profileIdc === 86 || profileIdc === 118 || profileIdc === 128) {
|
14602
|
+
const chromaFormatIdc = readUEG();
|
14603
|
+
if (chromaFormatIdc === 3) {
|
14604
|
+
skipBits(1);
|
14605
|
+
} // separate_colour_plane_flag
|
14606
|
+
|
14607
|
+
skipUEG(); // bit_depth_luma_minus8
|
14608
|
+
skipUEG(); // bit_depth_chroma_minus8
|
14609
|
+
skipBits(1); // qpprime_y_zero_transform_bypass_flag
|
14610
|
+
if (readBoolean()) {
|
14611
|
+
// seq_scaling_matrix_present_flag
|
14612
|
+
scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
|
14613
|
+
for (i = 0; i < scalingListCount; i++) {
|
14614
|
+
if (readBoolean()) {
|
14615
|
+
// seq_scaling_list_present_flag[ i ]
|
14616
|
+
if (i < 6) {
|
14617
|
+
skipScalingList(16, eg);
|
14618
|
+
} else {
|
14619
|
+
skipScalingList(64, eg);
|
14512
14620
|
}
|
14513
14621
|
}
|
14514
14622
|
}
|
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;
|
14528
14623
|
}
|
14529
14624
|
}
|
14530
|
-
|
14531
|
-
|
14532
|
-
|
14533
|
-
|
14534
|
-
|
14535
|
-
|
14536
|
-
|
14537
|
-
|
14625
|
+
skipUEG(); // log2_max_frame_num_minus4
|
14626
|
+
const picOrderCntType = readUEG();
|
14627
|
+
if (picOrderCntType === 0) {
|
14628
|
+
readUEG(); // log2_max_pic_order_cnt_lsb_minus4
|
14629
|
+
} else if (picOrderCntType === 1) {
|
14630
|
+
skipBits(1); // delta_pic_order_always_zero_flag
|
14631
|
+
skipEG(); // offset_for_non_ref_pic
|
14632
|
+
skipEG(); // offset_for_top_to_bottom_field
|
14633
|
+
numRefFramesInPicOrderCntCycle = readUEG();
|
14634
|
+
for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
|
14635
|
+
skipEG();
|
14636
|
+
} // offset_for_ref_frame[ i ]
|
14538
14637
|
}
|
14539
|
-
//
|
14540
|
-
|
14541
|
-
|
14542
|
-
|
14543
|
-
|
14544
|
-
|
14638
|
+
skipUEG(); // max_num_ref_frames
|
14639
|
+
skipBits(1); // gaps_in_frame_num_value_allowed_flag
|
14640
|
+
const picWidthInMbsMinus1 = readUEG();
|
14641
|
+
const picHeightInMapUnitsMinus1 = readUEG();
|
14642
|
+
const frameMbsOnlyFlag = readBits(1);
|
14643
|
+
if (frameMbsOnlyFlag === 0) {
|
14644
|
+
skipBits(1);
|
14645
|
+
} // mb_adaptive_frame_field_flag
|
14646
|
+
|
14647
|
+
skipBits(1); // direct_8x8_inference_flag
|
14648
|
+
if (readBoolean()) {
|
14649
|
+
// frame_cropping_flag
|
14650
|
+
frameCropLeftOffset = readUEG();
|
14651
|
+
frameCropRightOffset = readUEG();
|
14652
|
+
frameCropTopOffset = readUEG();
|
14653
|
+
frameCropBottomOffset = readUEG();
|
14654
|
+
}
|
14655
|
+
let pixelRatio = [1, 1];
|
14656
|
+
if (readBoolean()) {
|
14657
|
+
// vui_parameters_present_flag
|
14658
|
+
if (readBoolean()) {
|
14659
|
+
// aspect_ratio_info_present_flag
|
14660
|
+
const aspectRatioIdc = readUByte();
|
14661
|
+
switch (aspectRatioIdc) {
|
14662
|
+
case 1:
|
14663
|
+
pixelRatio = [1, 1];
|
14664
|
+
break;
|
14665
|
+
case 2:
|
14666
|
+
pixelRatio = [12, 11];
|
14667
|
+
break;
|
14668
|
+
case 3:
|
14669
|
+
pixelRatio = [10, 11];
|
14670
|
+
break;
|
14671
|
+
case 4:
|
14672
|
+
pixelRatio = [16, 11];
|
14673
|
+
break;
|
14674
|
+
case 5:
|
14675
|
+
pixelRatio = [40, 33];
|
14676
|
+
break;
|
14677
|
+
case 6:
|
14678
|
+
pixelRatio = [24, 11];
|
14679
|
+
break;
|
14680
|
+
case 7:
|
14681
|
+
pixelRatio = [20, 11];
|
14682
|
+
break;
|
14683
|
+
case 8:
|
14684
|
+
pixelRatio = [32, 11];
|
14685
|
+
break;
|
14686
|
+
case 9:
|
14687
|
+
pixelRatio = [80, 33];
|
14688
|
+
break;
|
14689
|
+
case 10:
|
14690
|
+
pixelRatio = [18, 11];
|
14691
|
+
break;
|
14692
|
+
case 11:
|
14693
|
+
pixelRatio = [15, 11];
|
14694
|
+
break;
|
14695
|
+
case 12:
|
14696
|
+
pixelRatio = [64, 33];
|
14697
|
+
break;
|
14698
|
+
case 13:
|
14699
|
+
pixelRatio = [160, 99];
|
14700
|
+
break;
|
14701
|
+
case 14:
|
14702
|
+
pixelRatio = [4, 3];
|
14703
|
+
break;
|
14704
|
+
case 15:
|
14705
|
+
pixelRatio = [3, 2];
|
14706
|
+
break;
|
14707
|
+
case 16:
|
14708
|
+
pixelRatio = [2, 1];
|
14709
|
+
break;
|
14710
|
+
case 255:
|
14711
|
+
{
|
14712
|
+
pixelRatio = [readUByte() << 8 | readUByte(), readUByte() << 8 | readUByte()];
|
14713
|
+
break;
|
14714
|
+
}
|
14715
|
+
}
|
14545
14716
|
}
|
14546
14717
|
}
|
14547
|
-
|
14548
|
-
|
14718
|
+
return {
|
14719
|
+
width: Math.ceil((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2),
|
14720
|
+
height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - (frameMbsOnlyFlag ? 2 : 4) * (frameCropTopOffset + frameCropBottomOffset),
|
14721
|
+
pixelRatio: pixelRatio
|
14722
|
+
};
|
14549
14723
|
}
|
14550
14724
|
}
|
14551
14725
|
|
@@ -14563,7 +14737,7 @@ class SampleAesDecrypter {
|
|
14563
14737
|
});
|
14564
14738
|
}
|
14565
14739
|
decryptBuffer(encryptedData) {
|
14566
|
-
return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer);
|
14740
|
+
return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer, DecrypterAesMode.cbc);
|
14567
14741
|
}
|
14568
14742
|
|
14569
14743
|
// AAC - encrypt all full 16 bytes blocks starting from offset 16
|
@@ -14677,7 +14851,7 @@ class TSDemuxer {
|
|
14677
14851
|
this.observer = observer;
|
14678
14852
|
this.config = config;
|
14679
14853
|
this.typeSupported = typeSupported;
|
14680
|
-
this.videoParser =
|
14854
|
+
this.videoParser = null;
|
14681
14855
|
}
|
14682
14856
|
static probe(data) {
|
14683
14857
|
const syncOffset = TSDemuxer.syncOffset(data);
|
@@ -14842,7 +15016,16 @@ class TSDemuxer {
|
|
14842
15016
|
case videoPid:
|
14843
15017
|
if (stt) {
|
14844
15018
|
if (videoData && (pes = parsePES(videoData))) {
|
14845
|
-
this.videoParser
|
15019
|
+
if (this.videoParser === null) {
|
15020
|
+
switch (videoTrack.segmentCodec) {
|
15021
|
+
case 'avc':
|
15022
|
+
this.videoParser = new AvcVideoParser();
|
15023
|
+
break;
|
15024
|
+
}
|
15025
|
+
}
|
15026
|
+
if (this.videoParser !== null) {
|
15027
|
+
this.videoParser.parsePES(videoTrack, textTrack, pes, false, this._duration);
|
15028
|
+
}
|
14846
15029
|
}
|
14847
15030
|
videoData = {
|
14848
15031
|
data: [],
|
@@ -15004,8 +15187,17 @@ class TSDemuxer {
|
|
15004
15187
|
// try to parse last PES packets
|
15005
15188
|
let pes;
|
15006
15189
|
if (videoData && (pes = parsePES(videoData))) {
|
15007
|
-
this.videoParser
|
15008
|
-
|
15190
|
+
if (this.videoParser === null) {
|
15191
|
+
switch (videoTrack.segmentCodec) {
|
15192
|
+
case 'avc':
|
15193
|
+
this.videoParser = new AvcVideoParser();
|
15194
|
+
break;
|
15195
|
+
}
|
15196
|
+
}
|
15197
|
+
if (this.videoParser !== null) {
|
15198
|
+
this.videoParser.parsePES(videoTrack, textTrack, pes, true, this._duration);
|
15199
|
+
videoTrack.pesData = null;
|
15200
|
+
}
|
15009
15201
|
} else {
|
15010
15202
|
// either avcData null or PES truncated, keep it for next frag parsing
|
15011
15203
|
videoTrack.pesData = videoData;
|
@@ -15308,7 +15500,10 @@ function parsePMT(data, offset, typeSupported, isSampleAes) {
|
|
15308
15500
|
logger.warn('Unsupported EC-3 in M2TS found');
|
15309
15501
|
break;
|
15310
15502
|
case 0x24:
|
15311
|
-
|
15503
|
+
// ITU-T Rec. H.265 and ISO/IEC 23008-2 (HEVC)
|
15504
|
+
{
|
15505
|
+
logger.warn('Unsupported HEVC in M2TS found');
|
15506
|
+
}
|
15312
15507
|
break;
|
15313
15508
|
}
|
15314
15509
|
// move to the next table entry
|
@@ -15531,6 +15726,8 @@ class MP4 {
|
|
15531
15726
|
avc1: [],
|
15532
15727
|
// codingname
|
15533
15728
|
avcC: [],
|
15729
|
+
hvc1: [],
|
15730
|
+
hvcC: [],
|
15534
15731
|
btrt: [],
|
15535
15732
|
dinf: [],
|
15536
15733
|
dref: [],
|
@@ -15955,8 +16152,10 @@ class MP4 {
|
|
15955
16152
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.ac3(track));
|
15956
16153
|
}
|
15957
16154
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track));
|
15958
|
-
} else {
|
16155
|
+
} else if (track.segmentCodec === 'avc') {
|
15959
16156
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
|
16157
|
+
} else {
|
16158
|
+
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.hvc1(track));
|
15960
16159
|
}
|
15961
16160
|
}
|
15962
16161
|
static tkhd(track) {
|
@@ -16094,6 +16293,84 @@ class MP4 {
|
|
16094
16293
|
const result = appendUint8Array(MP4.FTYP, movie);
|
16095
16294
|
return result;
|
16096
16295
|
}
|
16296
|
+
static hvc1(track) {
|
16297
|
+
const ps = track.params;
|
16298
|
+
const units = [track.vps, track.sps, track.pps];
|
16299
|
+
const NALuLengthSize = 4;
|
16300
|
+
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]);
|
16301
|
+
|
16302
|
+
// compute hvcC size in bytes
|
16303
|
+
let length = config.length;
|
16304
|
+
for (let i = 0; i < units.length; i += 1) {
|
16305
|
+
length += 3;
|
16306
|
+
for (let j = 0; j < units[i].length; j += 1) {
|
16307
|
+
length += 2 + units[i][j].length;
|
16308
|
+
}
|
16309
|
+
}
|
16310
|
+
const hvcC = new Uint8Array(length);
|
16311
|
+
hvcC.set(config, 0);
|
16312
|
+
length = config.length;
|
16313
|
+
// append parameter set units: one vps, one or more sps and pps
|
16314
|
+
const iMax = units.length - 1;
|
16315
|
+
for (let i = 0; i < units.length; i += 1) {
|
16316
|
+
hvcC.set(new Uint8Array([32 + i | (i === iMax ? 128 : 0), 0x00, units[i].length]), length);
|
16317
|
+
length += 3;
|
16318
|
+
for (let j = 0; j < units[i].length; j += 1) {
|
16319
|
+
hvcC.set(new Uint8Array([units[i][j].length >> 8, units[i][j].length & 255]), length);
|
16320
|
+
length += 2;
|
16321
|
+
hvcC.set(units[i][j], length);
|
16322
|
+
length += units[i][j].length;
|
16323
|
+
}
|
16324
|
+
}
|
16325
|
+
const hvcc = MP4.box(MP4.types.hvcC, hvcC);
|
16326
|
+
const width = track.width;
|
16327
|
+
const height = track.height;
|
16328
|
+
const hSpacing = track.pixelRatio[0];
|
16329
|
+
const vSpacing = track.pixelRatio[1];
|
16330
|
+
return MP4.box(MP4.types.hvc1, new Uint8Array([0x00, 0x00, 0x00,
|
16331
|
+
// reserved
|
16332
|
+
0x00, 0x00, 0x00,
|
16333
|
+
// reserved
|
16334
|
+
0x00, 0x01,
|
16335
|
+
// data_reference_index
|
16336
|
+
0x00, 0x00,
|
16337
|
+
// pre_defined
|
16338
|
+
0x00, 0x00,
|
16339
|
+
// reserved
|
16340
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
16341
|
+
// pre_defined
|
16342
|
+
width >> 8 & 0xff, width & 0xff,
|
16343
|
+
// width
|
16344
|
+
height >> 8 & 0xff, height & 0xff,
|
16345
|
+
// height
|
16346
|
+
0x00, 0x48, 0x00, 0x00,
|
16347
|
+
// horizresolution
|
16348
|
+
0x00, 0x48, 0x00, 0x00,
|
16349
|
+
// vertresolution
|
16350
|
+
0x00, 0x00, 0x00, 0x00,
|
16351
|
+
// reserved
|
16352
|
+
0x00, 0x01,
|
16353
|
+
// frame_count
|
16354
|
+
0x12, 0x64, 0x61, 0x69, 0x6c,
|
16355
|
+
// dailymotion/hls.js
|
16356
|
+
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,
|
16357
|
+
// compressorname
|
16358
|
+
0x00, 0x18,
|
16359
|
+
// depth = 24
|
16360
|
+
0x11, 0x11]),
|
16361
|
+
// pre_defined = -1
|
16362
|
+
hvcc, MP4.box(MP4.types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80,
|
16363
|
+
// bufferSizeDB
|
16364
|
+
0x00, 0x2d, 0xc6, 0xc0,
|
16365
|
+
// maxBitrate
|
16366
|
+
0x00, 0x2d, 0xc6, 0xc0])),
|
16367
|
+
// avgBitrate
|
16368
|
+
MP4.box(MP4.types.pasp, new Uint8Array([hSpacing >> 24,
|
16369
|
+
// hSpacing
|
16370
|
+
hSpacing >> 16 & 0xff, hSpacing >> 8 & 0xff, hSpacing & 0xff, vSpacing >> 24,
|
16371
|
+
// vSpacing
|
16372
|
+
vSpacing >> 16 & 0xff, vSpacing >> 8 & 0xff, vSpacing & 0xff])));
|
16373
|
+
}
|
16097
16374
|
}
|
16098
16375
|
MP4.types = void 0;
|
16099
16376
|
MP4.HDLR_TYPES = void 0;
|
@@ -16469,9 +16746,9 @@ class MP4Remuxer {
|
|
16469
16746
|
const foundOverlap = delta < -1;
|
16470
16747
|
if (foundHole || foundOverlap) {
|
16471
16748
|
if (foundHole) {
|
16472
|
-
logger.warn(
|
16749
|
+
logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
|
16473
16750
|
} else {
|
16474
|
-
logger.warn(
|
16751
|
+
logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
|
16475
16752
|
}
|
16476
16753
|
if (!foundOverlap || nextAvcDts >= inputSamples[0].pts || chromeVersion) {
|
16477
16754
|
firstDTS = nextAvcDts;
|
@@ -16480,12 +16757,24 @@ class MP4Remuxer {
|
|
16480
16757
|
inputSamples[0].dts = firstDTS;
|
16481
16758
|
inputSamples[0].pts = firstPTS;
|
16482
16759
|
} else {
|
16760
|
+
let isPTSOrderRetained = true;
|
16483
16761
|
for (let i = 0; i < inputSamples.length; i++) {
|
16484
|
-
if (inputSamples[i].dts > firstPTS) {
|
16762
|
+
if (inputSamples[i].dts > firstPTS && isPTSOrderRetained) {
|
16485
16763
|
break;
|
16486
16764
|
}
|
16765
|
+
const prevPTS = inputSamples[i].pts;
|
16487
16766
|
inputSamples[i].dts -= delta;
|
16488
16767
|
inputSamples[i].pts -= delta;
|
16768
|
+
|
16769
|
+
// check to see if this sample's PTS order has changed
|
16770
|
+
// relative to the next one
|
16771
|
+
if (i < inputSamples.length - 1) {
|
16772
|
+
const nextSamplePTS = inputSamples[i + 1].pts;
|
16773
|
+
const currentSamplePTS = inputSamples[i].pts;
|
16774
|
+
const currentOrder = nextSamplePTS <= currentSamplePTS;
|
16775
|
+
const prevOrder = nextSamplePTS <= prevPTS;
|
16776
|
+
isPTSOrderRetained = currentOrder == prevOrder;
|
16777
|
+
}
|
16489
16778
|
}
|
16490
16779
|
}
|
16491
16780
|
logger.log(`Video: Initial PTS/DTS adjusted: ${toMsFromMpegTsClock(firstPTS, true)}/${toMsFromMpegTsClock(firstDTS, true)}, delta: ${toMsFromMpegTsClock(delta, true)} ms`);
|
@@ -16633,7 +16922,7 @@ class MP4Remuxer {
|
|
16633
16922
|
}
|
16634
16923
|
}
|
16635
16924
|
}
|
16636
|
-
// next AVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
|
16925
|
+
// next AVC/HEVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
|
16637
16926
|
mp4SampleDuration = stretchedLastFrame || !mp4SampleDuration ? averageSampleDuration : mp4SampleDuration;
|
16638
16927
|
this.nextAvcDts = nextAvcDts = lastDTS + mp4SampleDuration;
|
16639
16928
|
this.videoSampleDuration = mp4SampleDuration;
|
@@ -16766,7 +17055,7 @@ class MP4Remuxer {
|
|
16766
17055
|
logger.warn(`[mp4-remuxer]: Injecting ${missing} audio frame @ ${(nextPts / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`);
|
16767
17056
|
for (let j = 0; j < missing; j++) {
|
16768
17057
|
const newStamp = Math.max(nextPts, 0);
|
16769
|
-
let fillFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
|
17058
|
+
let fillFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
|
16770
17059
|
if (!fillFrame) {
|
16771
17060
|
logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.');
|
16772
17061
|
fillFrame = sample.unit.subarray();
|
@@ -16894,7 +17183,7 @@ class MP4Remuxer {
|
|
16894
17183
|
// samples count of this segment's duration
|
16895
17184
|
const nbSamples = Math.ceil((endDTS - startDTS) / frameDuration);
|
16896
17185
|
// silent frame
|
16897
|
-
const silentFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
|
17186
|
+
const silentFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
|
16898
17187
|
logger.warn('[mp4-remuxer]: remux empty Audio');
|
16899
17188
|
// Can't remux if we can't generate a silent frame...
|
16900
17189
|
if (!silentFrame) {
|
@@ -17285,13 +17574,15 @@ class Transmuxer {
|
|
17285
17574
|
initSegmentData
|
17286
17575
|
} = transmuxConfig;
|
17287
17576
|
const keyData = getEncryptionType(uintData, decryptdata);
|
17288
|
-
if (keyData && keyData.method
|
17577
|
+
if (keyData && isFullSegmentEncryption(keyData.method)) {
|
17289
17578
|
const decrypter = this.getDecrypter();
|
17579
|
+
const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
|
17580
|
+
|
17290
17581
|
// Software decryption is synchronous; webCrypto is not
|
17291
17582
|
if (decrypter.isSync()) {
|
17292
17583
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
17293
17584
|
// data is handled in the flush() call
|
17294
|
-
let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer);
|
17585
|
+
let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode);
|
17295
17586
|
// For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
|
17296
17587
|
const loadingParts = chunkMeta.part > -1;
|
17297
17588
|
if (loadingParts) {
|
@@ -17303,7 +17594,7 @@ class Transmuxer {
|
|
17303
17594
|
}
|
17304
17595
|
uintData = new Uint8Array(decryptedData);
|
17305
17596
|
} else {
|
17306
|
-
this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer).then(decryptedData => {
|
17597
|
+
this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode).then(decryptedData => {
|
17307
17598
|
// Calling push here is important; if flush() is called while this is still resolving, this ensures that
|
17308
17599
|
// the decrypted data has been transmuxed
|
17309
17600
|
const result = this.push(decryptedData, null, chunkMeta);
|
@@ -17957,14 +18248,7 @@ class TransmuxerInterface {
|
|
17957
18248
|
this.observer = new EventEmitter();
|
17958
18249
|
this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
|
17959
18250
|
this.observer.on(Events.ERROR, forwardMessage);
|
17960
|
-
const
|
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
|
-
};
|
18251
|
+
const m2tsTypeSupported = getM2TSSupportedAudioTypes(config.preferManagedMediaSource);
|
17968
18252
|
|
17969
18253
|
// navigator.vendor is not always available in Web Worker
|
17970
18254
|
// refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
|
@@ -18228,8 +18512,9 @@ const STALL_MINIMUM_DURATION_MS = 250;
|
|
18228
18512
|
const MAX_START_GAP_JUMP = 2.0;
|
18229
18513
|
const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
|
18230
18514
|
const SKIP_BUFFER_RANGE_START = 0.05;
|
18231
|
-
class GapController {
|
18515
|
+
class GapController extends Logger {
|
18232
18516
|
constructor(config, media, fragmentTracker, hls) {
|
18517
|
+
super('gap-controller', hls.logger);
|
18233
18518
|
this.config = void 0;
|
18234
18519
|
this.media = null;
|
18235
18520
|
this.fragmentTracker = void 0;
|
@@ -18239,6 +18524,7 @@ class GapController {
|
|
18239
18524
|
this.stalled = null;
|
18240
18525
|
this.moved = false;
|
18241
18526
|
this.seeking = false;
|
18527
|
+
this.ended = 0;
|
18242
18528
|
this.config = config;
|
18243
18529
|
this.media = media;
|
18244
18530
|
this.fragmentTracker = fragmentTracker;
|
@@ -18256,7 +18542,7 @@ class GapController {
|
|
18256
18542
|
*
|
18257
18543
|
* @param lastCurrentTime - Previously read playhead position
|
18258
18544
|
*/
|
18259
|
-
poll(lastCurrentTime, activeFrag) {
|
18545
|
+
poll(lastCurrentTime, activeFrag, levelDetails, state) {
|
18260
18546
|
const {
|
18261
18547
|
config,
|
18262
18548
|
media,
|
@@ -18275,6 +18561,7 @@ class GapController {
|
|
18275
18561
|
|
18276
18562
|
// The playhead is moving, no-op
|
18277
18563
|
if (currentTime !== lastCurrentTime) {
|
18564
|
+
this.ended = 0;
|
18278
18565
|
this.moved = true;
|
18279
18566
|
if (!seeking) {
|
18280
18567
|
this.nudgeRetry = 0;
|
@@ -18283,7 +18570,7 @@ class GapController {
|
|
18283
18570
|
// The playhead is now moving, but was previously stalled
|
18284
18571
|
if (this.stallReported) {
|
18285
18572
|
const _stalledDuration = self.performance.now() - stalled;
|
18286
|
-
|
18573
|
+
this.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
|
18287
18574
|
this.stallReported = false;
|
18288
18575
|
}
|
18289
18576
|
this.stalled = null;
|
@@ -18319,7 +18606,6 @@ class GapController {
|
|
18319
18606
|
// Skip start gaps if we haven't played, but the last poll detected the start of a stall
|
18320
18607
|
// The addition poll gives the browser a chance to jump the gap for us
|
18321
18608
|
if (!this.moved && this.stalled !== null) {
|
18322
|
-
var _level$details;
|
18323
18609
|
// There is no playable buffer (seeked, waiting for buffer)
|
18324
18610
|
const isBuffered = bufferInfo.len > 0;
|
18325
18611
|
if (!isBuffered && !nextStart) {
|
@@ -18331,9 +18617,8 @@ class GapController {
|
|
18331
18617
|
// When joining a live stream with audio tracks, account for live playlist window sliding by allowing
|
18332
18618
|
// a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
|
18333
18619
|
// that begins over 1 target duration after the video start position.
|
18334
|
-
const
|
18335
|
-
const
|
18336
|
-
const maxStartGapJump = isLive ? level.details.targetduration * 2 : MAX_START_GAP_JUMP;
|
18620
|
+
const isLive = !!(levelDetails != null && levelDetails.live);
|
18621
|
+
const maxStartGapJump = isLive ? levelDetails.targetduration * 2 : MAX_START_GAP_JUMP;
|
18337
18622
|
const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
|
18338
18623
|
if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
|
18339
18624
|
if (!media.paused) {
|
@@ -18351,6 +18636,17 @@ class GapController {
|
|
18351
18636
|
}
|
18352
18637
|
const stalledDuration = tnow - stalled;
|
18353
18638
|
if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) {
|
18639
|
+
// Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
|
18640
|
+
if (state === State.ENDED && !(levelDetails && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? void 0 : levelDetails.edge) || 0)) < 1) {
|
18641
|
+
if (stalledDuration < 1000 || this.ended) {
|
18642
|
+
return;
|
18643
|
+
}
|
18644
|
+
this.ended = currentTime;
|
18645
|
+
this.hls.trigger(Events.MEDIA_ENDED, {
|
18646
|
+
stalled: true
|
18647
|
+
});
|
18648
|
+
return;
|
18649
|
+
}
|
18354
18650
|
// Report stalling after trying to fix
|
18355
18651
|
this._reportStall(bufferInfo);
|
18356
18652
|
if (!this.media) {
|
@@ -18394,7 +18690,7 @@ class GapController {
|
|
18394
18690
|
// needs to cross some sort of threshold covering all source-buffers content
|
18395
18691
|
// to start playing properly.
|
18396
18692
|
if ((bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
|
18397
|
-
|
18693
|
+
this.warn('Trying to nudge playhead over buffer-hole');
|
18398
18694
|
// Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
|
18399
18695
|
// We only try to jump the hole if it's under the configured size
|
18400
18696
|
// Reset stalled so to rearm watchdog timer
|
@@ -18418,7 +18714,7 @@ class GapController {
|
|
18418
18714
|
// Report stalled error once
|
18419
18715
|
this.stallReported = true;
|
18420
18716
|
const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${JSON.stringify(bufferInfo)})`);
|
18421
|
-
|
18717
|
+
this.warn(error.message);
|
18422
18718
|
hls.trigger(Events.ERROR, {
|
18423
18719
|
type: ErrorTypes.MEDIA_ERROR,
|
18424
18720
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
@@ -18486,7 +18782,7 @@ class GapController {
|
|
18486
18782
|
}
|
18487
18783
|
}
|
18488
18784
|
const targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
|
18489
|
-
|
18785
|
+
this.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
|
18490
18786
|
this.moved = true;
|
18491
18787
|
this.stalled = null;
|
18492
18788
|
media.currentTime = targetTime;
|
@@ -18527,7 +18823,7 @@ class GapController {
|
|
18527
18823
|
const targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset;
|
18528
18824
|
// playback stalled in buffered area ... let's nudge currentTime to try to overcome this
|
18529
18825
|
const error = new Error(`Nudging 'currentTime' from ${currentTime} to ${targetTime}`);
|
18530
|
-
|
18826
|
+
this.warn(error.message);
|
18531
18827
|
media.currentTime = targetTime;
|
18532
18828
|
hls.trigger(Events.ERROR, {
|
18533
18829
|
type: ErrorTypes.MEDIA_ERROR,
|
@@ -18537,7 +18833,7 @@ class GapController {
|
|
18537
18833
|
});
|
18538
18834
|
} else {
|
18539
18835
|
const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`);
|
18540
|
-
|
18836
|
+
this.error(error.message);
|
18541
18837
|
hls.trigger(Events.ERROR, {
|
18542
18838
|
type: ErrorTypes.MEDIA_ERROR,
|
18543
18839
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
@@ -18552,7 +18848,7 @@ const TICK_INTERVAL = 100; // how often to tick in ms
|
|
18552
18848
|
|
18553
18849
|
class StreamController extends BaseStreamController {
|
18554
18850
|
constructor(hls, fragmentTracker, keyLoader) {
|
18555
|
-
super(hls, fragmentTracker, keyLoader, '
|
18851
|
+
super(hls, fragmentTracker, keyLoader, 'stream-controller', PlaylistLevelType.MAIN);
|
18556
18852
|
this.audioCodecSwap = false;
|
18557
18853
|
this.gapController = null;
|
18558
18854
|
this.level = -1;
|
@@ -18560,27 +18856,43 @@ class StreamController extends BaseStreamController {
|
|
18560
18856
|
this.altAudio = false;
|
18561
18857
|
this.audioOnly = false;
|
18562
18858
|
this.fragPlaying = null;
|
18563
|
-
this.onvplaying = null;
|
18564
|
-
this.onvseeked = null;
|
18565
18859
|
this.fragLastKbps = 0;
|
18566
18860
|
this.couldBacktrack = false;
|
18567
18861
|
this.backtrackFragment = null;
|
18568
18862
|
this.audioCodecSwitch = false;
|
18569
18863
|
this.videoBuffer = null;
|
18570
|
-
this.
|
18864
|
+
this.onMediaPlaying = () => {
|
18865
|
+
// tick to speed up FRAG_CHANGED triggering
|
18866
|
+
this.tick();
|
18867
|
+
};
|
18868
|
+
this.onMediaSeeked = () => {
|
18869
|
+
const media = this.media;
|
18870
|
+
const currentTime = media ? media.currentTime : null;
|
18871
|
+
if (isFiniteNumber(currentTime)) {
|
18872
|
+
this.log(`Media seeked to ${currentTime.toFixed(3)}`);
|
18873
|
+
}
|
18874
|
+
|
18875
|
+
// If seeked was issued before buffer was appended do not tick immediately
|
18876
|
+
const bufferInfo = this.getMainFwdBufferInfo();
|
18877
|
+
if (bufferInfo === null || bufferInfo.len === 0) {
|
18878
|
+
this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
|
18879
|
+
return;
|
18880
|
+
}
|
18881
|
+
|
18882
|
+
// tick to speed up FRAG_CHANGED triggering
|
18883
|
+
this.tick();
|
18884
|
+
};
|
18885
|
+
this.registerListeners();
|
18571
18886
|
}
|
18572
|
-
|
18887
|
+
registerListeners() {
|
18888
|
+
super.registerListeners();
|
18573
18889
|
const {
|
18574
18890
|
hls
|
18575
18891
|
} = 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);
|
18579
18892
|
hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
18580
18893
|
hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
|
18581
18894
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
18582
18895
|
hls.on(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
|
18583
|
-
hls.on(Events.ERROR, this.onError, this);
|
18584
18896
|
hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
18585
18897
|
hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
18586
18898
|
hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -18588,17 +18900,14 @@ class StreamController extends BaseStreamController {
|
|
18588
18900
|
hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
|
18589
18901
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
18590
18902
|
}
|
18591
|
-
|
18903
|
+
unregisterListeners() {
|
18904
|
+
super.unregisterListeners();
|
18592
18905
|
const {
|
18593
18906
|
hls
|
18594
18907
|
} = 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);
|
18598
18908
|
hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
18599
18909
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
18600
18910
|
hls.off(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
|
18601
|
-
hls.off(Events.ERROR, this.onError, this);
|
18602
18911
|
hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
18603
18912
|
hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
18604
18913
|
hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -18607,7 +18916,9 @@ class StreamController extends BaseStreamController {
|
|
18607
18916
|
hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
18608
18917
|
}
|
18609
18918
|
onHandlerDestroying() {
|
18610
|
-
|
18919
|
+
// @ts-ignore
|
18920
|
+
this.onMediaPlaying = this.onMediaSeeked = null;
|
18921
|
+
this.unregisterListeners();
|
18611
18922
|
super.onHandlerDestroying();
|
18612
18923
|
}
|
18613
18924
|
startLoad(startPosition) {
|
@@ -18932,20 +19243,17 @@ class StreamController extends BaseStreamController {
|
|
18932
19243
|
onMediaAttached(event, data) {
|
18933
19244
|
super.onMediaAttached(event, data);
|
18934
19245
|
const media = data.media;
|
18935
|
-
|
18936
|
-
|
18937
|
-
media.addEventListener('playing', this.onvplaying);
|
18938
|
-
media.addEventListener('seeked', this.onvseeked);
|
19246
|
+
media.addEventListener('playing', this.onMediaPlaying);
|
19247
|
+
media.addEventListener('seeked', this.onMediaSeeked);
|
18939
19248
|
this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
|
18940
19249
|
}
|
18941
19250
|
onMediaDetaching() {
|
18942
19251
|
const {
|
18943
19252
|
media
|
18944
19253
|
} = this;
|
18945
|
-
if (media
|
18946
|
-
media.removeEventListener('playing', this.
|
18947
|
-
media.removeEventListener('seeked', this.
|
18948
|
-
this.onvplaying = this.onvseeked = null;
|
19254
|
+
if (media) {
|
19255
|
+
media.removeEventListener('playing', this.onMediaPlaying);
|
19256
|
+
media.removeEventListener('seeked', this.onMediaSeeked);
|
18949
19257
|
this.videoBuffer = null;
|
18950
19258
|
}
|
18951
19259
|
this.fragPlaying = null;
|
@@ -18955,27 +19263,6 @@ class StreamController extends BaseStreamController {
|
|
18955
19263
|
}
|
18956
19264
|
super.onMediaDetaching();
|
18957
19265
|
}
|
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
|
-
}
|
18979
19266
|
onManifestLoading() {
|
18980
19267
|
// reset buffer on manifest loading
|
18981
19268
|
this.log('Trigger BUFFER_RESET');
|
@@ -19267,8 +19554,10 @@ class StreamController extends BaseStreamController {
|
|
19267
19554
|
}
|
19268
19555
|
if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) {
|
19269
19556
|
// Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
|
19270
|
-
const
|
19271
|
-
|
19557
|
+
const state = this.state;
|
19558
|
+
const activeFrag = state !== State.IDLE ? this.fragCurrent : null;
|
19559
|
+
const levelDetails = this.getLevelDetails();
|
19560
|
+
gapController.poll(this.lastCurrentTime, activeFrag, levelDetails, state);
|
19272
19561
|
}
|
19273
19562
|
this.lastCurrentTime = media.currentTime;
|
19274
19563
|
}
|
@@ -19706,7 +19995,7 @@ class Hls {
|
|
19706
19995
|
* Get the video-dev/hls.js package version.
|
19707
19996
|
*/
|
19708
19997
|
static get version() {
|
19709
|
-
return "1.5.
|
19998
|
+
return "1.5.7-0.canary.10014";
|
19710
19999
|
}
|
19711
20000
|
|
19712
20001
|
/**
|
@@ -19769,9 +20058,12 @@ class Hls {
|
|
19769
20058
|
* The configuration object provided on player instantiation.
|
19770
20059
|
*/
|
19771
20060
|
this.userConfig = void 0;
|
20061
|
+
/**
|
20062
|
+
* The logger functions used by this player instance, configured on player instantiation.
|
20063
|
+
*/
|
20064
|
+
this.logger = void 0;
|
19772
20065
|
this.coreComponents = void 0;
|
19773
20066
|
this.networkControllers = void 0;
|
19774
|
-
this.started = false;
|
19775
20067
|
this._emitter = new EventEmitter();
|
19776
20068
|
this._autoLevelCapping = -1;
|
19777
20069
|
this._maxHdcpLevel = null;
|
@@ -19788,11 +20080,11 @@ class Hls {
|
|
19788
20080
|
this._media = null;
|
19789
20081
|
this.url = null;
|
19790
20082
|
this.triggeringException = void 0;
|
19791
|
-
enableLogs(userConfig.debug || false, 'Hls instance');
|
19792
|
-
const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig);
|
20083
|
+
const logger = this.logger = enableLogs(userConfig.debug || false, 'Hls instance');
|
20084
|
+
const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig, logger);
|
19793
20085
|
this.userConfig = userConfig;
|
19794
20086
|
if (config.progressive) {
|
19795
|
-
enableStreamingMode(config);
|
20087
|
+
enableStreamingMode(config, logger);
|
19796
20088
|
}
|
19797
20089
|
|
19798
20090
|
// core controllers and network loaders
|
@@ -19891,7 +20183,7 @@ class Hls {
|
|
19891
20183
|
try {
|
19892
20184
|
return this.emit(event, event, eventObject);
|
19893
20185
|
} catch (error) {
|
19894
|
-
logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
|
20186
|
+
this.logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
|
19895
20187
|
// Prevent recursion in error event handlers that throw #5497
|
19896
20188
|
if (!this.triggeringException) {
|
19897
20189
|
this.triggeringException = true;
|
@@ -19917,7 +20209,7 @@ class Hls {
|
|
19917
20209
|
* Dispose of the instance
|
19918
20210
|
*/
|
19919
20211
|
destroy() {
|
19920
|
-
logger.log('destroy');
|
20212
|
+
this.logger.log('destroy');
|
19921
20213
|
this.trigger(Events.DESTROYING, undefined);
|
19922
20214
|
this.detachMedia();
|
19923
20215
|
this.removeAllListeners();
|
@@ -19938,7 +20230,7 @@ class Hls {
|
|
19938
20230
|
* Attaches Hls.js to a media element
|
19939
20231
|
*/
|
19940
20232
|
attachMedia(media) {
|
19941
|
-
logger.log('attachMedia');
|
20233
|
+
this.logger.log('attachMedia');
|
19942
20234
|
this._media = media;
|
19943
20235
|
this.trigger(Events.MEDIA_ATTACHING, {
|
19944
20236
|
media: media
|
@@ -19949,7 +20241,7 @@ class Hls {
|
|
19949
20241
|
* Detach Hls.js from the media
|
19950
20242
|
*/
|
19951
20243
|
detachMedia() {
|
19952
|
-
logger.log('detachMedia');
|
20244
|
+
this.logger.log('detachMedia');
|
19953
20245
|
this.trigger(Events.MEDIA_DETACHING, undefined);
|
19954
20246
|
this._media = null;
|
19955
20247
|
}
|
@@ -19966,7 +20258,7 @@ class Hls {
|
|
19966
20258
|
});
|
19967
20259
|
this._autoLevelCapping = -1;
|
19968
20260
|
this._maxHdcpLevel = null;
|
19969
|
-
logger.log(`loadSource:${loadingSource}`);
|
20261
|
+
this.logger.log(`loadSource:${loadingSource}`);
|
19970
20262
|
if (media && loadedSource && (loadedSource !== loadingSource || this.bufferController.hasSourceTypes())) {
|
19971
20263
|
this.detachMedia();
|
19972
20264
|
this.attachMedia(media);
|
@@ -19985,8 +20277,7 @@ class Hls {
|
|
19985
20277
|
* Defaults to -1 (None: starts from earliest point)
|
19986
20278
|
*/
|
19987
20279
|
startLoad(startPosition = -1) {
|
19988
|
-
logger.log(`startLoad(${startPosition})`);
|
19989
|
-
this.started = true;
|
20280
|
+
this.logger.log(`startLoad(${startPosition})`);
|
19990
20281
|
this.networkControllers.forEach(controller => {
|
19991
20282
|
controller.startLoad(startPosition);
|
19992
20283
|
});
|
@@ -19996,34 +20287,31 @@ class Hls {
|
|
19996
20287
|
* Stop loading of any stream data.
|
19997
20288
|
*/
|
19998
20289
|
stopLoad() {
|
19999
|
-
logger.log('stopLoad');
|
20000
|
-
this.started = false;
|
20290
|
+
this.logger.log('stopLoad');
|
20001
20291
|
this.networkControllers.forEach(controller => {
|
20002
20292
|
controller.stopLoad();
|
20003
20293
|
});
|
20004
20294
|
}
|
20005
20295
|
|
20006
20296
|
/**
|
20007
|
-
* Resumes stream controller segment loading
|
20297
|
+
* Resumes stream controller segment loading after `pauseBuffering` has been called.
|
20008
20298
|
*/
|
20009
20299
|
resumeBuffering() {
|
20010
|
-
|
20011
|
-
|
20012
|
-
|
20013
|
-
|
20014
|
-
|
20015
|
-
});
|
20016
|
-
}
|
20300
|
+
this.networkControllers.forEach(controller => {
|
20301
|
+
if (controller.resumeBuffering) {
|
20302
|
+
controller.resumeBuffering();
|
20303
|
+
}
|
20304
|
+
});
|
20017
20305
|
}
|
20018
20306
|
|
20019
20307
|
/**
|
20020
|
-
*
|
20308
|
+
* Prevents stream controller from loading new segments until `resumeBuffering` is called.
|
20021
20309
|
* This allows for media buffering to be paused without interupting playlist loading.
|
20022
20310
|
*/
|
20023
20311
|
pauseBuffering() {
|
20024
20312
|
this.networkControllers.forEach(controller => {
|
20025
|
-
if (
|
20026
|
-
controller.
|
20313
|
+
if (controller.pauseBuffering) {
|
20314
|
+
controller.pauseBuffering();
|
20027
20315
|
}
|
20028
20316
|
});
|
20029
20317
|
}
|
@@ -20032,7 +20320,7 @@ class Hls {
|
|
20032
20320
|
* Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
|
20033
20321
|
*/
|
20034
20322
|
swapAudioCodec() {
|
20035
|
-
logger.log('swapAudioCodec');
|
20323
|
+
this.logger.log('swapAudioCodec');
|
20036
20324
|
this.streamController.swapAudioCodec();
|
20037
20325
|
}
|
20038
20326
|
|
@@ -20043,7 +20331,7 @@ class Hls {
|
|
20043
20331
|
* Automatic recovery of media-errors by this process is configurable.
|
20044
20332
|
*/
|
20045
20333
|
recoverMediaError() {
|
20046
|
-
logger.log('recoverMediaError');
|
20334
|
+
this.logger.log('recoverMediaError');
|
20047
20335
|
const media = this._media;
|
20048
20336
|
this.detachMedia();
|
20049
20337
|
if (media) {
|
@@ -20073,7 +20361,7 @@ class Hls {
|
|
20073
20361
|
* 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.
|
20074
20362
|
*/
|
20075
20363
|
set currentLevel(newLevel) {
|
20076
|
-
logger.log(`set currentLevel:${newLevel}`);
|
20364
|
+
this.logger.log(`set currentLevel:${newLevel}`);
|
20077
20365
|
this.levelController.manualLevel = newLevel;
|
20078
20366
|
this.streamController.immediateLevelSwitch();
|
20079
20367
|
}
|
@@ -20092,7 +20380,7 @@ class Hls {
|
|
20092
20380
|
* @param newLevel - Pass -1 for automatic level selection
|
20093
20381
|
*/
|
20094
20382
|
set nextLevel(newLevel) {
|
20095
|
-
logger.log(`set nextLevel:${newLevel}`);
|
20383
|
+
this.logger.log(`set nextLevel:${newLevel}`);
|
20096
20384
|
this.levelController.manualLevel = newLevel;
|
20097
20385
|
this.streamController.nextLevelSwitch();
|
20098
20386
|
}
|
@@ -20111,7 +20399,7 @@ class Hls {
|
|
20111
20399
|
* @param newLevel - Pass -1 for automatic level selection
|
20112
20400
|
*/
|
20113
20401
|
set loadLevel(newLevel) {
|
20114
|
-
logger.log(`set loadLevel:${newLevel}`);
|
20402
|
+
this.logger.log(`set loadLevel:${newLevel}`);
|
20115
20403
|
this.levelController.manualLevel = newLevel;
|
20116
20404
|
}
|
20117
20405
|
|
@@ -20142,7 +20430,7 @@ class Hls {
|
|
20142
20430
|
* Sets "first-level", see getter.
|
20143
20431
|
*/
|
20144
20432
|
set firstLevel(newLevel) {
|
20145
|
-
logger.log(`set firstLevel:${newLevel}`);
|
20433
|
+
this.logger.log(`set firstLevel:${newLevel}`);
|
20146
20434
|
this.levelController.firstLevel = newLevel;
|
20147
20435
|
}
|
20148
20436
|
|
@@ -20167,7 +20455,7 @@ class Hls {
|
|
20167
20455
|
* (determined from download of first segment)
|
20168
20456
|
*/
|
20169
20457
|
set startLevel(newLevel) {
|
20170
|
-
logger.log(`set startLevel:${newLevel}`);
|
20458
|
+
this.logger.log(`set startLevel:${newLevel}`);
|
20171
20459
|
// if not in automatic start level detection, ensure startLevel is greater than minAutoLevel
|
20172
20460
|
if (newLevel !== -1) {
|
20173
20461
|
newLevel = Math.max(newLevel, this.minAutoLevel);
|
@@ -20242,7 +20530,7 @@ class Hls {
|
|
20242
20530
|
*/
|
20243
20531
|
set autoLevelCapping(newLevel) {
|
20244
20532
|
if (this._autoLevelCapping !== newLevel) {
|
20245
|
-
logger.log(`set autoLevelCapping:${newLevel}`);
|
20533
|
+
this.logger.log(`set autoLevelCapping:${newLevel}`);
|
20246
20534
|
this._autoLevelCapping = newLevel;
|
20247
20535
|
this.levelController.checkMaxAutoUpdated();
|
20248
20536
|
}
|
@@ -20521,5 +20809,5 @@ var KeySystemFormats = empty.KeySystemFormats;
|
|
20521
20809
|
var KeySystems = empty.KeySystems;
|
20522
20810
|
var SubtitleStreamController = empty.SubtitleStreamController;
|
20523
20811
|
var TimelineController = empty.TimelineController;
|
20524
|
-
export { AbrController, AttrList,
|
20812
|
+
export { AbrController, AttrList, HevcVideoParser as AudioStreamController, HevcVideoParser as AudioTrackController, BasePlaylistController, BaseSegment, BaseStreamController, BufferController, HevcVideoParser as CMCDController, CapLevelController, ChunkMetadata, ContentSteeringController, DateRange, HevcVideoParser as EMEController, ErrorActionFlags, ErrorController, ErrorDetails, ErrorTypes, Events, FPSController, Fragment, Hls, HlsSkip, HlsUrlParameters, KeySystemFormats, KeySystems, Level, LevelDetails, LevelKey, LoadStats, MetadataSchema, NetworkErrorAction, Part, PlaylistLevelType, SubtitleStreamController, HevcVideoParser as SubtitleTrackController, TimelineController, Hls as default, getMediaSource, isMSESupported, isSupported };
|
20525
20813
|
//# sourceMappingURL=hls.light.mjs.map
|