hls.js 1.5.6 → 1.5.7-0.canary.10015
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 +2077 -1166
- package/dist/hls.js.d.ts +65 -50
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +1149 -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 +985 -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 +1758 -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/src/utils/mp4-tools.ts +3 -1
package/dist/hls.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.10015"}`);
|
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) {
|
@@ -1036,6 +1058,26 @@ function strToUtf8array(str) {
|
|
1036
1058
|
return Uint8Array.from(unescape(encodeURIComponent(str)), c => c.charCodeAt(0));
|
1037
1059
|
}
|
1038
1060
|
|
1061
|
+
var DecrypterAesMode = {
|
1062
|
+
cbc: 0,
|
1063
|
+
ctr: 1
|
1064
|
+
};
|
1065
|
+
|
1066
|
+
function isFullSegmentEncryption(method) {
|
1067
|
+
return method === 'AES-128' || method === 'AES-256' || method === 'AES-256-CTR';
|
1068
|
+
}
|
1069
|
+
function getAesModeFromFullSegmentMethod(method) {
|
1070
|
+
switch (method) {
|
1071
|
+
case 'AES-128':
|
1072
|
+
case 'AES-256':
|
1073
|
+
return DecrypterAesMode.cbc;
|
1074
|
+
case 'AES-256-CTR':
|
1075
|
+
return DecrypterAesMode.ctr;
|
1076
|
+
default:
|
1077
|
+
throw new Error(`invalid full segment method ${method}`);
|
1078
|
+
}
|
1079
|
+
}
|
1080
|
+
|
1039
1081
|
/** returns `undefined` is `self` is missing, e.g. in node */
|
1040
1082
|
const optionalSelf = typeof self !== 'undefined' ? self : undefined;
|
1041
1083
|
|
@@ -1899,7 +1941,9 @@ function parseStsd(stsd) {
|
|
1899
1941
|
}
|
1900
1942
|
function skipBERInteger(bytes, i) {
|
1901
1943
|
const limit = i + 5;
|
1902
|
-
while (bytes[i++] & 0x80 && i < limit) {
|
1944
|
+
while (bytes[i++] & 0x80 && i < limit) {
|
1945
|
+
/* do nothing */
|
1946
|
+
}
|
1903
1947
|
return i;
|
1904
1948
|
}
|
1905
1949
|
function toHex(x) {
|
@@ -2688,12 +2732,12 @@ class LevelKey {
|
|
2688
2732
|
this.keyFormatVersions = formatversions;
|
2689
2733
|
this.iv = iv;
|
2690
2734
|
this.encrypted = method ? method !== 'NONE' : false;
|
2691
|
-
this.isCommonEncryption = this.encrypted && method
|
2735
|
+
this.isCommonEncryption = this.encrypted && !isFullSegmentEncryption(method);
|
2692
2736
|
}
|
2693
2737
|
isSupported() {
|
2694
2738
|
// If it's Segment encryption or No encryption, just select that key system
|
2695
2739
|
if (this.method) {
|
2696
|
-
if (this.method
|
2740
|
+
if (isFullSegmentEncryption(this.method) || this.method === 'NONE') {
|
2697
2741
|
return true;
|
2698
2742
|
}
|
2699
2743
|
if (this.keyFormat === 'identity') {
|
@@ -2715,14 +2759,13 @@ class LevelKey {
|
|
2715
2759
|
if (!this.encrypted || !this.uri) {
|
2716
2760
|
return null;
|
2717
2761
|
}
|
2718
|
-
if (this.method
|
2762
|
+
if (isFullSegmentEncryption(this.method) && this.uri && !this.iv) {
|
2719
2763
|
if (typeof sn !== 'number') {
|
2720
2764
|
// We are fetching decryption data for a initialization segment
|
2721
|
-
// If the segment was encrypted with AES-128
|
2765
|
+
// If the segment was encrypted with AES-128/256
|
2722
2766
|
// It must have an IV defined. We cannot substitute the Segment Number in.
|
2723
|
-
|
2724
|
-
|
2725
|
-
}
|
2767
|
+
logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
|
2768
|
+
|
2726
2769
|
// Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
|
2727
2770
|
sn = 0;
|
2728
2771
|
}
|
@@ -3001,23 +3044,28 @@ function getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource =
|
|
3001
3044
|
if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) {
|
3002
3045
|
return CODEC_COMPATIBLE_NAMES[lowerCaseCodec];
|
3003
3046
|
}
|
3004
|
-
|
3005
|
-
// Idealy fLaC and Opus would be first (spec-compliant) but
|
3006
|
-
// some browsers will report that fLaC is supported then fail.
|
3007
|
-
// see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
|
3008
3047
|
const codecsToCheck = {
|
3048
|
+
// Idealy fLaC and Opus would be first (spec-compliant) but
|
3049
|
+
// some browsers will report that fLaC is supported then fail.
|
3050
|
+
// see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
|
3009
3051
|
flac: ['flac', 'fLaC', 'FLAC'],
|
3010
|
-
opus: ['opus', 'Opus']
|
3052
|
+
opus: ['opus', 'Opus'],
|
3053
|
+
// Replace audio codec info if browser does not support mp4a.40.34,
|
3054
|
+
// and demuxer can fallback to 'audio/mpeg' or 'audio/mp4;codecs="mp3"'
|
3055
|
+
'mp4a.40.34': ['mp3']
|
3011
3056
|
}[lowerCaseCodec];
|
3012
3057
|
for (let i = 0; i < codecsToCheck.length; i++) {
|
3058
|
+
var _getMediaSource;
|
3013
3059
|
if (isCodecMediaSourceSupported(codecsToCheck[i], 'audio', preferManagedMediaSource)) {
|
3014
3060
|
CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
|
3015
3061
|
return codecsToCheck[i];
|
3062
|
+
} else if (codecsToCheck[i] === 'mp3' && (_getMediaSource = getMediaSource(preferManagedMediaSource)) != null && _getMediaSource.isTypeSupported('audio/mpeg')) {
|
3063
|
+
return '';
|
3016
3064
|
}
|
3017
3065
|
}
|
3018
3066
|
return lowerCaseCodec;
|
3019
3067
|
}
|
3020
|
-
const AUDIO_CODEC_REGEXP = /flac|opus/i;
|
3068
|
+
const AUDIO_CODEC_REGEXP = /flac|opus|mp4a\.40\.34/i;
|
3021
3069
|
function getCodecCompatibleName(codec, preferManagedMediaSource = true) {
|
3022
3070
|
return codec.replace(AUDIO_CODEC_REGEXP, m => getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource));
|
3023
3071
|
}
|
@@ -3040,6 +3088,16 @@ function convertAVC1ToAVCOTI(codec) {
|
|
3040
3088
|
}
|
3041
3089
|
return codec;
|
3042
3090
|
}
|
3091
|
+
function getM2TSSupportedAudioTypes(preferManagedMediaSource) {
|
3092
|
+
const MediaSource = getMediaSource(preferManagedMediaSource) || {
|
3093
|
+
isTypeSupported: () => false
|
3094
|
+
};
|
3095
|
+
return {
|
3096
|
+
mpeg: MediaSource.isTypeSupported('audio/mpeg'),
|
3097
|
+
mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
|
3098
|
+
ac3: MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
|
3099
|
+
};
|
3100
|
+
}
|
3043
3101
|
|
3044
3102
|
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;
|
3045
3103
|
const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g;
|
@@ -3889,10 +3947,10 @@ class PlaylistLoader {
|
|
3889
3947
|
const loaderContext = loader.context;
|
3890
3948
|
if (loaderContext && loaderContext.url === context.url && loaderContext.level === context.level) {
|
3891
3949
|
// same URL can't overlap
|
3892
|
-
logger.trace('[playlist-loader]: playlist request ongoing');
|
3950
|
+
this.hls.logger.trace('[playlist-loader]: playlist request ongoing');
|
3893
3951
|
return;
|
3894
3952
|
}
|
3895
|
-
logger.log(`[playlist-loader]: aborting previous loader for type: ${context.type}`);
|
3953
|
+
this.hls.logger.log(`[playlist-loader]: aborting previous loader for type: ${context.type}`);
|
3896
3954
|
loader.abort();
|
3897
3955
|
}
|
3898
3956
|
|
@@ -4002,7 +4060,7 @@ class PlaylistLoader {
|
|
4002
4060
|
// alt audio rendition in which quality levels (main)
|
4003
4061
|
// contains both audio+video. but with mixed audio track not signaled
|
4004
4062
|
if (!embeddedAudioFound && levels[0].audioCodec && !levels[0].attrs.AUDIO) {
|
4005
|
-
logger.log('[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one');
|
4063
|
+
this.hls.logger.log('[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one');
|
4006
4064
|
audioTracks.unshift({
|
4007
4065
|
type: 'main',
|
4008
4066
|
name: 'main',
|
@@ -4101,7 +4159,7 @@ class PlaylistLoader {
|
|
4101
4159
|
message += ` id: ${context.id} group-id: "${context.groupId}"`;
|
4102
4160
|
}
|
4103
4161
|
const error = new Error(message);
|
4104
|
-
logger.warn(`[playlist-loader]: ${message}`);
|
4162
|
+
this.hls.logger.warn(`[playlist-loader]: ${message}`);
|
4105
4163
|
let details = ErrorDetails.UNKNOWN;
|
4106
4164
|
let fatal = false;
|
4107
4165
|
const loader = this.getInternalLoader(context);
|
@@ -4706,7 +4764,47 @@ class LatencyController {
|
|
4706
4764
|
this.currentTime = 0;
|
4707
4765
|
this.stallCount = 0;
|
4708
4766
|
this._latency = null;
|
4709
|
-
this.
|
4767
|
+
this.onTimeupdate = () => {
|
4768
|
+
const {
|
4769
|
+
media,
|
4770
|
+
levelDetails
|
4771
|
+
} = this;
|
4772
|
+
if (!media || !levelDetails) {
|
4773
|
+
return;
|
4774
|
+
}
|
4775
|
+
this.currentTime = media.currentTime;
|
4776
|
+
const latency = this.computeLatency();
|
4777
|
+
if (latency === null) {
|
4778
|
+
return;
|
4779
|
+
}
|
4780
|
+
this._latency = latency;
|
4781
|
+
|
4782
|
+
// Adapt playbackRate to meet target latency in low-latency mode
|
4783
|
+
const {
|
4784
|
+
lowLatencyMode,
|
4785
|
+
maxLiveSyncPlaybackRate
|
4786
|
+
} = this.config;
|
4787
|
+
if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
|
4788
|
+
return;
|
4789
|
+
}
|
4790
|
+
const targetLatency = this.targetLatency;
|
4791
|
+
if (targetLatency === null) {
|
4792
|
+
return;
|
4793
|
+
}
|
4794
|
+
const distanceFromTarget = latency - targetLatency;
|
4795
|
+
// Only adjust playbackRate when within one target duration of targetLatency
|
4796
|
+
// and more than one second from under-buffering.
|
4797
|
+
// Playback further than one target duration from target can be considered DVR playback.
|
4798
|
+
const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
|
4799
|
+
const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
|
4800
|
+
if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
|
4801
|
+
const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
|
4802
|
+
const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
|
4803
|
+
media.playbackRate = Math.min(max, Math.max(1, rate));
|
4804
|
+
} else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
|
4805
|
+
media.playbackRate = 1;
|
4806
|
+
}
|
4807
|
+
};
|
4710
4808
|
this.hls = hls;
|
4711
4809
|
this.config = hls.config;
|
4712
4810
|
this.registerListeners();
|
@@ -4798,7 +4896,7 @@ class LatencyController {
|
|
4798
4896
|
this.onMediaDetaching();
|
4799
4897
|
this.levelDetails = null;
|
4800
4898
|
// @ts-ignore
|
4801
|
-
this.hls =
|
4899
|
+
this.hls = null;
|
4802
4900
|
}
|
4803
4901
|
registerListeners() {
|
4804
4902
|
this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
@@ -4816,11 +4914,11 @@ class LatencyController {
|
|
4816
4914
|
}
|
4817
4915
|
onMediaAttached(event, data) {
|
4818
4916
|
this.media = data.media;
|
4819
|
-
this.media.addEventListener('timeupdate', this.
|
4917
|
+
this.media.addEventListener('timeupdate', this.onTimeupdate);
|
4820
4918
|
}
|
4821
4919
|
onMediaDetaching() {
|
4822
4920
|
if (this.media) {
|
4823
|
-
this.media.removeEventListener('timeupdate', this.
|
4921
|
+
this.media.removeEventListener('timeupdate', this.onTimeupdate);
|
4824
4922
|
this.media = null;
|
4825
4923
|
}
|
4826
4924
|
}
|
@@ -4834,10 +4932,10 @@ class LatencyController {
|
|
4834
4932
|
}) {
|
4835
4933
|
this.levelDetails = details;
|
4836
4934
|
if (details.advanced) {
|
4837
|
-
this.
|
4935
|
+
this.onTimeupdate();
|
4838
4936
|
}
|
4839
4937
|
if (!details.live && this.media) {
|
4840
|
-
this.media.removeEventListener('timeupdate', this.
|
4938
|
+
this.media.removeEventListener('timeupdate', this.onTimeupdate);
|
4841
4939
|
}
|
4842
4940
|
}
|
4843
4941
|
onError(event, data) {
|
@@ -4847,48 +4945,7 @@ class LatencyController {
|
|
4847
4945
|
}
|
4848
4946
|
this.stallCount++;
|
4849
4947
|
if ((_this$levelDetails = this.levelDetails) != null && _this$levelDetails.live) {
|
4850
|
-
logger.warn('[
|
4851
|
-
}
|
4852
|
-
}
|
4853
|
-
timeupdate() {
|
4854
|
-
const {
|
4855
|
-
media,
|
4856
|
-
levelDetails
|
4857
|
-
} = this;
|
4858
|
-
if (!media || !levelDetails) {
|
4859
|
-
return;
|
4860
|
-
}
|
4861
|
-
this.currentTime = media.currentTime;
|
4862
|
-
const latency = this.computeLatency();
|
4863
|
-
if (latency === null) {
|
4864
|
-
return;
|
4865
|
-
}
|
4866
|
-
this._latency = latency;
|
4867
|
-
|
4868
|
-
// Adapt playbackRate to meet target latency in low-latency mode
|
4869
|
-
const {
|
4870
|
-
lowLatencyMode,
|
4871
|
-
maxLiveSyncPlaybackRate
|
4872
|
-
} = this.config;
|
4873
|
-
if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
|
4874
|
-
return;
|
4875
|
-
}
|
4876
|
-
const targetLatency = this.targetLatency;
|
4877
|
-
if (targetLatency === null) {
|
4878
|
-
return;
|
4879
|
-
}
|
4880
|
-
const distanceFromTarget = latency - targetLatency;
|
4881
|
-
// Only adjust playbackRate when within one target duration of targetLatency
|
4882
|
-
// and more than one second from under-buffering.
|
4883
|
-
// Playback further than one target duration from target can be considered DVR playback.
|
4884
|
-
const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
|
4885
|
-
const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
|
4886
|
-
if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
|
4887
|
-
const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
|
4888
|
-
const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
|
4889
|
-
media.playbackRate = Math.min(max, Math.max(1, rate));
|
4890
|
-
} else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
|
4891
|
-
media.playbackRate = 1;
|
4948
|
+
this.hls.logger.warn('[latency-controller]: Stall detected, adjusting target latency');
|
4892
4949
|
}
|
4893
4950
|
}
|
4894
4951
|
estimateLiveEdge() {
|
@@ -5660,18 +5717,13 @@ var ErrorActionFlags = {
|
|
5660
5717
|
MoveAllAlternatesMatchingHDCP: 2,
|
5661
5718
|
SwitchToSDR: 4
|
5662
5719
|
}; // Reserved for future use
|
5663
|
-
class ErrorController {
|
5720
|
+
class ErrorController extends Logger {
|
5664
5721
|
constructor(hls) {
|
5722
|
+
super('error-controller', hls.logger);
|
5665
5723
|
this.hls = void 0;
|
5666
5724
|
this.playlistError = 0;
|
5667
5725
|
this.penalizedRenditions = {};
|
5668
|
-
this.log = void 0;
|
5669
|
-
this.warn = void 0;
|
5670
|
-
this.error = void 0;
|
5671
5726
|
this.hls = hls;
|
5672
|
-
this.log = logger.log.bind(logger, `[info]:`);
|
5673
|
-
this.warn = logger.warn.bind(logger, `[warning]:`);
|
5674
|
-
this.error = logger.error.bind(logger, `[error]:`);
|
5675
5727
|
this.registerListeners();
|
5676
5728
|
}
|
5677
5729
|
registerListeners() {
|
@@ -6023,16 +6075,13 @@ class ErrorController {
|
|
6023
6075
|
}
|
6024
6076
|
}
|
6025
6077
|
|
6026
|
-
class BasePlaylistController {
|
6078
|
+
class BasePlaylistController extends Logger {
|
6027
6079
|
constructor(hls, logPrefix) {
|
6080
|
+
super(logPrefix, hls.logger);
|
6028
6081
|
this.hls = void 0;
|
6029
6082
|
this.timer = -1;
|
6030
6083
|
this.requestScheduled = -1;
|
6031
6084
|
this.canLoad = false;
|
6032
|
-
this.log = void 0;
|
6033
|
-
this.warn = void 0;
|
6034
|
-
this.log = logger.log.bind(logger, `${logPrefix}:`);
|
6035
|
-
this.warn = logger.warn.bind(logger, `${logPrefix}:`);
|
6036
6085
|
this.hls = hls;
|
6037
6086
|
}
|
6038
6087
|
destroy() {
|
@@ -6065,7 +6114,7 @@ class BasePlaylistController {
|
|
6065
6114
|
try {
|
6066
6115
|
uri = new self.URL(attr.URI, previous.url).href;
|
6067
6116
|
} catch (error) {
|
6068
|
-
|
6117
|
+
this.warn(`Could not construct new URL for Rendition Report: ${error}`);
|
6069
6118
|
uri = attr.URI || '';
|
6070
6119
|
}
|
6071
6120
|
// Use exact match. Otherwise, the last partial match, if any, will be used
|
@@ -6152,7 +6201,12 @@ class BasePlaylistController {
|
|
6152
6201
|
const cdnAge = lastAdvanced + details.ageHeader;
|
6153
6202
|
let currentGoal = Math.min(cdnAge - details.partTarget, details.targetduration * 1.5);
|
6154
6203
|
if (currentGoal > 0) {
|
6155
|
-
if (
|
6204
|
+
if (cdnAge > details.targetduration * 3) {
|
6205
|
+
// Omit segment and part directives when the last response was more than 3 target durations ago,
|
6206
|
+
this.log(`Playlist last advanced ${lastAdvanced.toFixed(2)}s ago. Omitting segment and part directives.`);
|
6207
|
+
msn = undefined;
|
6208
|
+
part = undefined;
|
6209
|
+
} else if (previousDetails != null && previousDetails.tuneInGoal && cdnAge - details.partTarget > previousDetails.tuneInGoal) {
|
6156
6210
|
// If we attempted to get the next or latest playlist update, but currentGoal increased,
|
6157
6211
|
// then we either can't catchup, or the "age" header cannot be trusted.
|
6158
6212
|
this.warn(`CDN Tune-in goal increased from: ${previousDetails.tuneInGoal} to: ${currentGoal} with playlist age: ${details.age}`);
|
@@ -6824,8 +6878,9 @@ function searchDownAndUpList(arr, searchIndex, predicate) {
|
|
6824
6878
|
return -1;
|
6825
6879
|
}
|
6826
6880
|
|
6827
|
-
class AbrController {
|
6881
|
+
class AbrController extends Logger {
|
6828
6882
|
constructor(_hls) {
|
6883
|
+
super('abr', _hls.logger);
|
6829
6884
|
this.hls = void 0;
|
6830
6885
|
this.lastLevelLoadSec = 0;
|
6831
6886
|
this.lastLoadedFragLevel = -1;
|
@@ -6939,7 +6994,7 @@ class AbrController {
|
|
6939
6994
|
this.resetEstimator(nextLoadLevelBitrate);
|
6940
6995
|
}
|
6941
6996
|
this.clearTimer();
|
6942
|
-
|
6997
|
+
this.warn(`Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
|
6943
6998
|
Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s
|
6944
6999
|
Estimated load time for current fragment: ${fragLoadedDelay.toFixed(3)} s
|
6945
7000
|
Estimated load time for down switch fragment: ${fragLevelNextLoadedDelay.toFixed(3)} s
|
@@ -6959,7 +7014,7 @@ class AbrController {
|
|
6959
7014
|
}
|
6960
7015
|
resetEstimator(abrEwmaDefaultEstimate) {
|
6961
7016
|
if (abrEwmaDefaultEstimate) {
|
6962
|
-
|
7017
|
+
this.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
|
6963
7018
|
this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate;
|
6964
7019
|
}
|
6965
7020
|
this.firstSelection = -1;
|
@@ -7191,7 +7246,7 @@ class AbrController {
|
|
7191
7246
|
}
|
7192
7247
|
const firstLevel = this.hls.firstLevel;
|
7193
7248
|
const clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel);
|
7194
|
-
|
7249
|
+
this.warn(`Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
|
7195
7250
|
return clamped;
|
7196
7251
|
}
|
7197
7252
|
get forcedAutoLevel() {
|
@@ -7269,13 +7324,13 @@ class AbrController {
|
|
7269
7324
|
// cap maxLoadingDelay and ensure it is not bigger 'than bitrate test' frag duration
|
7270
7325
|
const maxLoadingDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxLoadingDelay) : config.maxLoadingDelay;
|
7271
7326
|
maxStarvationDelay = maxLoadingDelay - bitrateTestDelay;
|
7272
|
-
|
7327
|
+
this.info(`bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
|
7273
7328
|
// don't use conservative factor on bitrate test
|
7274
7329
|
bwFactor = bwUpFactor = 1;
|
7275
7330
|
}
|
7276
7331
|
}
|
7277
7332
|
const bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor);
|
7278
|
-
|
7333
|
+
this.info(`${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
|
7279
7334
|
if (bestLevel > -1) {
|
7280
7335
|
return bestLevel;
|
7281
7336
|
}
|
@@ -7349,7 +7404,7 @@ class AbrController {
|
|
7349
7404
|
currentVideoRange = preferHDR ? videoRanges[videoRanges.length - 1] : videoRanges[0];
|
7350
7405
|
currentFrameRate = minFramerate;
|
7351
7406
|
currentBw = Math.max(currentBw, minBitrate);
|
7352
|
-
|
7407
|
+
this.log(`picked start tier ${JSON.stringify(startTier)}`);
|
7353
7408
|
} else {
|
7354
7409
|
currentCodecSet = level == null ? void 0 : level.codecSet;
|
7355
7410
|
currentVideoRange = level == null ? void 0 : level.videoRange;
|
@@ -7376,11 +7431,11 @@ class AbrController {
|
|
7376
7431
|
const levels = this.hls.levels;
|
7377
7432
|
const index = levels.indexOf(levelInfo);
|
7378
7433
|
if (decodingInfo.error) {
|
7379
|
-
|
7434
|
+
this.warn(`MediaCapabilities decodingInfo error: "${decodingInfo.error}" for level ${index} ${JSON.stringify(decodingInfo)}`);
|
7380
7435
|
} else if (!decodingInfo.supported) {
|
7381
|
-
|
7436
|
+
this.warn(`Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify(decodingInfo)}`);
|
7382
7437
|
if (index > -1 && levels.length > 1) {
|
7383
|
-
|
7438
|
+
this.log(`Removing unsupported level ${index}`);
|
7384
7439
|
this.hls.removeLevel(index);
|
7385
7440
|
}
|
7386
7441
|
}
|
@@ -7427,9 +7482,9 @@ class AbrController {
|
|
7427
7482
|
const forcedAutoLevel = this.forcedAutoLevel;
|
7428
7483
|
if (i !== loadLevel && (forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)) {
|
7429
7484
|
if (levelsSkipped.length) {
|
7430
|
-
|
7485
|
+
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}`);
|
7431
7486
|
}
|
7432
|
-
|
7487
|
+
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}`);
|
7433
7488
|
}
|
7434
7489
|
if (firstSelection) {
|
7435
7490
|
this.firstSelection = i;
|
@@ -7483,8 +7538,9 @@ class AbrController {
|
|
7483
7538
|
* we are limiting the task execution per call stack to exactly one, but scheduling/post-poning further
|
7484
7539
|
* task processing on the next main loop iteration (also known as "next tick" in the Node/JS runtime lingo).
|
7485
7540
|
*/
|
7486
|
-
class TaskLoop {
|
7487
|
-
constructor() {
|
7541
|
+
class TaskLoop extends Logger {
|
7542
|
+
constructor(label, logger) {
|
7543
|
+
super(label, logger);
|
7488
7544
|
this._boundTick = void 0;
|
7489
7545
|
this._tickTimer = null;
|
7490
7546
|
this._tickInterval = null;
|
@@ -8575,8 +8631,8 @@ function createLoaderContext(frag, part = null) {
|
|
8575
8631
|
var _frag$decryptdata;
|
8576
8632
|
let byteRangeStart = start;
|
8577
8633
|
let byteRangeEnd = end;
|
8578
|
-
if (frag.sn === 'initSegment' && ((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method)
|
8579
|
-
// MAP segment encrypted with method 'AES-128', when served with HTTP Range,
|
8634
|
+
if (frag.sn === 'initSegment' && isMethodFullSegmentAesCbc((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method)) {
|
8635
|
+
// MAP segment encrypted with method 'AES-128' or 'AES-256' (cbc), when served with HTTP Range,
|
8580
8636
|
// has the unencrypted size specified in the range.
|
8581
8637
|
// Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
|
8582
8638
|
const fragmentLen = end - start;
|
@@ -8609,6 +8665,9 @@ function createGapLoadError(frag, part) {
|
|
8609
8665
|
(part ? part : frag).stats.aborted = true;
|
8610
8666
|
return new LoadError(errorData);
|
8611
8667
|
}
|
8668
|
+
function isMethodFullSegmentAesCbc(method) {
|
8669
|
+
return method === 'AES-128' || method === 'AES-256';
|
8670
|
+
}
|
8612
8671
|
class LoadError extends Error {
|
8613
8672
|
constructor(data) {
|
8614
8673
|
super(data.error.message);
|
@@ -8618,33 +8677,61 @@ class LoadError extends Error {
|
|
8618
8677
|
}
|
8619
8678
|
|
8620
8679
|
class AESCrypto {
|
8621
|
-
constructor(subtle, iv) {
|
8680
|
+
constructor(subtle, iv, aesMode) {
|
8622
8681
|
this.subtle = void 0;
|
8623
8682
|
this.aesIV = void 0;
|
8683
|
+
this.aesMode = void 0;
|
8624
8684
|
this.subtle = subtle;
|
8625
8685
|
this.aesIV = iv;
|
8686
|
+
this.aesMode = aesMode;
|
8626
8687
|
}
|
8627
8688
|
decrypt(data, key) {
|
8628
|
-
|
8629
|
-
|
8630
|
-
|
8631
|
-
|
8689
|
+
switch (this.aesMode) {
|
8690
|
+
case DecrypterAesMode.cbc:
|
8691
|
+
return this.subtle.decrypt({
|
8692
|
+
name: 'AES-CBC',
|
8693
|
+
iv: this.aesIV
|
8694
|
+
}, key, data);
|
8695
|
+
case DecrypterAesMode.ctr:
|
8696
|
+
return this.subtle.decrypt({
|
8697
|
+
name: 'AES-CTR',
|
8698
|
+
counter: this.aesIV,
|
8699
|
+
length: 64
|
8700
|
+
},
|
8701
|
+
//64 : NIST SP800-38A standard suggests that the counter should occupy half of the counter block
|
8702
|
+
key, data);
|
8703
|
+
default:
|
8704
|
+
throw new Error(`[AESCrypto] invalid aes mode ${this.aesMode}`);
|
8705
|
+
}
|
8632
8706
|
}
|
8633
8707
|
}
|
8634
8708
|
|
8635
8709
|
class FastAESKey {
|
8636
|
-
constructor(subtle, key) {
|
8710
|
+
constructor(subtle, key, aesMode) {
|
8637
8711
|
this.subtle = void 0;
|
8638
8712
|
this.key = void 0;
|
8713
|
+
this.aesMode = void 0;
|
8639
8714
|
this.subtle = subtle;
|
8640
8715
|
this.key = key;
|
8716
|
+
this.aesMode = aesMode;
|
8641
8717
|
}
|
8642
8718
|
expandKey() {
|
8719
|
+
const subtleAlgoName = getSubtleAlgoName(this.aesMode);
|
8643
8720
|
return this.subtle.importKey('raw', this.key, {
|
8644
|
-
name:
|
8721
|
+
name: subtleAlgoName
|
8645
8722
|
}, false, ['encrypt', 'decrypt']);
|
8646
8723
|
}
|
8647
8724
|
}
|
8725
|
+
function getSubtleAlgoName(aesMode) {
|
8726
|
+
switch (aesMode) {
|
8727
|
+
case DecrypterAesMode.cbc:
|
8728
|
+
return 'AES-CBC';
|
8729
|
+
case DecrypterAesMode.ctr:
|
8730
|
+
return 'AES-CTR';
|
8731
|
+
default:
|
8732
|
+
throw new Error(`[FastAESKey] invalid aes mode ${aesMode}`);
|
8733
|
+
}
|
8734
|
+
}
|
8648
8735
|
|
8649
8736
|
// PKCS7
|
8650
8737
|
function removePadding(array) {
|
@@ -8894,7 +8981,8 @@ class Decrypter {
|
|
8894
8981
|
this.currentIV = null;
|
8895
8982
|
this.currentResult = null;
|
8896
8983
|
this.useSoftware = void 0;
|
8897
|
-
this.
|
8984
|
+
this.enableSoftwareAES = void 0;
|
8985
|
+
this.enableSoftwareAES = config.enableSoftwareAES;
|
8898
8986
|
this.removePKCS7Padding = removePKCS7Padding;
|
8899
8987
|
// built in decryptor expects PKCS7 padding
|
8900
8988
|
if (removePKCS7Padding) {
|
@@ -8907,9 +8995,7 @@ class Decrypter {
|
|
8907
8995
|
/* no-op */
|
8908
8996
|
}
|
8909
8997
|
}
|
8910
|
-
|
8911
|
-
this.useSoftware = true;
|
8912
|
-
}
|
8998
|
+
this.useSoftware = this.subtle === null;
|
8913
8999
|
}
|
8914
9000
|
destroy() {
|
8915
9001
|
this.subtle = null;
|
@@ -8947,10 +9033,10 @@ class Decrypter {
|
|
8947
9033
|
this.softwareDecrypter = null;
|
8948
9034
|
}
|
8949
9035
|
}
|
8950
|
-
decrypt(data, key, iv) {
|
9036
|
+
decrypt(data, key, iv, aesMode) {
|
8951
9037
|
if (this.useSoftware) {
|
8952
9038
|
return new Promise((resolve, reject) => {
|
8953
|
-
this.softwareDecrypt(new Uint8Array(data), key, iv);
|
9039
|
+
this.softwareDecrypt(new Uint8Array(data), key, iv, aesMode);
|
8954
9040
|
const decryptResult = this.flush();
|
8955
9041
|
if (decryptResult) {
|
8956
9042
|
resolve(decryptResult.buffer);
|
@@ -8959,17 +9045,21 @@ class Decrypter {
|
|
8959
9045
|
}
|
8960
9046
|
});
|
8961
9047
|
}
|
8962
|
-
return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
|
9048
|
+
return this.webCryptoDecrypt(new Uint8Array(data), key, iv, aesMode);
|
8963
9049
|
}
|
8964
9050
|
|
8965
9051
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
8966
9052
|
// data is handled in the flush() call
|
8967
|
-
softwareDecrypt(data, key, iv) {
|
9053
|
+
softwareDecrypt(data, key, iv, aesMode) {
|
8968
9054
|
const {
|
8969
9055
|
currentIV,
|
8970
9056
|
currentResult,
|
8971
9057
|
remainderData
|
8972
9058
|
} = this;
|
9059
|
+
if (aesMode !== DecrypterAesMode.cbc || key.byteLength !== 16) {
|
9060
|
+
logger.warn('SoftwareDecrypt: can only handle AES-128-CBC');
|
9061
|
+
return null;
|
9062
|
+
}
|
8973
9063
|
this.logOnce('JS AES decrypt');
|
8974
9064
|
// The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
|
8975
9065
|
// This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached
|
@@ -9002,11 +9092,11 @@ class Decrypter {
|
|
9002
9092
|
}
|
9003
9093
|
return result;
|
9004
9094
|
}
|
9005
|
-
webCryptoDecrypt(data, key, iv) {
|
9095
|
+
webCryptoDecrypt(data, key, iv, aesMode) {
|
9006
9096
|
const subtle = this.subtle;
|
9007
9097
|
if (this.key !== key || !this.fastAesKey) {
|
9008
9098
|
this.key = key;
|
9009
|
-
this.fastAesKey = new FastAESKey(subtle, key);
|
9099
|
+
this.fastAesKey = new FastAESKey(subtle, key, aesMode);
|
9010
9100
|
}
|
9011
9101
|
return this.fastAesKey.expandKey().then(aesKey => {
|
9012
9102
|
// decrypt using web crypto
|
@@ -9014,22 +9104,25 @@ class Decrypter {
|
|
9014
9104
|
return Promise.reject(new Error('web crypto not initialized'));
|
9015
9105
|
}
|
9016
9106
|
this.logOnce('WebCrypto AES decrypt');
|
9017
|
-
const crypto = new AESCrypto(subtle, new Uint8Array(iv));
|
9107
|
+
const crypto = new AESCrypto(subtle, new Uint8Array(iv), aesMode);
|
9018
9108
|
return crypto.decrypt(data.buffer, aesKey);
|
9019
9109
|
}).catch(err => {
|
9020
9110
|
logger.warn(`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`);
|
9021
|
-
return this.onWebCryptoError(data, key, iv);
|
9111
|
+
return this.onWebCryptoError(data, key, iv, aesMode);
|
9022
9112
|
});
|
9023
9113
|
}
|
9024
|
-
onWebCryptoError(data, key, iv) {
|
9025
|
-
|
9026
|
-
|
9027
|
-
|
9028
|
-
|
9029
|
-
|
9030
|
-
|
9114
|
+
onWebCryptoError(data, key, iv, aesMode) {
|
9115
|
+
const enableSoftwareAES = this.enableSoftwareAES;
|
9116
|
+
if (enableSoftwareAES) {
|
9117
|
+
this.useSoftware = true;
|
9118
|
+
this.logEnabled = true;
|
9119
|
+
this.softwareDecrypt(data, key, iv, aesMode);
|
9120
|
+
const decryptResult = this.flush();
|
9121
|
+
if (decryptResult) {
|
9122
|
+
return decryptResult.buffer;
|
9123
|
+
}
|
9031
9124
|
}
|
9032
|
-
throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data');
|
9125
|
+
throw new Error('WebCrypto' + (enableSoftwareAES ? ' and softwareDecrypt' : '') + ': failed to decrypt data');
|
9033
9126
|
}
|
9034
9127
|
getValidChunk(data) {
|
9035
9128
|
let currentChunk = data;
|
@@ -9080,7 +9173,7 @@ const State = {
|
|
9080
9173
|
};
|
9081
9174
|
class BaseStreamController extends TaskLoop {
|
9082
9175
|
constructor(hls, fragmentTracker, keyLoader, logPrefix, playlistType) {
|
9083
|
-
super();
|
9176
|
+
super(logPrefix, hls.logger);
|
9084
9177
|
this.hls = void 0;
|
9085
9178
|
this.fragPrevious = null;
|
9086
9179
|
this.fragCurrent = null;
|
@@ -9105,22 +9198,98 @@ class BaseStreamController extends TaskLoop {
|
|
9105
9198
|
this.startFragRequested = false;
|
9106
9199
|
this.decrypter = void 0;
|
9107
9200
|
this.initPTS = [];
|
9108
|
-
this.
|
9109
|
-
this.
|
9110
|
-
this.
|
9111
|
-
|
9112
|
-
|
9201
|
+
this.buffering = true;
|
9202
|
+
this.loadingParts = false;
|
9203
|
+
this.onMediaSeeking = () => {
|
9204
|
+
const {
|
9205
|
+
config,
|
9206
|
+
fragCurrent,
|
9207
|
+
media,
|
9208
|
+
mediaBuffer,
|
9209
|
+
state
|
9210
|
+
} = this;
|
9211
|
+
const currentTime = media ? media.currentTime : 0;
|
9212
|
+
const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
|
9213
|
+
this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
|
9214
|
+
if (this.state === State.ENDED) {
|
9215
|
+
this.resetLoadingState();
|
9216
|
+
} else if (fragCurrent) {
|
9217
|
+
// Seeking while frag load is in progress
|
9218
|
+
const tolerance = config.maxFragLookUpTolerance;
|
9219
|
+
const fragStartOffset = fragCurrent.start - tolerance;
|
9220
|
+
const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
|
9221
|
+
// if seeking out of buffered range or into new one
|
9222
|
+
if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
|
9223
|
+
const pastFragment = currentTime > fragEndOffset;
|
9224
|
+
// if the seek position is outside the current fragment range
|
9225
|
+
if (currentTime < fragStartOffset || pastFragment) {
|
9226
|
+
if (pastFragment && fragCurrent.loader) {
|
9227
|
+
this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
|
9228
|
+
fragCurrent.abortRequests();
|
9229
|
+
this.resetLoadingState();
|
9230
|
+
}
|
9231
|
+
this.fragPrevious = null;
|
9232
|
+
}
|
9233
|
+
}
|
9234
|
+
}
|
9235
|
+
if (media) {
|
9236
|
+
// Remove gap fragments
|
9237
|
+
this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
|
9238
|
+
this.lastCurrentTime = currentTime;
|
9239
|
+
if (!this.loadingParts) {
|
9240
|
+
const bufferEnd = Math.max(bufferInfo.end, currentTime);
|
9241
|
+
const shouldLoadParts = this.shouldLoadParts(this.getLevelDetails(), bufferEnd);
|
9242
|
+
if (shouldLoadParts) {
|
9243
|
+
this.log(`LL-Part loading ON after seeking to ${currentTime.toFixed(2)} with buffer @${bufferEnd.toFixed(2)}`);
|
9244
|
+
this.loadingParts = shouldLoadParts;
|
9245
|
+
}
|
9246
|
+
}
|
9247
|
+
}
|
9248
|
+
|
9249
|
+
// in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
|
9250
|
+
if (!this.loadedmetadata && !bufferInfo.len) {
|
9251
|
+
this.nextLoadPosition = this.startPosition = currentTime;
|
9252
|
+
}
|
9253
|
+
|
9254
|
+
// Async tick to speed up processing
|
9255
|
+
this.tickImmediate();
|
9256
|
+
};
|
9257
|
+
this.onMediaEnded = () => {
|
9258
|
+
// reset startPosition and lastCurrentTime to restart playback @ stream beginning
|
9259
|
+
this.startPosition = this.lastCurrentTime = 0;
|
9260
|
+
if (this.playlistType === PlaylistLevelType.MAIN) {
|
9261
|
+
this.hls.trigger(Events.MEDIA_ENDED, {
|
9262
|
+
stalled: false
|
9263
|
+
});
|
9264
|
+
}
|
9265
|
+
};
|
9113
9266
|
this.playlistType = playlistType;
|
9114
|
-
this.logPrefix = logPrefix;
|
9115
|
-
this.log = logger.log.bind(logger, `${logPrefix}:`);
|
9116
|
-
this.warn = logger.warn.bind(logger, `${logPrefix}:`);
|
9117
9267
|
this.hls = hls;
|
9118
9268
|
this.fragmentLoader = new FragmentLoader(hls.config);
|
9119
9269
|
this.keyLoader = keyLoader;
|
9120
9270
|
this.fragmentTracker = fragmentTracker;
|
9121
9271
|
this.config = hls.config;
|
9122
9272
|
this.decrypter = new Decrypter(hls.config);
|
9273
|
+
}
|
9274
|
+
registerListeners() {
|
9275
|
+
const {
|
9276
|
+
hls
|
9277
|
+
} = this;
|
9278
|
+
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
9279
|
+
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
9280
|
+
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
9123
9281
|
hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
9282
|
+
hls.on(Events.ERROR, this.onError, this);
|
9283
|
+
}
|
9284
|
+
unregisterListeners() {
|
9285
|
+
const {
|
9286
|
+
hls
|
9287
|
+
} = this;
|
9288
|
+
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
9289
|
+
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
9290
|
+
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
9291
|
+
hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
9292
|
+
hls.off(Events.ERROR, this.onError, this);
|
9124
9293
|
}
|
9125
9294
|
doTick() {
|
9126
9295
|
this.onTickEnd();
|
@@ -9144,6 +9313,12 @@ class BaseStreamController extends TaskLoop {
|
|
9144
9313
|
this.clearNextTick();
|
9145
9314
|
this.state = State.STOPPED;
|
9146
9315
|
}
|
9316
|
+
pauseBuffering() {
|
9317
|
+
this.buffering = false;
|
9318
|
+
}
|
9319
|
+
resumeBuffering() {
|
9320
|
+
this.buffering = true;
|
9321
|
+
}
|
9147
9322
|
_streamEnded(bufferInfo, levelDetails) {
|
9148
9323
|
// If playlist is live, there is another buffered range after the current range, nothing buffered, media is detached,
|
9149
9324
|
// of nothing loading/loaded return false
|
@@ -9174,10 +9349,8 @@ class BaseStreamController extends TaskLoop {
|
|
9174
9349
|
}
|
9175
9350
|
onMediaAttached(event, data) {
|
9176
9351
|
const media = this.media = this.mediaBuffer = data.media;
|
9177
|
-
|
9178
|
-
|
9179
|
-
media.addEventListener('seeking', this.onvseeking);
|
9180
|
-
media.addEventListener('ended', this.onvended);
|
9352
|
+
media.addEventListener('seeking', this.onMediaSeeking);
|
9353
|
+
media.addEventListener('ended', this.onMediaEnded);
|
9181
9354
|
const config = this.config;
|
9182
9355
|
if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
|
9183
9356
|
this.startLoad(config.startPosition);
|
@@ -9191,10 +9364,9 @@ class BaseStreamController extends TaskLoop {
|
|
9191
9364
|
}
|
9192
9365
|
|
9193
9366
|
// remove video listeners
|
9194
|
-
if (media
|
9195
|
-
media.removeEventListener('seeking', this.
|
9196
|
-
media.removeEventListener('ended', this.
|
9197
|
-
this.onvseeking = this.onvended = null;
|
9367
|
+
if (media) {
|
9368
|
+
media.removeEventListener('seeking', this.onMediaSeeking);
|
9369
|
+
media.removeEventListener('ended', this.onMediaEnded);
|
9198
9370
|
}
|
9199
9371
|
if (this.keyLoader) {
|
9200
9372
|
this.keyLoader.detach();
|
@@ -9204,56 +9376,8 @@ class BaseStreamController extends TaskLoop {
|
|
9204
9376
|
this.fragmentTracker.removeAllFragments();
|
9205
9377
|
this.stopLoad();
|
9206
9378
|
}
|
9207
|
-
|
9208
|
-
|
9209
|
-
config,
|
9210
|
-
fragCurrent,
|
9211
|
-
media,
|
9212
|
-
mediaBuffer,
|
9213
|
-
state
|
9214
|
-
} = this;
|
9215
|
-
const currentTime = media ? media.currentTime : 0;
|
9216
|
-
const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
|
9217
|
-
this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
|
9218
|
-
if (this.state === State.ENDED) {
|
9219
|
-
this.resetLoadingState();
|
9220
|
-
} else if (fragCurrent) {
|
9221
|
-
// Seeking while frag load is in progress
|
9222
|
-
const tolerance = config.maxFragLookUpTolerance;
|
9223
|
-
const fragStartOffset = fragCurrent.start - tolerance;
|
9224
|
-
const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
|
9225
|
-
// if seeking out of buffered range or into new one
|
9226
|
-
if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
|
9227
|
-
const pastFragment = currentTime > fragEndOffset;
|
9228
|
-
// if the seek position is outside the current fragment range
|
9229
|
-
if (currentTime < fragStartOffset || pastFragment) {
|
9230
|
-
if (pastFragment && fragCurrent.loader) {
|
9231
|
-
this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
|
9232
|
-
fragCurrent.abortRequests();
|
9233
|
-
this.resetLoadingState();
|
9234
|
-
}
|
9235
|
-
this.fragPrevious = null;
|
9236
|
-
}
|
9237
|
-
}
|
9238
|
-
}
|
9239
|
-
if (media) {
|
9240
|
-
// Remove gap fragments
|
9241
|
-
this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
|
9242
|
-
this.lastCurrentTime = currentTime;
|
9243
|
-
}
|
9244
|
-
|
9245
|
-
// in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
|
9246
|
-
if (!this.loadedmetadata && !bufferInfo.len) {
|
9247
|
-
this.nextLoadPosition = this.startPosition = currentTime;
|
9248
|
-
}
|
9249
|
-
|
9250
|
-
// Async tick to speed up processing
|
9251
|
-
this.tickImmediate();
|
9252
|
-
}
|
9253
|
-
onMediaEnded() {
|
9254
|
-
// reset startPosition and lastCurrentTime to restart playback @ stream beginning
|
9255
|
-
this.startPosition = this.lastCurrentTime = 0;
|
9256
|
-
}
|
9379
|
+
onManifestLoading() {}
|
9380
|
+
onError(event, data) {}
|
9257
9381
|
onManifestLoaded(event, data) {
|
9258
9382
|
this.startTimeOffset = data.startTimeOffset;
|
9259
9383
|
this.initPTS = [];
|
@@ -9263,7 +9387,7 @@ class BaseStreamController extends TaskLoop {
|
|
9263
9387
|
this.stopLoad();
|
9264
9388
|
super.onHandlerDestroying();
|
9265
9389
|
// @ts-ignore
|
9266
|
-
this.hls = null;
|
9390
|
+
this.hls = this.onMediaSeeking = this.onMediaEnded = null;
|
9267
9391
|
}
|
9268
9392
|
onHandlerDestroyed() {
|
9269
9393
|
this.state = State.STOPPED;
|
@@ -9394,10 +9518,10 @@ class BaseStreamController extends TaskLoop {
|
|
9394
9518
|
const decryptData = frag.decryptdata;
|
9395
9519
|
|
9396
9520
|
// check to see if the payload needs to be decrypted
|
9397
|
-
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method
|
9521
|
+
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
|
9398
9522
|
const startTime = self.performance.now();
|
9399
9523
|
// decrypt init segment data
|
9400
|
-
return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
|
9524
|
+
return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch(err => {
|
9401
9525
|
hls.trigger(Events.ERROR, {
|
9402
9526
|
type: ErrorTypes.MEDIA_ERROR,
|
9403
9527
|
details: ErrorDetails.FRAG_DECRYPT_ERROR,
|
@@ -9509,7 +9633,7 @@ class BaseStreamController extends TaskLoop {
|
|
9509
9633
|
}
|
9510
9634
|
let keyLoadingPromise = null;
|
9511
9635
|
if (frag.encrypted && !((_frag$decryptdata = frag.decryptdata) != null && _frag$decryptdata.key)) {
|
9512
|
-
this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.
|
9636
|
+
this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'} ${frag.level}`);
|
9513
9637
|
this.state = State.KEY_LOADING;
|
9514
9638
|
this.fragCurrent = frag;
|
9515
9639
|
keyLoadingPromise = this.keyLoader.load(frag).then(keyLoadedData => {
|
@@ -9530,8 +9654,16 @@ class BaseStreamController extends TaskLoop {
|
|
9530
9654
|
} else if (!frag.encrypted && details.encryptedFragments.length) {
|
9531
9655
|
this.keyLoader.loadClear(frag, details.encryptedFragments);
|
9532
9656
|
}
|
9657
|
+
const fragPrevious = this.fragPrevious;
|
9658
|
+
if (frag.sn !== 'initSegment' && (!fragPrevious || frag.sn !== fragPrevious.sn)) {
|
9659
|
+
const shouldLoadParts = this.shouldLoadParts(level.details, frag.end);
|
9660
|
+
if (shouldLoadParts !== this.loadingParts) {
|
9661
|
+
this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} loading sn ${fragPrevious == null ? void 0 : fragPrevious.sn}->${frag.sn}`);
|
9662
|
+
this.loadingParts = shouldLoadParts;
|
9663
|
+
}
|
9664
|
+
}
|
9533
9665
|
targetBufferTime = Math.max(frag.start, targetBufferTime || 0);
|
9534
|
-
if (this.
|
9666
|
+
if (this.loadingParts && frag.sn !== 'initSegment') {
|
9535
9667
|
const partList = details.partList;
|
9536
9668
|
if (partList && progressCallback) {
|
9537
9669
|
if (targetBufferTime > frag.end && details.fragmentHint) {
|
@@ -9540,7 +9672,7 @@ class BaseStreamController extends TaskLoop {
|
|
9540
9672
|
const partIndex = this.getNextPart(partList, frag, targetBufferTime);
|
9541
9673
|
if (partIndex > -1) {
|
9542
9674
|
const part = partList[partIndex];
|
9543
|
-
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.
|
9675
|
+
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))}`);
|
9544
9676
|
this.nextLoadPosition = part.start + part.duration;
|
9545
9677
|
this.state = State.FRAG_LOADING;
|
9546
9678
|
let _result;
|
@@ -9569,7 +9701,14 @@ class BaseStreamController extends TaskLoop {
|
|
9569
9701
|
}
|
9570
9702
|
}
|
9571
9703
|
}
|
9572
|
-
|
9704
|
+
if (frag.sn !== 'initSegment' && this.loadingParts) {
|
9705
|
+
this.log(`LL-Part loading OFF after next part miss @${targetBufferTime.toFixed(2)}`);
|
9706
|
+
this.loadingParts = false;
|
9707
|
+
} else if (!frag.url) {
|
9708
|
+
// Selected fragment hint for part but not loading parts
|
9709
|
+
return Promise.resolve(null);
|
9710
|
+
}
|
9711
|
+
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))}`);
|
9573
9712
|
// Don't update nextLoadPosition for fragments which are not buffered
|
9574
9713
|
if (isFiniteNumber(frag.sn) && !this.bitrateTest) {
|
9575
9714
|
this.nextLoadPosition = frag.start + frag.duration;
|
@@ -9667,8 +9806,36 @@ class BaseStreamController extends TaskLoop {
|
|
9667
9806
|
if (part) {
|
9668
9807
|
part.stats.parsing.end = now;
|
9669
9808
|
}
|
9809
|
+
// See if part loading should be disabled/enabled based on buffer and playback position.
|
9810
|
+
if (frag.sn !== 'initSegment') {
|
9811
|
+
const levelDetails = this.getLevelDetails();
|
9812
|
+
const loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
|
9813
|
+
const shouldLoadParts = loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
|
9814
|
+
if (shouldLoadParts !== this.loadingParts) {
|
9815
|
+
this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} after parsing segment ending @${frag.end.toFixed(2)}`);
|
9816
|
+
this.loadingParts = shouldLoadParts;
|
9817
|
+
}
|
9818
|
+
}
|
9670
9819
|
this.updateLevelTiming(frag, part, level, chunkMeta.partial);
|
9671
9820
|
}
|
9821
|
+
shouldLoadParts(details, bufferEnd) {
|
9822
|
+
if (this.config.lowLatencyMode) {
|
9823
|
+
if (!details) {
|
9824
|
+
return this.loadingParts;
|
9825
|
+
}
|
9826
|
+
if (details != null && details.partList) {
|
9827
|
+
var _details$fragmentHint;
|
9828
|
+
// Buffer must be ahead of first part + duration of parts after last segment
|
9829
|
+
// and playback must be at or past segment adjacent to part list
|
9830
|
+
const firstPart = details.partList[0];
|
9831
|
+
const safePartStart = firstPart.end + (((_details$fragmentHint = details.fragmentHint) == null ? void 0 : _details$fragmentHint.duration) || 0);
|
9832
|
+
if (bufferEnd >= safePartStart && this.lastCurrentTime > firstPart.start - firstPart.fragment.duration) {
|
9833
|
+
return true;
|
9834
|
+
}
|
9835
|
+
}
|
9836
|
+
}
|
9837
|
+
return false;
|
9838
|
+
}
|
9672
9839
|
getCurrentContext(chunkMeta) {
|
9673
9840
|
const {
|
9674
9841
|
levels,
|
@@ -9817,7 +9984,8 @@ class BaseStreamController extends TaskLoop {
|
|
9817
9984
|
config
|
9818
9985
|
} = this;
|
9819
9986
|
const start = fragments[0].start;
|
9820
|
-
|
9987
|
+
const canLoadParts = config.lowLatencyMode && !!levelDetails.partList;
|
9988
|
+
let frag = null;
|
9821
9989
|
if (levelDetails.live) {
|
9822
9990
|
const initialLiveManifestSize = config.initialLiveManifestSize;
|
9823
9991
|
if (fragLen < initialLiveManifestSize) {
|
@@ -9829,6 +9997,10 @@ class BaseStreamController extends TaskLoop {
|
|
9829
9997
|
// Do not load using live logic if the starting frag is requested - we want to use getFragmentAtPosition() so that
|
9830
9998
|
// we get the fragment matching that start time
|
9831
9999
|
if (!levelDetails.PTSKnown && !this.startFragRequested && this.startPosition === -1 || pos < start) {
|
10000
|
+
if (canLoadParts && !this.loadingParts) {
|
10001
|
+
this.log(`LL-Part loading ON for initial live fragment`);
|
10002
|
+
this.loadingParts = true;
|
10003
|
+
}
|
9832
10004
|
frag = this.getInitialLiveFragment(levelDetails, fragments);
|
9833
10005
|
this.startPosition = this.nextLoadPosition = frag ? this.hls.liveSyncPosition || frag.start : pos;
|
9834
10006
|
}
|
@@ -9839,7 +10011,7 @@ class BaseStreamController extends TaskLoop {
|
|
9839
10011
|
|
9840
10012
|
// If we haven't run into any special cases already, just load the fragment most closely matching the requested position
|
9841
10013
|
if (!frag) {
|
9842
|
-
const end =
|
10014
|
+
const end = this.loadingParts ? levelDetails.partEnd : levelDetails.fragmentEnd;
|
9843
10015
|
frag = this.getFragmentAtPosition(pos, end, levelDetails);
|
9844
10016
|
}
|
9845
10017
|
return this.mapToInitFragWhenRequired(frag);
|
@@ -9961,7 +10133,7 @@ class BaseStreamController extends TaskLoop {
|
|
9961
10133
|
} = levelDetails;
|
9962
10134
|
const tolerance = config.maxFragLookUpTolerance;
|
9963
10135
|
const partList = levelDetails.partList;
|
9964
|
-
const loadingParts = !!(
|
10136
|
+
const loadingParts = !!(this.loadingParts && partList != null && partList.length && fragmentHint);
|
9965
10137
|
if (loadingParts && fragmentHint && !this.bitrateTest) {
|
9966
10138
|
// Include incomplete fragment with parts at end
|
9967
10139
|
fragments = fragments.concat(fragmentHint);
|
@@ -10154,7 +10326,7 @@ class BaseStreamController extends TaskLoop {
|
|
10154
10326
|
errorAction.resolved = true;
|
10155
10327
|
}
|
10156
10328
|
} else {
|
10157
|
-
|
10329
|
+
this.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
|
10158
10330
|
return;
|
10159
10331
|
}
|
10160
10332
|
} else if ((errorAction == null ? void 0 : errorAction.action) === NetworkErrorAction.SendAlternateToPenaltyBox) {
|
@@ -10563,6 +10735,7 @@ const initPTSFn = (timestamp, timeOffset, initPTS) => {
|
|
10563
10735
|
*/
|
10564
10736
|
function getAudioConfig(observer, data, offset, audioCodec) {
|
10565
10737
|
let adtsObjectType;
|
10738
|
+
let originalAdtsObjectType;
|
10566
10739
|
let adtsExtensionSamplingIndex;
|
10567
10740
|
let adtsChannelConfig;
|
10568
10741
|
let config;
|
@@ -10570,7 +10743,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
10570
10743
|
const manifestCodec = audioCodec;
|
10571
10744
|
const adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
|
10572
10745
|
// byte 2
|
10573
|
-
adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
|
10746
|
+
adtsObjectType = originalAdtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
|
10574
10747
|
const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
|
10575
10748
|
if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
|
10576
10749
|
const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
|
@@ -10587,8 +10760,8 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
10587
10760
|
// byte 3
|
10588
10761
|
adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6;
|
10589
10762
|
logger.log(`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`);
|
10590
|
-
//
|
10591
|
-
if (/firefox/i.test(userAgent)) {
|
10763
|
+
// Firefox and Pale Moon: freq less than 24kHz = AAC SBR (HE-AAC)
|
10764
|
+
if (/firefox|palemoon/i.test(userAgent)) {
|
10592
10765
|
if (adtsSamplingIndex >= 6) {
|
10593
10766
|
adtsObjectType = 5;
|
10594
10767
|
config = new Array(4);
|
@@ -10682,6 +10855,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
10682
10855
|
samplerate: adtsSamplingRates[adtsSamplingIndex],
|
10683
10856
|
channelCount: adtsChannelConfig,
|
10684
10857
|
codec: 'mp4a.40.' + adtsObjectType,
|
10858
|
+
parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
|
10685
10859
|
manifestCodec
|
10686
10860
|
};
|
10687
10861
|
}
|
@@ -10736,7 +10910,8 @@ function initTrackConfig(track, observer, data, offset, audioCodec) {
|
|
10736
10910
|
track.channelCount = config.channelCount;
|
10737
10911
|
track.codec = config.codec;
|
10738
10912
|
track.manifestCodec = config.manifestCodec;
|
10739
|
-
|
10913
|
+
track.parsedCodec = config.parsedCodec;
|
10914
|
+
logger.log(`parsed codec:${track.parsedCodec}, codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
|
10740
10915
|
}
|
10741
10916
|
}
|
10742
10917
|
function getFrameDuration(samplerate) {
|
@@ -11327,11 +11502,115 @@ class BaseVideoParser {
|
|
11327
11502
|
logger.log(VideoSample.pts + '/' + VideoSample.dts + ':' + VideoSample.debug);
|
11328
11503
|
}
|
11329
11504
|
}
|
11330
|
-
|
11505
|
+
parseNALu(track, array) {
|
11506
|
+
const len = array.byteLength;
|
11507
|
+
let state = track.naluState || 0;
|
11508
|
+
const lastState = state;
|
11509
|
+
const units = [];
|
11510
|
+
let i = 0;
|
11511
|
+
let value;
|
11512
|
+
let overflow;
|
11513
|
+
let unitType;
|
11514
|
+
let lastUnitStart = -1;
|
11515
|
+
let lastUnitType = 0;
|
11516
|
+
// logger.log('PES:' + Hex.hexDump(array));
|
11331
11517
|
|
11332
|
-
|
11333
|
-
|
11334
|
-
|
11518
|
+
if (state === -1) {
|
11519
|
+
// special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
|
11520
|
+
lastUnitStart = 0;
|
11521
|
+
// NALu type is value read from offset 0
|
11522
|
+
lastUnitType = this.getNALuType(array, 0);
|
11523
|
+
state = 0;
|
11524
|
+
i = 1;
|
11525
|
+
}
|
11526
|
+
while (i < len) {
|
11527
|
+
value = array[i++];
|
11528
|
+
// optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
|
11529
|
+
if (!state) {
|
11530
|
+
state = value ? 0 : 1;
|
11531
|
+
continue;
|
11532
|
+
}
|
11533
|
+
if (state === 1) {
|
11534
|
+
state = value ? 0 : 2;
|
11535
|
+
continue;
|
11536
|
+
}
|
11537
|
+
// here we have state either equal to 2 or 3
|
11538
|
+
if (!value) {
|
11539
|
+
state = 3;
|
11540
|
+
} else if (value === 1) {
|
11541
|
+
overflow = i - state - 1;
|
11542
|
+
if (lastUnitStart >= 0) {
|
11543
|
+
const unit = {
|
11544
|
+
data: array.subarray(lastUnitStart, overflow),
|
11545
|
+
type: lastUnitType
|
11546
|
+
};
|
11547
|
+
// logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
|
11548
|
+
units.push(unit);
|
11549
|
+
} else {
|
11550
|
+
// lastUnitStart is undefined => this is the first start code found in this PES packet
|
11551
|
+
// first check if start code delimiter is overlapping between 2 PES packets,
|
11552
|
+
// ie it started in last packet (lastState not zero)
|
11553
|
+
// and ended at the beginning of this PES packet (i <= 4 - lastState)
|
11554
|
+
const lastUnit = this.getLastNalUnit(track.samples);
|
11555
|
+
if (lastUnit) {
|
11556
|
+
if (lastState && i <= 4 - lastState) {
|
11557
|
+
// start delimiter overlapping between PES packets
|
11558
|
+
// strip start delimiter bytes from the end of last NAL unit
|
11559
|
+
// check if lastUnit had a state different from zero
|
11560
|
+
if (lastUnit.state) {
|
11561
|
+
// strip last bytes
|
11562
|
+
lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
|
11563
|
+
}
|
11564
|
+
}
|
11565
|
+
// If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
|
11566
|
+
|
11567
|
+
if (overflow > 0) {
|
11568
|
+
// logger.log('first NALU found with overflow:' + overflow);
|
11569
|
+
lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
|
11570
|
+
lastUnit.state = 0;
|
11571
|
+
}
|
11572
|
+
}
|
11573
|
+
}
|
11574
|
+
// check if we can read unit type
|
11575
|
+
if (i < len) {
|
11576
|
+
unitType = this.getNALuType(array, i);
|
11577
|
+
// logger.log('find NALU @ offset:' + i + ',type:' + unitType);
|
11578
|
+
lastUnitStart = i;
|
11579
|
+
lastUnitType = unitType;
|
11580
|
+
state = 0;
|
11581
|
+
} else {
|
11582
|
+
// not enough byte to read unit type. let's read it on next PES parsing
|
11583
|
+
state = -1;
|
11584
|
+
}
|
11585
|
+
} else {
|
11586
|
+
state = 0;
|
11587
|
+
}
|
11588
|
+
}
|
11589
|
+
if (lastUnitStart >= 0 && state >= 0) {
|
11590
|
+
const unit = {
|
11591
|
+
data: array.subarray(lastUnitStart, len),
|
11592
|
+
type: lastUnitType,
|
11593
|
+
state: state
|
11594
|
+
};
|
11595
|
+
units.push(unit);
|
11596
|
+
// logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
|
11597
|
+
}
|
11598
|
+
// no NALu found
|
11599
|
+
if (units.length === 0) {
|
11600
|
+
// append pes.data to previous NAL unit
|
11601
|
+
const lastUnit = this.getLastNalUnit(track.samples);
|
11602
|
+
if (lastUnit) {
|
11603
|
+
lastUnit.data = appendUint8Array(lastUnit.data, array);
|
11604
|
+
}
|
11605
|
+
}
|
11606
|
+
track.naluState = state;
|
11607
|
+
return units;
|
11608
|
+
}
|
11609
|
+
}
|
11610
|
+
|
11611
|
+
/**
|
11612
|
+
* Parser for exponential Golomb codes, a variable-bitwidth number encoding scheme used by h264.
|
11613
|
+
*/
|
11335
11614
|
|
11336
11615
|
class ExpGolomb {
|
11337
11616
|
constructor(data) {
|
@@ -11469,21 +11748,171 @@ class ExpGolomb {
|
|
11469
11748
|
readUInt() {
|
11470
11749
|
return this.readBits(32);
|
11471
11750
|
}
|
11751
|
+
}
|
11752
|
+
|
11753
|
+
class AvcVideoParser extends BaseVideoParser {
|
11754
|
+
parsePES(track, textTrack, pes, last, duration) {
|
11755
|
+
const units = this.parseNALu(track, pes.data);
|
11756
|
+
let VideoSample = this.VideoSample;
|
11757
|
+
let push;
|
11758
|
+
let spsfound = false;
|
11759
|
+
// free pes.data to save up some memory
|
11760
|
+
pes.data = null;
|
11761
|
+
|
11762
|
+
// if new NAL units found and last sample still there, let's push ...
|
11763
|
+
// this helps parsing streams with missing AUD (only do this if AUD never found)
|
11764
|
+
if (VideoSample && units.length && !track.audFound) {
|
11765
|
+
this.pushAccessUnit(VideoSample, track);
|
11766
|
+
VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
|
11767
|
+
}
|
11768
|
+
units.forEach(unit => {
|
11769
|
+
var _VideoSample2;
|
11770
|
+
switch (unit.type) {
|
11771
|
+
// NDR
|
11772
|
+
case 1:
|
11773
|
+
{
|
11774
|
+
let iskey = false;
|
11775
|
+
push = true;
|
11776
|
+
const data = unit.data;
|
11777
|
+
// only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
|
11778
|
+
if (spsfound && data.length > 4) {
|
11779
|
+
// retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
|
11780
|
+
const sliceType = this.readSliceType(data);
|
11781
|
+
// 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
|
11782
|
+
// SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
|
11783
|
+
// An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
|
11784
|
+
// I slice: A slice that is not an SI slice that is decoded using intra prediction only.
|
11785
|
+
// if (sliceType === 2 || sliceType === 7) {
|
11786
|
+
if (sliceType === 2 || sliceType === 4 || sliceType === 7 || sliceType === 9) {
|
11787
|
+
iskey = true;
|
11788
|
+
}
|
11789
|
+
}
|
11790
|
+
if (iskey) {
|
11791
|
+
var _VideoSample;
|
11792
|
+
// if we have non-keyframe data already, that cannot belong to the same frame as a keyframe, so force a push
|
11793
|
+
if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
|
11794
|
+
this.pushAccessUnit(VideoSample, track);
|
11795
|
+
VideoSample = this.VideoSample = null;
|
11796
|
+
}
|
11797
|
+
}
|
11798
|
+
if (!VideoSample) {
|
11799
|
+
VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
|
11800
|
+
}
|
11801
|
+
VideoSample.frame = true;
|
11802
|
+
VideoSample.key = iskey;
|
11803
|
+
break;
|
11804
|
+
// IDR
|
11805
|
+
}
|
11806
|
+
case 5:
|
11807
|
+
push = true;
|
11808
|
+
// handle PES not starting with AUD
|
11809
|
+
// if we have frame data already, that cannot belong to the same frame, so force a push
|
11810
|
+
if ((_VideoSample2 = VideoSample) != null && _VideoSample2.frame && !VideoSample.key) {
|
11811
|
+
this.pushAccessUnit(VideoSample, track);
|
11812
|
+
VideoSample = this.VideoSample = null;
|
11813
|
+
}
|
11814
|
+
if (!VideoSample) {
|
11815
|
+
VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
|
11816
|
+
}
|
11817
|
+
VideoSample.key = true;
|
11818
|
+
VideoSample.frame = true;
|
11819
|
+
break;
|
11820
|
+
// SEI
|
11821
|
+
case 6:
|
11822
|
+
{
|
11823
|
+
push = true;
|
11824
|
+
parseSEIMessageFromNALu(unit.data, 1, pes.pts, textTrack.samples);
|
11825
|
+
break;
|
11826
|
+
// SPS
|
11827
|
+
}
|
11828
|
+
case 7:
|
11829
|
+
{
|
11830
|
+
var _track$pixelRatio, _track$pixelRatio2;
|
11831
|
+
push = true;
|
11832
|
+
spsfound = true;
|
11833
|
+
const sps = unit.data;
|
11834
|
+
const config = this.readSPS(sps);
|
11835
|
+
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]) {
|
11836
|
+
track.width = config.width;
|
11837
|
+
track.height = config.height;
|
11838
|
+
track.pixelRatio = config.pixelRatio;
|
11839
|
+
track.sps = [sps];
|
11840
|
+
track.duration = duration;
|
11841
|
+
const codecarray = sps.subarray(1, 4);
|
11842
|
+
let codecstring = 'avc1.';
|
11843
|
+
for (let i = 0; i < 3; i++) {
|
11844
|
+
let h = codecarray[i].toString(16);
|
11845
|
+
if (h.length < 2) {
|
11846
|
+
h = '0' + h;
|
11847
|
+
}
|
11848
|
+
codecstring += h;
|
11849
|
+
}
|
11850
|
+
track.codec = codecstring;
|
11851
|
+
}
|
11852
|
+
break;
|
11853
|
+
}
|
11854
|
+
// PPS
|
11855
|
+
case 8:
|
11856
|
+
push = true;
|
11857
|
+
track.pps = [unit.data];
|
11858
|
+
break;
|
11859
|
+
// AUD
|
11860
|
+
case 9:
|
11861
|
+
push = true;
|
11862
|
+
track.audFound = true;
|
11863
|
+
if (VideoSample) {
|
11864
|
+
this.pushAccessUnit(VideoSample, track);
|
11865
|
+
}
|
11866
|
+
VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
|
11867
|
+
break;
|
11868
|
+
// Filler Data
|
11869
|
+
case 12:
|
11870
|
+
push = true;
|
11871
|
+
break;
|
11872
|
+
default:
|
11873
|
+
push = false;
|
11874
|
+
if (VideoSample) {
|
11875
|
+
VideoSample.debug += 'unknown NAL ' + unit.type + ' ';
|
11876
|
+
}
|
11877
|
+
break;
|
11878
|
+
}
|
11879
|
+
if (VideoSample && push) {
|
11880
|
+
const units = VideoSample.units;
|
11881
|
+
units.push(unit);
|
11882
|
+
}
|
11883
|
+
});
|
11884
|
+
// if last PES packet, push samples
|
11885
|
+
if (last && VideoSample) {
|
11886
|
+
this.pushAccessUnit(VideoSample, track);
|
11887
|
+
this.VideoSample = null;
|
11888
|
+
}
|
11889
|
+
}
|
11890
|
+
getNALuType(data, offset) {
|
11891
|
+
return data[offset] & 0x1f;
|
11892
|
+
}
|
11893
|
+
readSliceType(data) {
|
11894
|
+
const eg = new ExpGolomb(data);
|
11895
|
+
// skip NALu type
|
11896
|
+
eg.readUByte();
|
11897
|
+
// discard first_mb_in_slice
|
11898
|
+
eg.readUEG();
|
11899
|
+
// return slice_type
|
11900
|
+
return eg.readUEG();
|
11901
|
+
}
|
11472
11902
|
|
11473
11903
|
/**
|
11474
|
-
*
|
11475
|
-
* list is optionally transmitted as part of a sequence parameter
|
11904
|
+
* The scaling list is optionally transmitted as part of a sequence parameter
|
11476
11905
|
* set and is not relevant to transmuxing.
|
11477
11906
|
* @param count the number of entries in this scaling list
|
11478
11907
|
* @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
|
11479
11908
|
*/
|
11480
|
-
skipScalingList(count) {
|
11909
|
+
skipScalingList(count, reader) {
|
11481
11910
|
let lastScale = 8;
|
11482
11911
|
let nextScale = 8;
|
11483
11912
|
let deltaScale;
|
11484
11913
|
for (let j = 0; j < count; j++) {
|
11485
11914
|
if (nextScale !== 0) {
|
11486
|
-
deltaScale =
|
11915
|
+
deltaScale = reader.readEG();
|
11487
11916
|
nextScale = (lastScale + deltaScale + 256) % 256;
|
11488
11917
|
}
|
11489
11918
|
lastScale = nextScale === 0 ? lastScale : nextScale;
|
@@ -11498,7 +11927,8 @@ class ExpGolomb {
|
|
11498
11927
|
* sequence parameter set, including the dimensions of the
|
11499
11928
|
* associated video frames.
|
11500
11929
|
*/
|
11501
|
-
readSPS() {
|
11930
|
+
readSPS(sps) {
|
11931
|
+
const eg = new ExpGolomb(sps);
|
11502
11932
|
let frameCropLeftOffset = 0;
|
11503
11933
|
let frameCropRightOffset = 0;
|
11504
11934
|
let frameCropTopOffset = 0;
|
@@ -11506,13 +11936,13 @@ class ExpGolomb {
|
|
11506
11936
|
let numRefFramesInPicOrderCntCycle;
|
11507
11937
|
let scalingListCount;
|
11508
11938
|
let i;
|
11509
|
-
const readUByte =
|
11510
|
-
const readBits =
|
11511
|
-
const readUEG =
|
11512
|
-
const readBoolean =
|
11513
|
-
const skipBits =
|
11514
|
-
const skipEG =
|
11515
|
-
const skipUEG =
|
11939
|
+
const readUByte = eg.readUByte.bind(eg);
|
11940
|
+
const readBits = eg.readBits.bind(eg);
|
11941
|
+
const readUEG = eg.readUEG.bind(eg);
|
11942
|
+
const readBoolean = eg.readBoolean.bind(eg);
|
11943
|
+
const skipBits = eg.skipBits.bind(eg);
|
11944
|
+
const skipEG = eg.skipEG.bind(eg);
|
11945
|
+
const skipUEG = eg.skipUEG.bind(eg);
|
11516
11946
|
const skipScalingList = this.skipScalingList.bind(this);
|
11517
11947
|
readUByte();
|
11518
11948
|
const profileIdc = readUByte(); // profile_idc
|
@@ -11537,9 +11967,9 @@ class ExpGolomb {
|
|
11537
11967
|
if (readBoolean()) {
|
11538
11968
|
// seq_scaling_list_present_flag[ i ]
|
11539
11969
|
if (i < 6) {
|
11540
|
-
skipScalingList(16);
|
11970
|
+
skipScalingList(16, eg);
|
11541
11971
|
} else {
|
11542
|
-
skipScalingList(64);
|
11972
|
+
skipScalingList(64, eg);
|
11543
11973
|
}
|
11544
11974
|
}
|
11545
11975
|
}
|
@@ -11644,19 +12074,15 @@ class ExpGolomb {
|
|
11644
12074
|
pixelRatio: pixelRatio
|
11645
12075
|
};
|
11646
12076
|
}
|
11647
|
-
readSliceType() {
|
11648
|
-
// skip NALu type
|
11649
|
-
this.readUByte();
|
11650
|
-
// discard first_mb_in_slice
|
11651
|
-
this.readUEG();
|
11652
|
-
// return slice_type
|
11653
|
-
return this.readUEG();
|
11654
|
-
}
|
11655
12077
|
}
|
11656
12078
|
|
11657
|
-
class
|
11658
|
-
|
11659
|
-
|
12079
|
+
class HevcVideoParser extends BaseVideoParser {
|
12080
|
+
constructor(...args) {
|
12081
|
+
super(...args);
|
12082
|
+
this.initVPS = null;
|
12083
|
+
}
|
12084
|
+
parsePES(track, textTrack, pes, last, duration) {
|
12085
|
+
const units = this.parseNALu(track, pes.data);
|
11660
12086
|
let VideoSample = this.VideoSample;
|
11661
12087
|
let push;
|
11662
12088
|
let spsfound = false;
|
@@ -11672,42 +12098,49 @@ class AvcVideoParser extends BaseVideoParser {
|
|
11672
12098
|
units.forEach(unit => {
|
11673
12099
|
var _VideoSample2;
|
11674
12100
|
switch (unit.type) {
|
11675
|
-
//
|
12101
|
+
// NON-IDR, NON RANDOM ACCESS SLICE
|
12102
|
+
case 0:
|
11676
12103
|
case 1:
|
11677
|
-
|
11678
|
-
|
11679
|
-
|
11680
|
-
|
11681
|
-
|
11682
|
-
|
11683
|
-
|
11684
|
-
|
11685
|
-
|
11686
|
-
|
11687
|
-
|
11688
|
-
|
11689
|
-
|
11690
|
-
|
11691
|
-
|
11692
|
-
|
11693
|
-
|
11694
|
-
|
11695
|
-
|
11696
|
-
|
11697
|
-
|
11698
|
-
|
11699
|
-
|
11700
|
-
|
11701
|
-
|
11702
|
-
if (!VideoSample) {
|
11703
|
-
|
12104
|
+
case 2:
|
12105
|
+
case 3:
|
12106
|
+
case 4:
|
12107
|
+
case 5:
|
12108
|
+
case 6:
|
12109
|
+
case 7:
|
12110
|
+
case 8:
|
12111
|
+
case 9:
|
12112
|
+
if (!VideoSample) {
|
12113
|
+
VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
|
12114
|
+
}
|
12115
|
+
VideoSample.frame = true;
|
12116
|
+
push = true;
|
12117
|
+
break;
|
12118
|
+
|
12119
|
+
// CRA, BLA (random access picture)
|
12120
|
+
case 16:
|
12121
|
+
case 17:
|
12122
|
+
case 18:
|
12123
|
+
case 21:
|
12124
|
+
push = true;
|
12125
|
+
if (spsfound) {
|
12126
|
+
var _VideoSample;
|
12127
|
+
// handle PES not starting with AUD
|
12128
|
+
// if we have frame data already, that cannot belong to the same frame, so force a push
|
12129
|
+
if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
|
12130
|
+
this.pushAccessUnit(VideoSample, track);
|
12131
|
+
VideoSample = this.VideoSample = null;
|
11704
12132
|
}
|
11705
|
-
VideoSample.frame = true;
|
11706
|
-
VideoSample.key = iskey;
|
11707
|
-
break;
|
11708
|
-
// IDR
|
11709
12133
|
}
|
11710
|
-
|
12134
|
+
if (!VideoSample) {
|
12135
|
+
VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
|
12136
|
+
}
|
12137
|
+
VideoSample.key = true;
|
12138
|
+
VideoSample.frame = true;
|
12139
|
+
break;
|
12140
|
+
|
12141
|
+
// IDR
|
12142
|
+
case 19:
|
12143
|
+
case 20:
|
11711
12144
|
push = true;
|
11712
12145
|
// handle PES not starting with AUD
|
11713
12146
|
// if we have frame data already, that cannot belong to the same frame, so force a push
|
@@ -11721,180 +12154,518 @@ class AvcVideoParser extends BaseVideoParser {
|
|
11721
12154
|
VideoSample.key = true;
|
11722
12155
|
VideoSample.frame = true;
|
11723
12156
|
break;
|
12157
|
+
|
11724
12158
|
// SEI
|
11725
|
-
case
|
11726
|
-
{
|
11727
|
-
push = true;
|
11728
|
-
parseSEIMessageFromNALu(unit.data, 1, pes.pts, textTrack.samples);
|
11729
|
-
break;
|
11730
|
-
// SPS
|
11731
|
-
}
|
11732
|
-
case 7:
|
11733
|
-
{
|
11734
|
-
var _track$pixelRatio, _track$pixelRatio2;
|
11735
|
-
push = true;
|
11736
|
-
spsfound = true;
|
11737
|
-
const sps = unit.data;
|
11738
|
-
const expGolombDecoder = new ExpGolomb(sps);
|
11739
|
-
const config = expGolombDecoder.readSPS();
|
11740
|
-
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]) {
|
11741
|
-
track.width = config.width;
|
11742
|
-
track.height = config.height;
|
11743
|
-
track.pixelRatio = config.pixelRatio;
|
11744
|
-
track.sps = [sps];
|
11745
|
-
track.duration = duration;
|
11746
|
-
const codecarray = sps.subarray(1, 4);
|
11747
|
-
let codecstring = 'avc1.';
|
11748
|
-
for (let i = 0; i < 3; i++) {
|
11749
|
-
let h = codecarray[i].toString(16);
|
11750
|
-
if (h.length < 2) {
|
11751
|
-
h = '0' + h;
|
11752
|
-
}
|
11753
|
-
codecstring += h;
|
11754
|
-
}
|
11755
|
-
track.codec = codecstring;
|
11756
|
-
}
|
11757
|
-
break;
|
11758
|
-
}
|
11759
|
-
// PPS
|
11760
|
-
case 8:
|
12159
|
+
case 39:
|
11761
12160
|
push = true;
|
11762
|
-
|
11763
|
-
|
11764
|
-
|
11765
|
-
case 9:
|
11766
|
-
push = true;
|
11767
|
-
track.audFound = true;
|
11768
|
-
if (VideoSample) {
|
11769
|
-
this.pushAccessUnit(VideoSample, track);
|
11770
|
-
}
|
11771
|
-
VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
|
12161
|
+
parseSEIMessageFromNALu(unit.data, 2,
|
12162
|
+
// NALu header size
|
12163
|
+
pes.pts, textTrack.samples);
|
11772
12164
|
break;
|
11773
|
-
|
11774
|
-
|
12165
|
+
|
12166
|
+
// VPS
|
12167
|
+
case 32:
|
11775
12168
|
push = true;
|
11776
|
-
|
11777
|
-
|
11778
|
-
|
11779
|
-
|
11780
|
-
VideoSample.debug += 'unknown NAL ' + unit.type + ' ';
|
12169
|
+
if (!track.vps) {
|
12170
|
+
const config = this.readVPS(unit.data);
|
12171
|
+
track.params = _objectSpread2({}, config);
|
12172
|
+
this.initVPS = unit.data;
|
11781
12173
|
}
|
12174
|
+
track.vps = [unit.data];
|
11782
12175
|
break;
|
11783
|
-
}
|
11784
|
-
if (VideoSample && push) {
|
11785
|
-
const units = VideoSample.units;
|
11786
|
-
units.push(unit);
|
11787
|
-
}
|
11788
|
-
});
|
11789
|
-
// if last PES packet, push samples
|
11790
|
-
if (last && VideoSample) {
|
11791
|
-
this.pushAccessUnit(VideoSample, track);
|
11792
|
-
this.VideoSample = null;
|
11793
|
-
}
|
11794
|
-
}
|
11795
|
-
parseAVCNALu(track, array) {
|
11796
|
-
const len = array.byteLength;
|
11797
|
-
let state = track.naluState || 0;
|
11798
|
-
const lastState = state;
|
11799
|
-
const units = [];
|
11800
|
-
let i = 0;
|
11801
|
-
let value;
|
11802
|
-
let overflow;
|
11803
|
-
let unitType;
|
11804
|
-
let lastUnitStart = -1;
|
11805
|
-
let lastUnitType = 0;
|
11806
|
-
// logger.log('PES:' + Hex.hexDump(array));
|
11807
12176
|
|
11808
|
-
|
11809
|
-
|
11810
|
-
|
11811
|
-
|
11812
|
-
|
11813
|
-
|
11814
|
-
|
11815
|
-
|
11816
|
-
|
11817
|
-
|
11818
|
-
|
11819
|
-
|
11820
|
-
|
11821
|
-
|
11822
|
-
|
11823
|
-
|
11824
|
-
|
11825
|
-
|
11826
|
-
|
11827
|
-
// here we have state either equal to 2 or 3
|
11828
|
-
if (!value) {
|
11829
|
-
state = 3;
|
11830
|
-
} else if (value === 1) {
|
11831
|
-
overflow = i - state - 1;
|
11832
|
-
if (lastUnitStart >= 0) {
|
11833
|
-
const unit = {
|
11834
|
-
data: array.subarray(lastUnitStart, overflow),
|
11835
|
-
type: lastUnitType
|
11836
|
-
};
|
11837
|
-
// logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
|
11838
|
-
units.push(unit);
|
11839
|
-
} else {
|
11840
|
-
// lastUnitStart is undefined => this is the first start code found in this PES packet
|
11841
|
-
// first check if start code delimiter is overlapping between 2 PES packets,
|
11842
|
-
// ie it started in last packet (lastState not zero)
|
11843
|
-
// and ended at the beginning of this PES packet (i <= 4 - lastState)
|
11844
|
-
const lastUnit = this.getLastNalUnit(track.samples);
|
11845
|
-
if (lastUnit) {
|
11846
|
-
if (lastState && i <= 4 - lastState) {
|
11847
|
-
// start delimiter overlapping between PES packets
|
11848
|
-
// strip start delimiter bytes from the end of last NAL unit
|
11849
|
-
// check if lastUnit had a state different from zero
|
11850
|
-
if (lastUnit.state) {
|
11851
|
-
// strip last bytes
|
11852
|
-
lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
|
12177
|
+
// SPS
|
12178
|
+
case 33:
|
12179
|
+
push = true;
|
12180
|
+
spsfound = true;
|
12181
|
+
if (typeof track.params === 'object') {
|
12182
|
+
if (track.vps !== undefined && track.vps[0] !== this.initVPS && track.sps !== undefined && !this.matchSPS(track.sps[0], unit.data)) {
|
12183
|
+
this.initVPS = track.vps[0];
|
12184
|
+
track.sps = track.pps = undefined;
|
12185
|
+
}
|
12186
|
+
if (!track.sps) {
|
12187
|
+
const config = this.readSPS(unit.data);
|
12188
|
+
track.width = config.width;
|
12189
|
+
track.height = config.height;
|
12190
|
+
track.pixelRatio = config.pixelRatio;
|
12191
|
+
track.duration = duration;
|
12192
|
+
track.codec = config.codecString;
|
12193
|
+
track.sps = [];
|
12194
|
+
for (const prop in config.params) {
|
12195
|
+
track.params[prop] = config.params[prop];
|
11853
12196
|
}
|
11854
12197
|
}
|
11855
|
-
|
12198
|
+
if (track.vps !== undefined && track.vps[0] === this.initVPS) {
|
12199
|
+
track.sps.push(unit.data);
|
12200
|
+
}
|
12201
|
+
}
|
12202
|
+
if (!VideoSample) {
|
12203
|
+
VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
|
12204
|
+
}
|
12205
|
+
VideoSample.key = true;
|
12206
|
+
break;
|
11856
12207
|
|
11857
|
-
|
11858
|
-
|
11859
|
-
|
11860
|
-
|
12208
|
+
// PPS
|
12209
|
+
case 34:
|
12210
|
+
push = true;
|
12211
|
+
if (typeof track.params === 'object') {
|
12212
|
+
if (!track.pps) {
|
12213
|
+
track.pps = [];
|
12214
|
+
const config = this.readPPS(unit.data);
|
12215
|
+
for (const prop in config) {
|
12216
|
+
track.params[prop] = config[prop];
|
12217
|
+
}
|
12218
|
+
}
|
12219
|
+
if (this.initVPS !== null || track.pps.length === 0) {
|
12220
|
+
track.pps.push(unit.data);
|
11861
12221
|
}
|
11862
12222
|
}
|
12223
|
+
break;
|
12224
|
+
|
12225
|
+
// ACCESS UNIT DELIMITER
|
12226
|
+
case 35:
|
12227
|
+
push = true;
|
12228
|
+
track.audFound = true;
|
12229
|
+
if (VideoSample) {
|
12230
|
+
this.pushAccessUnit(VideoSample, track);
|
12231
|
+
}
|
12232
|
+
VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
|
12233
|
+
break;
|
12234
|
+
default:
|
12235
|
+
push = false;
|
12236
|
+
if (VideoSample) {
|
12237
|
+
VideoSample.debug += 'unknown or irrelevant NAL ' + unit.type + ' ';
|
12238
|
+
}
|
12239
|
+
break;
|
12240
|
+
}
|
12241
|
+
if (VideoSample && push) {
|
12242
|
+
const units = VideoSample.units;
|
12243
|
+
units.push(unit);
|
12244
|
+
}
|
12245
|
+
});
|
12246
|
+
// if last PES packet, push samples
|
12247
|
+
if (last && VideoSample) {
|
12248
|
+
this.pushAccessUnit(VideoSample, track);
|
12249
|
+
this.VideoSample = null;
|
12250
|
+
}
|
12251
|
+
}
|
12252
|
+
getNALuType(data, offset) {
|
12253
|
+
return (data[offset] & 0x7e) >>> 1;
|
12254
|
+
}
|
12255
|
+
ebsp2rbsp(arr) {
|
12256
|
+
const dst = new Uint8Array(arr.byteLength);
|
12257
|
+
let dstIdx = 0;
|
12258
|
+
for (let i = 0; i < arr.byteLength; i++) {
|
12259
|
+
if (i >= 2) {
|
12260
|
+
// Unescape: Skip 0x03 after 00 00
|
12261
|
+
if (arr[i] === 0x03 && arr[i - 1] === 0x00 && arr[i - 2] === 0x00) {
|
12262
|
+
continue;
|
11863
12263
|
}
|
11864
|
-
// check if we can read unit type
|
11865
|
-
if (i < len) {
|
11866
|
-
unitType = array[i] & 0x1f;
|
11867
|
-
// logger.log('find NALU @ offset:' + i + ',type:' + unitType);
|
11868
|
-
lastUnitStart = i;
|
11869
|
-
lastUnitType = unitType;
|
11870
|
-
state = 0;
|
11871
|
-
} else {
|
11872
|
-
// not enough byte to read unit type. let's read it on next PES parsing
|
11873
|
-
state = -1;
|
11874
|
-
}
|
11875
|
-
} else {
|
11876
|
-
state = 0;
|
11877
12264
|
}
|
12265
|
+
dst[dstIdx] = arr[i];
|
12266
|
+
dstIdx++;
|
11878
12267
|
}
|
11879
|
-
|
11880
|
-
|
11881
|
-
|
11882
|
-
|
11883
|
-
|
11884
|
-
|
11885
|
-
|
11886
|
-
|
12268
|
+
return new Uint8Array(dst.buffer, 0, dstIdx);
|
12269
|
+
}
|
12270
|
+
readVPS(vps) {
|
12271
|
+
const eg = new ExpGolomb(vps);
|
12272
|
+
// remove header
|
12273
|
+
eg.readUByte();
|
12274
|
+
eg.readUByte();
|
12275
|
+
eg.readBits(4); // video_parameter_set_id
|
12276
|
+
eg.skipBits(2);
|
12277
|
+
eg.readBits(6); // max_layers_minus1
|
12278
|
+
const max_sub_layers_minus1 = eg.readBits(3);
|
12279
|
+
const temporal_id_nesting_flag = eg.readBoolean();
|
12280
|
+
// ...vui fps can be here, but empty fps value is not critical for metadata
|
12281
|
+
|
12282
|
+
return {
|
12283
|
+
numTemporalLayers: max_sub_layers_minus1 + 1,
|
12284
|
+
temporalIdNested: temporal_id_nesting_flag
|
12285
|
+
};
|
12286
|
+
}
|
12287
|
+
readSPS(sps) {
|
12288
|
+
const eg = new ExpGolomb(this.ebsp2rbsp(sps));
|
12289
|
+
eg.readUByte();
|
12290
|
+
eg.readUByte();
|
12291
|
+
eg.readBits(4); //video_parameter_set_id
|
12292
|
+
const max_sub_layers_minus1 = eg.readBits(3);
|
12293
|
+
eg.readBoolean(); // temporal_id_nesting_flag
|
12294
|
+
|
12295
|
+
// profile_tier_level
|
12296
|
+
const general_profile_space = eg.readBits(2);
|
12297
|
+
const general_tier_flag = eg.readBoolean();
|
12298
|
+
const general_profile_idc = eg.readBits(5);
|
12299
|
+
const general_profile_compatibility_flags_1 = eg.readUByte();
|
12300
|
+
const general_profile_compatibility_flags_2 = eg.readUByte();
|
12301
|
+
const general_profile_compatibility_flags_3 = eg.readUByte();
|
12302
|
+
const general_profile_compatibility_flags_4 = eg.readUByte();
|
12303
|
+
const general_constraint_indicator_flags_1 = eg.readUByte();
|
12304
|
+
const general_constraint_indicator_flags_2 = eg.readUByte();
|
12305
|
+
const general_constraint_indicator_flags_3 = eg.readUByte();
|
12306
|
+
const general_constraint_indicator_flags_4 = eg.readUByte();
|
12307
|
+
const general_constraint_indicator_flags_5 = eg.readUByte();
|
12308
|
+
const general_constraint_indicator_flags_6 = eg.readUByte();
|
12309
|
+
const general_level_idc = eg.readUByte();
|
12310
|
+
const sub_layer_profile_present_flags = [];
|
12311
|
+
const sub_layer_level_present_flags = [];
|
12312
|
+
for (let i = 0; i < max_sub_layers_minus1; i++) {
|
12313
|
+
sub_layer_profile_present_flags.push(eg.readBoolean());
|
12314
|
+
sub_layer_level_present_flags.push(eg.readBoolean());
|
12315
|
+
}
|
12316
|
+
if (max_sub_layers_minus1 > 0) {
|
12317
|
+
for (let i = max_sub_layers_minus1; i < 8; i++) {
|
12318
|
+
eg.readBits(2);
|
12319
|
+
}
|
12320
|
+
}
|
12321
|
+
for (let i = 0; i < max_sub_layers_minus1; i++) {
|
12322
|
+
if (sub_layer_profile_present_flags[i]) {
|
12323
|
+
eg.readUByte(); // sub_layer_profile_space, sub_layer_tier_flag, sub_layer_profile_idc
|
12324
|
+
eg.readUByte();
|
12325
|
+
eg.readUByte();
|
12326
|
+
eg.readUByte();
|
12327
|
+
eg.readUByte(); // sub_layer_profile_compatibility_flag
|
12328
|
+
eg.readUByte();
|
12329
|
+
eg.readUByte();
|
12330
|
+
eg.readUByte();
|
12331
|
+
eg.readUByte();
|
12332
|
+
eg.readUByte();
|
12333
|
+
eg.readUByte();
|
12334
|
+
}
|
12335
|
+
if (sub_layer_level_present_flags[i]) {
|
12336
|
+
eg.readUByte();
|
12337
|
+
}
|
12338
|
+
}
|
12339
|
+
eg.readUEG(); // seq_parameter_set_id
|
12340
|
+
const chroma_format_idc = eg.readUEG();
|
12341
|
+
if (chroma_format_idc == 3) {
|
12342
|
+
eg.skipBits(1); //separate_colour_plane_flag
|
12343
|
+
}
|
12344
|
+
const pic_width_in_luma_samples = eg.readUEG();
|
12345
|
+
const pic_height_in_luma_samples = eg.readUEG();
|
12346
|
+
const conformance_window_flag = eg.readBoolean();
|
12347
|
+
let pic_left_offset = 0,
|
12348
|
+
pic_right_offset = 0,
|
12349
|
+
pic_top_offset = 0,
|
12350
|
+
pic_bottom_offset = 0;
|
12351
|
+
if (conformance_window_flag) {
|
12352
|
+
pic_left_offset += eg.readUEG();
|
12353
|
+
pic_right_offset += eg.readUEG();
|
12354
|
+
pic_top_offset += eg.readUEG();
|
12355
|
+
pic_bottom_offset += eg.readUEG();
|
12356
|
+
}
|
12357
|
+
const bit_depth_luma_minus8 = eg.readUEG();
|
12358
|
+
const bit_depth_chroma_minus8 = eg.readUEG();
|
12359
|
+
const log2_max_pic_order_cnt_lsb_minus4 = eg.readUEG();
|
12360
|
+
const sub_layer_ordering_info_present_flag = eg.readBoolean();
|
12361
|
+
for (let i = sub_layer_ordering_info_present_flag ? 0 : max_sub_layers_minus1; i <= max_sub_layers_minus1; i++) {
|
12362
|
+
eg.skipUEG(); // max_dec_pic_buffering_minus1[i]
|
12363
|
+
eg.skipUEG(); // max_num_reorder_pics[i]
|
12364
|
+
eg.skipUEG(); // max_latency_increase_plus1[i]
|
12365
|
+
}
|
12366
|
+
eg.skipUEG(); // log2_min_luma_coding_block_size_minus3
|
12367
|
+
eg.skipUEG(); // log2_diff_max_min_luma_coding_block_size
|
12368
|
+
eg.skipUEG(); // log2_min_transform_block_size_minus2
|
12369
|
+
eg.skipUEG(); // log2_diff_max_min_transform_block_size
|
12370
|
+
eg.skipUEG(); // max_transform_hierarchy_depth_inter
|
12371
|
+
eg.skipUEG(); // max_transform_hierarchy_depth_intra
|
12372
|
+
const scaling_list_enabled_flag = eg.readBoolean();
|
12373
|
+
if (scaling_list_enabled_flag) {
|
12374
|
+
const sps_scaling_list_data_present_flag = eg.readBoolean();
|
12375
|
+
if (sps_scaling_list_data_present_flag) {
|
12376
|
+
for (let sizeId = 0; sizeId < 4; sizeId++) {
|
12377
|
+
for (let matrixId = 0; matrixId < (sizeId === 3 ? 2 : 6); matrixId++) {
|
12378
|
+
const scaling_list_pred_mode_flag = eg.readBoolean();
|
12379
|
+
if (!scaling_list_pred_mode_flag) {
|
12380
|
+
eg.readUEG(); // scaling_list_pred_matrix_id_delta
|
12381
|
+
} else {
|
12382
|
+
const coefNum = Math.min(64, 1 << 4 + (sizeId << 1));
|
12383
|
+
if (sizeId > 1) {
|
12384
|
+
eg.readEG();
|
12385
|
+
}
|
12386
|
+
for (let i = 0; i < coefNum; i++) {
|
12387
|
+
eg.readEG();
|
12388
|
+
}
|
12389
|
+
}
|
12390
|
+
}
|
12391
|
+
}
|
12392
|
+
}
|
11887
12393
|
}
|
11888
|
-
//
|
11889
|
-
|
11890
|
-
|
11891
|
-
|
11892
|
-
|
11893
|
-
|
12394
|
+
eg.readBoolean(); // amp_enabled_flag
|
12395
|
+
eg.readBoolean(); // sample_adaptive_offset_enabled_flag
|
12396
|
+
const pcm_enabled_flag = eg.readBoolean();
|
12397
|
+
if (pcm_enabled_flag) {
|
12398
|
+
eg.readUByte();
|
12399
|
+
eg.skipUEG();
|
12400
|
+
eg.skipUEG();
|
12401
|
+
eg.readBoolean();
|
12402
|
+
}
|
12403
|
+
const num_short_term_ref_pic_sets = eg.readUEG();
|
12404
|
+
let num_delta_pocs = 0;
|
12405
|
+
for (let i = 0; i < num_short_term_ref_pic_sets; i++) {
|
12406
|
+
let inter_ref_pic_set_prediction_flag = false;
|
12407
|
+
if (i !== 0) {
|
12408
|
+
inter_ref_pic_set_prediction_flag = eg.readBoolean();
|
12409
|
+
}
|
12410
|
+
if (inter_ref_pic_set_prediction_flag) {
|
12411
|
+
if (i === num_short_term_ref_pic_sets) {
|
12412
|
+
eg.readUEG();
|
12413
|
+
}
|
12414
|
+
eg.readBoolean();
|
12415
|
+
eg.readUEG();
|
12416
|
+
let next_num_delta_pocs = 0;
|
12417
|
+
for (let j = 0; j <= num_delta_pocs; j++) {
|
12418
|
+
const used_by_curr_pic_flag = eg.readBoolean();
|
12419
|
+
let use_delta_flag = false;
|
12420
|
+
if (!used_by_curr_pic_flag) {
|
12421
|
+
use_delta_flag = eg.readBoolean();
|
12422
|
+
}
|
12423
|
+
if (used_by_curr_pic_flag || use_delta_flag) {
|
12424
|
+
next_num_delta_pocs++;
|
12425
|
+
}
|
12426
|
+
}
|
12427
|
+
num_delta_pocs = next_num_delta_pocs;
|
12428
|
+
} else {
|
12429
|
+
const num_negative_pics = eg.readUEG();
|
12430
|
+
const num_positive_pics = eg.readUEG();
|
12431
|
+
num_delta_pocs = num_negative_pics + num_positive_pics;
|
12432
|
+
for (let j = 0; j < num_negative_pics; j++) {
|
12433
|
+
eg.readUEG();
|
12434
|
+
eg.readBoolean();
|
12435
|
+
}
|
12436
|
+
for (let j = 0; j < num_positive_pics; j++) {
|
12437
|
+
eg.readUEG();
|
12438
|
+
eg.readBoolean();
|
12439
|
+
}
|
12440
|
+
}
|
12441
|
+
}
|
12442
|
+
const long_term_ref_pics_present_flag = eg.readBoolean();
|
12443
|
+
if (long_term_ref_pics_present_flag) {
|
12444
|
+
const num_long_term_ref_pics_sps = eg.readUEG();
|
12445
|
+
for (let i = 0; i < num_long_term_ref_pics_sps; i++) {
|
12446
|
+
for (let j = 0; j < log2_max_pic_order_cnt_lsb_minus4 + 4; j++) {
|
12447
|
+
eg.readBits(1);
|
12448
|
+
}
|
12449
|
+
eg.readBits(1);
|
12450
|
+
}
|
12451
|
+
}
|
12452
|
+
let min_spatial_segmentation_idc = 0;
|
12453
|
+
let sar_width = 1,
|
12454
|
+
sar_height = 1;
|
12455
|
+
let fps_fixed = true,
|
12456
|
+
fps_den = 1,
|
12457
|
+
fps_num = 0;
|
12458
|
+
eg.readBoolean(); // sps_temporal_mvp_enabled_flag
|
12459
|
+
eg.readBoolean(); // strong_intra_smoothing_enabled_flag
|
12460
|
+
let default_display_window_flag = false;
|
12461
|
+
const vui_parameters_present_flag = eg.readBoolean();
|
12462
|
+
if (vui_parameters_present_flag) {
|
12463
|
+
const aspect_ratio_info_present_flag = eg.readBoolean();
|
12464
|
+
if (aspect_ratio_info_present_flag) {
|
12465
|
+
const aspect_ratio_idc = eg.readUByte();
|
12466
|
+
const sar_width_table = [1, 12, 10, 16, 40, 24, 20, 32, 80, 18, 15, 64, 160, 4, 3, 2];
|
12467
|
+
const sar_height_table = [1, 11, 11, 11, 33, 11, 11, 11, 33, 11, 11, 33, 99, 3, 2, 1];
|
12468
|
+
if (aspect_ratio_idc > 0 && aspect_ratio_idc < 16) {
|
12469
|
+
sar_width = sar_width_table[aspect_ratio_idc - 1];
|
12470
|
+
sar_height = sar_height_table[aspect_ratio_idc - 1];
|
12471
|
+
} else if (aspect_ratio_idc === 255) {
|
12472
|
+
sar_width = eg.readBits(16);
|
12473
|
+
sar_height = eg.readBits(16);
|
12474
|
+
}
|
12475
|
+
}
|
12476
|
+
const overscan_info_present_flag = eg.readBoolean();
|
12477
|
+
if (overscan_info_present_flag) {
|
12478
|
+
eg.readBoolean();
|
12479
|
+
}
|
12480
|
+
const video_signal_type_present_flag = eg.readBoolean();
|
12481
|
+
if (video_signal_type_present_flag) {
|
12482
|
+
eg.readBits(3);
|
12483
|
+
eg.readBoolean();
|
12484
|
+
const colour_description_present_flag = eg.readBoolean();
|
12485
|
+
if (colour_description_present_flag) {
|
12486
|
+
eg.readUByte();
|
12487
|
+
eg.readUByte();
|
12488
|
+
eg.readUByte();
|
12489
|
+
}
|
12490
|
+
}
|
12491
|
+
const chroma_loc_info_present_flag = eg.readBoolean();
|
12492
|
+
if (chroma_loc_info_present_flag) {
|
12493
|
+
eg.readUEG();
|
12494
|
+
eg.readUEG();
|
12495
|
+
}
|
12496
|
+
eg.readBoolean(); // neutral_chroma_indication_flag
|
12497
|
+
eg.readBoolean(); // field_seq_flag
|
12498
|
+
eg.readBoolean(); // frame_field_info_present_flag
|
12499
|
+
default_display_window_flag = eg.readBoolean();
|
12500
|
+
if (default_display_window_flag) {
|
12501
|
+
pic_left_offset += eg.readUEG();
|
12502
|
+
pic_right_offset += eg.readUEG();
|
12503
|
+
pic_top_offset += eg.readUEG();
|
12504
|
+
pic_bottom_offset += eg.readUEG();
|
12505
|
+
}
|
12506
|
+
const vui_timing_info_present_flag = eg.readBoolean();
|
12507
|
+
if (vui_timing_info_present_flag) {
|
12508
|
+
fps_den = eg.readBits(32);
|
12509
|
+
fps_num = eg.readBits(32);
|
12510
|
+
const vui_poc_proportional_to_timing_flag = eg.readBoolean();
|
12511
|
+
if (vui_poc_proportional_to_timing_flag) {
|
12512
|
+
eg.readUEG();
|
12513
|
+
}
|
12514
|
+
const vui_hrd_parameters_present_flag = eg.readBoolean();
|
12515
|
+
if (vui_hrd_parameters_present_flag) {
|
12516
|
+
//const commonInfPresentFlag = true;
|
12517
|
+
//if (commonInfPresentFlag) {
|
12518
|
+
const nal_hrd_parameters_present_flag = eg.readBoolean();
|
12519
|
+
const vcl_hrd_parameters_present_flag = eg.readBoolean();
|
12520
|
+
let sub_pic_hrd_params_present_flag = false;
|
12521
|
+
if (nal_hrd_parameters_present_flag || vcl_hrd_parameters_present_flag) {
|
12522
|
+
sub_pic_hrd_params_present_flag = eg.readBoolean();
|
12523
|
+
if (sub_pic_hrd_params_present_flag) {
|
12524
|
+
eg.readUByte();
|
12525
|
+
eg.readBits(5);
|
12526
|
+
eg.readBoolean();
|
12527
|
+
eg.readBits(5);
|
12528
|
+
}
|
12529
|
+
eg.readBits(4); // bit_rate_scale
|
12530
|
+
eg.readBits(4); // cpb_size_scale
|
12531
|
+
if (sub_pic_hrd_params_present_flag) {
|
12532
|
+
eg.readBits(4);
|
12533
|
+
}
|
12534
|
+
eg.readBits(5);
|
12535
|
+
eg.readBits(5);
|
12536
|
+
eg.readBits(5);
|
12537
|
+
}
|
12538
|
+
//}
|
12539
|
+
for (let i = 0; i <= max_sub_layers_minus1; i++) {
|
12540
|
+
fps_fixed = eg.readBoolean(); // fixed_pic_rate_general_flag
|
12541
|
+
const fixed_pic_rate_within_cvs_flag = fps_fixed || eg.readBoolean();
|
12542
|
+
let low_delay_hrd_flag = false;
|
12543
|
+
if (fixed_pic_rate_within_cvs_flag) {
|
12544
|
+
eg.readEG();
|
12545
|
+
} else {
|
12546
|
+
low_delay_hrd_flag = eg.readBoolean();
|
12547
|
+
}
|
12548
|
+
const cpb_cnt = low_delay_hrd_flag ? 1 : eg.readUEG() + 1;
|
12549
|
+
if (nal_hrd_parameters_present_flag) {
|
12550
|
+
for (let j = 0; j < cpb_cnt; j++) {
|
12551
|
+
eg.readUEG();
|
12552
|
+
eg.readUEG();
|
12553
|
+
if (sub_pic_hrd_params_present_flag) {
|
12554
|
+
eg.readUEG();
|
12555
|
+
eg.readUEG();
|
12556
|
+
}
|
12557
|
+
eg.skipBits(1);
|
12558
|
+
}
|
12559
|
+
}
|
12560
|
+
if (vcl_hrd_parameters_present_flag) {
|
12561
|
+
for (let j = 0; j < cpb_cnt; j++) {
|
12562
|
+
eg.readUEG();
|
12563
|
+
eg.readUEG();
|
12564
|
+
if (sub_pic_hrd_params_present_flag) {
|
12565
|
+
eg.readUEG();
|
12566
|
+
eg.readUEG();
|
12567
|
+
}
|
12568
|
+
eg.skipBits(1);
|
12569
|
+
}
|
12570
|
+
}
|
12571
|
+
}
|
12572
|
+
}
|
11894
12573
|
}
|
12574
|
+
const bitstream_restriction_flag = eg.readBoolean();
|
12575
|
+
if (bitstream_restriction_flag) {
|
12576
|
+
eg.readBoolean(); // tiles_fixed_structure_flag
|
12577
|
+
eg.readBoolean(); // motion_vectors_over_pic_boundaries_flag
|
12578
|
+
eg.readBoolean(); // restricted_ref_pic_lists_flag
|
12579
|
+
min_spatial_segmentation_idc = eg.readUEG();
|
12580
|
+
}
|
12581
|
+
}
|
12582
|
+
let width = pic_width_in_luma_samples,
|
12583
|
+
height = pic_height_in_luma_samples;
|
12584
|
+
if (conformance_window_flag || default_display_window_flag) {
|
12585
|
+
let chroma_scale_w = 1,
|
12586
|
+
chroma_scale_h = 1;
|
12587
|
+
if (chroma_format_idc === 1) {
|
12588
|
+
// YUV 420
|
12589
|
+
chroma_scale_w = chroma_scale_h = 2;
|
12590
|
+
} else if (chroma_format_idc == 2) {
|
12591
|
+
// YUV 422
|
12592
|
+
chroma_scale_w = 2;
|
12593
|
+
}
|
12594
|
+
width = pic_width_in_luma_samples - chroma_scale_w * pic_right_offset - chroma_scale_w * pic_left_offset;
|
12595
|
+
height = pic_height_in_luma_samples - chroma_scale_h * pic_bottom_offset - chroma_scale_h * pic_top_offset;
|
12596
|
+
}
|
12597
|
+
const profile_space_string = general_profile_space ? ['A', 'B', 'C'][general_profile_space] : '';
|
12598
|
+
const profile_compatibility_buf = general_profile_compatibility_flags_1 << 24 | general_profile_compatibility_flags_2 << 16 | general_profile_compatibility_flags_3 << 8 | general_profile_compatibility_flags_4;
|
12599
|
+
let profile_compatibility_rev = 0;
|
12600
|
+
for (let i = 0; i < 32; i++) {
|
12601
|
+
profile_compatibility_rev = (profile_compatibility_rev | (profile_compatibility_buf >> i & 1) << 31 - i) >>> 0; // reverse bit position (and cast as UInt32)
|
12602
|
+
}
|
12603
|
+
let profile_compatibility_flags_string = profile_compatibility_rev.toString(16);
|
12604
|
+
if (general_profile_idc === 1 && profile_compatibility_flags_string === '2') {
|
12605
|
+
profile_compatibility_flags_string = '6';
|
12606
|
+
}
|
12607
|
+
const tier_flag_string = general_tier_flag ? 'H' : 'L';
|
12608
|
+
return {
|
12609
|
+
codecString: `hvc1.${profile_space_string}${general_profile_idc}.${profile_compatibility_flags_string}.${tier_flag_string}${general_level_idc}.B0`,
|
12610
|
+
params: {
|
12611
|
+
general_tier_flag,
|
12612
|
+
general_profile_idc,
|
12613
|
+
general_profile_space,
|
12614
|
+
general_profile_compatibility_flags: [general_profile_compatibility_flags_1, general_profile_compatibility_flags_2, general_profile_compatibility_flags_3, general_profile_compatibility_flags_4],
|
12615
|
+
general_constraint_indicator_flags: [general_constraint_indicator_flags_1, general_constraint_indicator_flags_2, general_constraint_indicator_flags_3, general_constraint_indicator_flags_4, general_constraint_indicator_flags_5, general_constraint_indicator_flags_6],
|
12616
|
+
general_level_idc,
|
12617
|
+
bit_depth: bit_depth_luma_minus8 + 8,
|
12618
|
+
bit_depth_luma_minus8,
|
12619
|
+
bit_depth_chroma_minus8,
|
12620
|
+
min_spatial_segmentation_idc,
|
12621
|
+
chroma_format_idc: chroma_format_idc,
|
12622
|
+
frame_rate: {
|
12623
|
+
fixed: fps_fixed,
|
12624
|
+
fps: fps_num / fps_den
|
12625
|
+
}
|
12626
|
+
},
|
12627
|
+
width,
|
12628
|
+
height,
|
12629
|
+
pixelRatio: [sar_width, sar_height]
|
12630
|
+
};
|
12631
|
+
}
|
12632
|
+
readPPS(pps) {
|
12633
|
+
const eg = new ExpGolomb(this.ebsp2rbsp(pps));
|
12634
|
+
eg.readUByte();
|
12635
|
+
eg.readUByte();
|
12636
|
+
eg.skipUEG(); // pic_parameter_set_id
|
12637
|
+
eg.skipUEG(); // seq_parameter_set_id
|
12638
|
+
eg.skipBits(2); // dependent_slice_segments_enabled_flag, output_flag_present_flag
|
12639
|
+
eg.skipBits(3); // num_extra_slice_header_bits
|
12640
|
+
eg.skipBits(2); // sign_data_hiding_enabled_flag, cabac_init_present_flag
|
12641
|
+
eg.skipUEG();
|
12642
|
+
eg.skipUEG();
|
12643
|
+
eg.skipEG(); // init_qp_minus26
|
12644
|
+
eg.skipBits(2); // constrained_intra_pred_flag, transform_skip_enabled_flag
|
12645
|
+
const cu_qp_delta_enabled_flag = eg.readBoolean();
|
12646
|
+
if (cu_qp_delta_enabled_flag) {
|
12647
|
+
eg.skipUEG();
|
12648
|
+
}
|
12649
|
+
eg.skipEG(); // cb_qp_offset
|
12650
|
+
eg.skipEG(); // cr_qp_offset
|
12651
|
+
eg.skipBits(4); // pps_slice_chroma_qp_offsets_present_flag, weighted_pred_flag, weighted_bipred_flag, transquant_bypass_enabled_flag
|
12652
|
+
const tiles_enabled_flag = eg.readBoolean();
|
12653
|
+
const entropy_coding_sync_enabled_flag = eg.readBoolean();
|
12654
|
+
let parallelismType = 1; // slice-based parallel decoding
|
12655
|
+
if (entropy_coding_sync_enabled_flag && tiles_enabled_flag) {
|
12656
|
+
parallelismType = 0; // mixed-type parallel decoding
|
12657
|
+
} else if (entropy_coding_sync_enabled_flag) {
|
12658
|
+
parallelismType = 3; // wavefront-based parallel decoding
|
12659
|
+
} else if (tiles_enabled_flag) {
|
12660
|
+
parallelismType = 2; // tile-based parallel decoding
|
11895
12661
|
}
|
11896
|
-
|
11897
|
-
|
12662
|
+
return {
|
12663
|
+
parallelismType
|
12664
|
+
};
|
12665
|
+
}
|
12666
|
+
matchSPS(sps1, sps2) {
|
12667
|
+
// compare without headers and VPS related params
|
12668
|
+
return String.fromCharCode.apply(null, sps1).substr(3) === String.fromCharCode.apply(null, sps2).substr(3);
|
11898
12669
|
}
|
11899
12670
|
}
|
11900
12671
|
|
@@ -11912,7 +12683,7 @@ class SampleAesDecrypter {
|
|
11912
12683
|
});
|
11913
12684
|
}
|
11914
12685
|
decryptBuffer(encryptedData) {
|
11915
|
-
return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer);
|
12686
|
+
return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer, DecrypterAesMode.cbc);
|
11916
12687
|
}
|
11917
12688
|
|
11918
12689
|
// AAC - encrypt all full 16 bytes blocks starting from offset 16
|
@@ -12026,7 +12797,7 @@ class TSDemuxer {
|
|
12026
12797
|
this.observer = observer;
|
12027
12798
|
this.config = config;
|
12028
12799
|
this.typeSupported = typeSupported;
|
12029
|
-
this.videoParser =
|
12800
|
+
this.videoParser = null;
|
12030
12801
|
}
|
12031
12802
|
static probe(data) {
|
12032
12803
|
const syncOffset = TSDemuxer.syncOffset(data);
|
@@ -12191,7 +12962,21 @@ class TSDemuxer {
|
|
12191
12962
|
case videoPid:
|
12192
12963
|
if (stt) {
|
12193
12964
|
if (videoData && (pes = parsePES(videoData))) {
|
12194
|
-
this.videoParser
|
12965
|
+
if (this.videoParser === null) {
|
12966
|
+
switch (videoTrack.segmentCodec) {
|
12967
|
+
case 'avc':
|
12968
|
+
this.videoParser = new AvcVideoParser();
|
12969
|
+
break;
|
12970
|
+
case 'hevc':
|
12971
|
+
{
|
12972
|
+
this.videoParser = new HevcVideoParser();
|
12973
|
+
}
|
12974
|
+
break;
|
12975
|
+
}
|
12976
|
+
}
|
12977
|
+
if (this.videoParser !== null) {
|
12978
|
+
this.videoParser.parsePES(videoTrack, textTrack, pes, false, this._duration);
|
12979
|
+
}
|
12195
12980
|
}
|
12196
12981
|
videoData = {
|
12197
12982
|
data: [],
|
@@ -12358,8 +13143,22 @@ class TSDemuxer {
|
|
12358
13143
|
// try to parse last PES packets
|
12359
13144
|
let pes;
|
12360
13145
|
if (videoData && (pes = parsePES(videoData))) {
|
12361
|
-
this.videoParser
|
12362
|
-
|
13146
|
+
if (this.videoParser === null) {
|
13147
|
+
switch (videoTrack.segmentCodec) {
|
13148
|
+
case 'avc':
|
13149
|
+
this.videoParser = new AvcVideoParser();
|
13150
|
+
break;
|
13151
|
+
case 'hevc':
|
13152
|
+
{
|
13153
|
+
this.videoParser = new HevcVideoParser();
|
13154
|
+
}
|
13155
|
+
break;
|
13156
|
+
}
|
13157
|
+
}
|
13158
|
+
if (this.videoParser !== null) {
|
13159
|
+
this.videoParser.parsePES(videoTrack, textTrack, pes, true, this._duration);
|
13160
|
+
videoTrack.pesData = null;
|
13161
|
+
}
|
12363
13162
|
} else {
|
12364
13163
|
// either avcData null or PES truncated, keep it for next frag parsing
|
12365
13164
|
videoTrack.pesData = videoData;
|
@@ -12692,7 +13491,14 @@ function parsePMT(data, offset, typeSupported, isSampleAes) {
|
|
12692
13491
|
logger.warn('Unsupported EC-3 in M2TS found');
|
12693
13492
|
break;
|
12694
13493
|
case 0x24:
|
12695
|
-
|
13494
|
+
// ITU-T Rec. H.265 and ISO/IEC 23008-2 (HEVC)
|
13495
|
+
{
|
13496
|
+
if (result.videoPid === -1) {
|
13497
|
+
result.videoPid = pid;
|
13498
|
+
result.segmentVideoCodec = 'hevc';
|
13499
|
+
logger.log('HEVC in M2TS found');
|
13500
|
+
}
|
13501
|
+
}
|
12696
13502
|
break;
|
12697
13503
|
}
|
12698
13504
|
// move to the next table entry
|
@@ -12915,6 +13721,8 @@ class MP4 {
|
|
12915
13721
|
avc1: [],
|
12916
13722
|
// codingname
|
12917
13723
|
avcC: [],
|
13724
|
+
hvc1: [],
|
13725
|
+
hvcC: [],
|
12918
13726
|
btrt: [],
|
12919
13727
|
dinf: [],
|
12920
13728
|
dref: [],
|
@@ -13339,8 +14147,10 @@ class MP4 {
|
|
13339
14147
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.ac3(track));
|
13340
14148
|
}
|
13341
14149
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track));
|
13342
|
-
} else {
|
14150
|
+
} else if (track.segmentCodec === 'avc') {
|
13343
14151
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
|
14152
|
+
} else {
|
14153
|
+
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.hvc1(track));
|
13344
14154
|
}
|
13345
14155
|
}
|
13346
14156
|
static tkhd(track) {
|
@@ -13478,6 +14288,84 @@ class MP4 {
|
|
13478
14288
|
const result = appendUint8Array(MP4.FTYP, movie);
|
13479
14289
|
return result;
|
13480
14290
|
}
|
14291
|
+
static hvc1(track) {
|
14292
|
+
const ps = track.params;
|
14293
|
+
const units = [track.vps, track.sps, track.pps];
|
14294
|
+
const NALuLengthSize = 4;
|
14295
|
+
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]);
|
14296
|
+
|
14297
|
+
// compute hvcC size in bytes
|
14298
|
+
let length = config.length;
|
14299
|
+
for (let i = 0; i < units.length; i += 1) {
|
14300
|
+
length += 3;
|
14301
|
+
for (let j = 0; j < units[i].length; j += 1) {
|
14302
|
+
length += 2 + units[i][j].length;
|
14303
|
+
}
|
14304
|
+
}
|
14305
|
+
const hvcC = new Uint8Array(length);
|
14306
|
+
hvcC.set(config, 0);
|
14307
|
+
length = config.length;
|
14308
|
+
// append parameter set units: one vps, one or more sps and pps
|
14309
|
+
const iMax = units.length - 1;
|
14310
|
+
for (let i = 0; i < units.length; i += 1) {
|
14311
|
+
hvcC.set(new Uint8Array([32 + i | (i === iMax ? 128 : 0), 0x00, units[i].length]), length);
|
14312
|
+
length += 3;
|
14313
|
+
for (let j = 0; j < units[i].length; j += 1) {
|
14314
|
+
hvcC.set(new Uint8Array([units[i][j].length >> 8, units[i][j].length & 255]), length);
|
14315
|
+
length += 2;
|
14316
|
+
hvcC.set(units[i][j], length);
|
14317
|
+
length += units[i][j].length;
|
14318
|
+
}
|
14319
|
+
}
|
14320
|
+
const hvcc = MP4.box(MP4.types.hvcC, hvcC);
|
14321
|
+
const width = track.width;
|
14322
|
+
const height = track.height;
|
14323
|
+
const hSpacing = track.pixelRatio[0];
|
14324
|
+
const vSpacing = track.pixelRatio[1];
|
14325
|
+
return MP4.box(MP4.types.hvc1, new Uint8Array([0x00, 0x00, 0x00,
|
14326
|
+
// reserved
|
14327
|
+
0x00, 0x00, 0x00,
|
14328
|
+
// reserved
|
14329
|
+
0x00, 0x01,
|
14330
|
+
// data_reference_index
|
14331
|
+
0x00, 0x00,
|
14332
|
+
// pre_defined
|
14333
|
+
0x00, 0x00,
|
14334
|
+
// reserved
|
14335
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
14336
|
+
// pre_defined
|
14337
|
+
width >> 8 & 0xff, width & 0xff,
|
14338
|
+
// width
|
14339
|
+
height >> 8 & 0xff, height & 0xff,
|
14340
|
+
// height
|
14341
|
+
0x00, 0x48, 0x00, 0x00,
|
14342
|
+
// horizresolution
|
14343
|
+
0x00, 0x48, 0x00, 0x00,
|
14344
|
+
// vertresolution
|
14345
|
+
0x00, 0x00, 0x00, 0x00,
|
14346
|
+
// reserved
|
14347
|
+
0x00, 0x01,
|
14348
|
+
// frame_count
|
14349
|
+
0x12, 0x64, 0x61, 0x69, 0x6c,
|
14350
|
+
// dailymotion/hls.js
|
14351
|
+
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,
|
14352
|
+
// compressorname
|
14353
|
+
0x00, 0x18,
|
14354
|
+
// depth = 24
|
14355
|
+
0x11, 0x11]),
|
14356
|
+
// pre_defined = -1
|
14357
|
+
hvcc, MP4.box(MP4.types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80,
|
14358
|
+
// bufferSizeDB
|
14359
|
+
0x00, 0x2d, 0xc6, 0xc0,
|
14360
|
+
// maxBitrate
|
14361
|
+
0x00, 0x2d, 0xc6, 0xc0])),
|
14362
|
+
// avgBitrate
|
14363
|
+
MP4.box(MP4.types.pasp, new Uint8Array([hSpacing >> 24,
|
14364
|
+
// hSpacing
|
14365
|
+
hSpacing >> 16 & 0xff, hSpacing >> 8 & 0xff, hSpacing & 0xff, vSpacing >> 24,
|
14366
|
+
// vSpacing
|
14367
|
+
vSpacing >> 16 & 0xff, vSpacing >> 8 & 0xff, vSpacing & 0xff])));
|
14368
|
+
}
|
13481
14369
|
}
|
13482
14370
|
MP4.types = void 0;
|
13483
14371
|
MP4.HDLR_TYPES = void 0;
|
@@ -13859,9 +14747,9 @@ class MP4Remuxer {
|
|
13859
14747
|
const foundOverlap = delta < -1;
|
13860
14748
|
if (foundHole || foundOverlap) {
|
13861
14749
|
if (foundHole) {
|
13862
|
-
logger.warn(
|
14750
|
+
logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
|
13863
14751
|
} else {
|
13864
|
-
logger.warn(
|
14752
|
+
logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
|
13865
14753
|
}
|
13866
14754
|
if (!foundOverlap || nextAvcDts >= inputSamples[0].pts || chromeVersion) {
|
13867
14755
|
firstDTS = nextAvcDts;
|
@@ -13870,12 +14758,24 @@ class MP4Remuxer {
|
|
13870
14758
|
inputSamples[0].dts = firstDTS;
|
13871
14759
|
inputSamples[0].pts = firstPTS;
|
13872
14760
|
} else {
|
14761
|
+
let isPTSOrderRetained = true;
|
13873
14762
|
for (let i = 0; i < inputSamples.length; i++) {
|
13874
|
-
if (inputSamples[i].dts > firstPTS) {
|
14763
|
+
if (inputSamples[i].dts > firstPTS && isPTSOrderRetained) {
|
13875
14764
|
break;
|
13876
14765
|
}
|
14766
|
+
const prevPTS = inputSamples[i].pts;
|
13877
14767
|
inputSamples[i].dts -= delta;
|
13878
14768
|
inputSamples[i].pts -= delta;
|
14769
|
+
|
14770
|
+
// check to see if this sample's PTS order has changed
|
14771
|
+
// relative to the next one
|
14772
|
+
if (i < inputSamples.length - 1) {
|
14773
|
+
const nextSamplePTS = inputSamples[i + 1].pts;
|
14774
|
+
const currentSamplePTS = inputSamples[i].pts;
|
14775
|
+
const currentOrder = nextSamplePTS <= currentSamplePTS;
|
14776
|
+
const prevOrder = nextSamplePTS <= prevPTS;
|
14777
|
+
isPTSOrderRetained = currentOrder == prevOrder;
|
14778
|
+
}
|
13879
14779
|
}
|
13880
14780
|
}
|
13881
14781
|
logger.log(`Video: Initial PTS/DTS adjusted: ${toMsFromMpegTsClock(firstPTS, true)}/${toMsFromMpegTsClock(firstDTS, true)}, delta: ${toMsFromMpegTsClock(delta, true)} ms`);
|
@@ -14023,7 +14923,7 @@ class MP4Remuxer {
|
|
14023
14923
|
}
|
14024
14924
|
}
|
14025
14925
|
}
|
14026
|
-
// next AVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
|
14926
|
+
// next AVC/HEVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
|
14027
14927
|
mp4SampleDuration = stretchedLastFrame || !mp4SampleDuration ? averageSampleDuration : mp4SampleDuration;
|
14028
14928
|
this.nextAvcDts = nextAvcDts = lastDTS + mp4SampleDuration;
|
14029
14929
|
this.videoSampleDuration = mp4SampleDuration;
|
@@ -14156,7 +15056,7 @@ class MP4Remuxer {
|
|
14156
15056
|
logger.warn(`[mp4-remuxer]: Injecting ${missing} audio frame @ ${(nextPts / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`);
|
14157
15057
|
for (let j = 0; j < missing; j++) {
|
14158
15058
|
const newStamp = Math.max(nextPts, 0);
|
14159
|
-
let fillFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
|
15059
|
+
let fillFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
|
14160
15060
|
if (!fillFrame) {
|
14161
15061
|
logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.');
|
14162
15062
|
fillFrame = sample.unit.subarray();
|
@@ -14284,7 +15184,7 @@ class MP4Remuxer {
|
|
14284
15184
|
// samples count of this segment's duration
|
14285
15185
|
const nbSamples = Math.ceil((endDTS - startDTS) / frameDuration);
|
14286
15186
|
// silent frame
|
14287
|
-
const silentFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
|
15187
|
+
const silentFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
|
14288
15188
|
logger.warn('[mp4-remuxer]: remux empty Audio');
|
14289
15189
|
// Can't remux if we can't generate a silent frame...
|
14290
15190
|
if (!silentFrame) {
|
@@ -14678,13 +15578,15 @@ class Transmuxer {
|
|
14678
15578
|
initSegmentData
|
14679
15579
|
} = transmuxConfig;
|
14680
15580
|
const keyData = getEncryptionType(uintData, decryptdata);
|
14681
|
-
if (keyData && keyData.method
|
15581
|
+
if (keyData && isFullSegmentEncryption(keyData.method)) {
|
14682
15582
|
const decrypter = this.getDecrypter();
|
15583
|
+
const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
|
15584
|
+
|
14683
15585
|
// Software decryption is synchronous; webCrypto is not
|
14684
15586
|
if (decrypter.isSync()) {
|
14685
15587
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
14686
15588
|
// data is handled in the flush() call
|
14687
|
-
let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer);
|
15589
|
+
let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode);
|
14688
15590
|
// For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
|
14689
15591
|
const loadingParts = chunkMeta.part > -1;
|
14690
15592
|
if (loadingParts) {
|
@@ -14696,7 +15598,7 @@ class Transmuxer {
|
|
14696
15598
|
}
|
14697
15599
|
uintData = new Uint8Array(decryptedData);
|
14698
15600
|
} else {
|
14699
|
-
this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer).then(decryptedData => {
|
15601
|
+
this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode).then(decryptedData => {
|
14700
15602
|
// Calling push here is important; if flush() is called while this is still resolving, this ensures that
|
14701
15603
|
// the decrypted data has been transmuxed
|
14702
15604
|
const result = this.push(decryptedData, null, chunkMeta);
|
@@ -15350,14 +16252,7 @@ class TransmuxerInterface {
|
|
15350
16252
|
this.observer = new EventEmitter();
|
15351
16253
|
this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
|
15352
16254
|
this.observer.on(Events.ERROR, forwardMessage);
|
15353
|
-
const
|
15354
|
-
isTypeSupported: () => false
|
15355
|
-
};
|
15356
|
-
const m2tsTypeSupported = {
|
15357
|
-
mpeg: MediaSource.isTypeSupported('audio/mpeg'),
|
15358
|
-
mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
|
15359
|
-
ac3: MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
|
15360
|
-
};
|
16255
|
+
const m2tsTypeSupported = getM2TSSupportedAudioTypes(config.preferManagedMediaSource);
|
15361
16256
|
|
15362
16257
|
// navigator.vendor is not always available in Web Worker
|
15363
16258
|
// refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
|
@@ -15645,7 +16540,7 @@ const TICK_INTERVAL$2 = 100; // how often to tick in ms
|
|
15645
16540
|
|
15646
16541
|
class AudioStreamController extends BaseStreamController {
|
15647
16542
|
constructor(hls, fragmentTracker, keyLoader) {
|
15648
|
-
super(hls, fragmentTracker, keyLoader, '
|
16543
|
+
super(hls, fragmentTracker, keyLoader, 'audio-stream-controller', PlaylistLevelType.AUDIO);
|
15649
16544
|
this.videoBuffer = null;
|
15650
16545
|
this.videoTrackCC = -1;
|
15651
16546
|
this.waitingVideoCC = -1;
|
@@ -15657,27 +16552,24 @@ class AudioStreamController extends BaseStreamController {
|
|
15657
16552
|
this.flushing = false;
|
15658
16553
|
this.bufferFlushed = false;
|
15659
16554
|
this.cachedTrackLoadedData = null;
|
15660
|
-
this.
|
16555
|
+
this.registerListeners();
|
15661
16556
|
}
|
15662
16557
|
onHandlerDestroying() {
|
15663
|
-
this.
|
16558
|
+
this.unregisterListeners();
|
15664
16559
|
super.onHandlerDestroying();
|
15665
16560
|
this.mainDetails = null;
|
15666
16561
|
this.bufferedTrack = null;
|
15667
16562
|
this.switchingTrack = null;
|
15668
16563
|
}
|
15669
|
-
|
16564
|
+
registerListeners() {
|
16565
|
+
super.registerListeners();
|
15670
16566
|
const {
|
15671
16567
|
hls
|
15672
16568
|
} = this;
|
15673
|
-
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
15674
|
-
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
15675
|
-
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
15676
16569
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
15677
16570
|
hls.on(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
|
15678
16571
|
hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
15679
16572
|
hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
|
15680
|
-
hls.on(Events.ERROR, this.onError, this);
|
15681
16573
|
hls.on(Events.BUFFER_RESET, this.onBufferReset, this);
|
15682
16574
|
hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
15683
16575
|
hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
@@ -15685,18 +16577,18 @@ class AudioStreamController extends BaseStreamController {
|
|
15685
16577
|
hls.on(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);
|
15686
16578
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
15687
16579
|
}
|
15688
|
-
|
16580
|
+
unregisterListeners() {
|
15689
16581
|
const {
|
15690
16582
|
hls
|
15691
16583
|
} = this;
|
15692
|
-
hls
|
15693
|
-
|
15694
|
-
|
16584
|
+
if (!hls) {
|
16585
|
+
return;
|
16586
|
+
}
|
16587
|
+
super.unregisterListeners();
|
15695
16588
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
15696
16589
|
hls.off(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
|
15697
16590
|
hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
15698
16591
|
hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
|
15699
|
-
hls.off(Events.ERROR, this.onError, this);
|
15700
16592
|
hls.off(Events.BUFFER_RESET, this.onBufferReset, this);
|
15701
16593
|
hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
15702
16594
|
hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
@@ -15865,12 +16757,13 @@ class AudioStreamController extends BaseStreamController {
|
|
15865
16757
|
} = this;
|
15866
16758
|
const config = hls.config;
|
15867
16759
|
|
15868
|
-
// 1. if
|
16760
|
+
// 1. if buffering is suspended
|
16761
|
+
// 2. if video not attached AND
|
15869
16762
|
// start fragment already requested OR start frag prefetch not enabled
|
15870
|
-
//
|
16763
|
+
// 3. if tracks or track not loaded and selected
|
15871
16764
|
// then exit loop
|
15872
16765
|
// => if media not attached but start frag prefetch is enabled and start frag not requested yet, we will not exit loop
|
15873
|
-
if (!media && (this.startFragRequested || !config.startFragPrefetch) || !(levels != null && levels[trackId])) {
|
16766
|
+
if (!this.buffering || !media && (this.startFragRequested || !config.startFragPrefetch) || !(levels != null && levels[trackId])) {
|
15874
16767
|
return;
|
15875
16768
|
}
|
15876
16769
|
const levelInfo = levels[trackId];
|
@@ -16428,7 +17321,7 @@ class AudioStreamController extends BaseStreamController {
|
|
16428
17321
|
|
16429
17322
|
class AudioTrackController extends BasePlaylistController {
|
16430
17323
|
constructor(hls) {
|
16431
|
-
super(hls, '
|
17324
|
+
super(hls, 'audio-track-controller');
|
16432
17325
|
this.tracks = [];
|
16433
17326
|
this.groupIds = null;
|
16434
17327
|
this.tracksInGroup = [];
|
@@ -16747,26 +17640,23 @@ const TICK_INTERVAL$1 = 500; // how often to tick in ms
|
|
16747
17640
|
|
16748
17641
|
class SubtitleStreamController extends BaseStreamController {
|
16749
17642
|
constructor(hls, fragmentTracker, keyLoader) {
|
16750
|
-
super(hls, fragmentTracker, keyLoader, '
|
17643
|
+
super(hls, fragmentTracker, keyLoader, 'subtitle-stream-controller', PlaylistLevelType.SUBTITLE);
|
16751
17644
|
this.currentTrackId = -1;
|
16752
17645
|
this.tracksBuffered = [];
|
16753
17646
|
this.mainDetails = null;
|
16754
|
-
this.
|
17647
|
+
this.registerListeners();
|
16755
17648
|
}
|
16756
17649
|
onHandlerDestroying() {
|
16757
|
-
this.
|
17650
|
+
this.unregisterListeners();
|
16758
17651
|
super.onHandlerDestroying();
|
16759
17652
|
this.mainDetails = null;
|
16760
17653
|
}
|
16761
|
-
|
17654
|
+
registerListeners() {
|
17655
|
+
super.registerListeners();
|
16762
17656
|
const {
|
16763
17657
|
hls
|
16764
17658
|
} = this;
|
16765
|
-
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
16766
|
-
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
16767
|
-
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
16768
17659
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
16769
|
-
hls.on(Events.ERROR, this.onError, this);
|
16770
17660
|
hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
|
16771
17661
|
hls.on(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
|
16772
17662
|
hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
|
@@ -16774,15 +17664,12 @@ class SubtitleStreamController extends BaseStreamController {
|
|
16774
17664
|
hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
16775
17665
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
16776
17666
|
}
|
16777
|
-
|
17667
|
+
unregisterListeners() {
|
17668
|
+
super.unregisterListeners();
|
16778
17669
|
const {
|
16779
17670
|
hls
|
16780
17671
|
} = this;
|
16781
|
-
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
16782
|
-
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
16783
|
-
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
16784
17672
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
16785
|
-
hls.off(Events.ERROR, this.onError, this);
|
16786
17673
|
hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
|
16787
17674
|
hls.off(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
|
16788
17675
|
hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
|
@@ -17009,10 +17896,10 @@ class SubtitleStreamController extends BaseStreamController {
|
|
17009
17896
|
return;
|
17010
17897
|
}
|
17011
17898
|
// check to see if the payload needs to be decrypted
|
17012
|
-
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method
|
17899
|
+
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
|
17013
17900
|
const startTime = performance.now();
|
17014
17901
|
// decrypt the subtitles
|
17015
|
-
this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
|
17902
|
+
this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch(err => {
|
17016
17903
|
hls.trigger(Events.ERROR, {
|
17017
17904
|
type: ErrorTypes.MEDIA_ERROR,
|
17018
17905
|
details: ErrorDetails.FRAG_DECRYPT_ERROR,
|
@@ -17146,7 +18033,7 @@ class BufferableInstance {
|
|
17146
18033
|
|
17147
18034
|
class SubtitleTrackController extends BasePlaylistController {
|
17148
18035
|
constructor(hls) {
|
17149
|
-
super(hls, '
|
18036
|
+
super(hls, 'subtitle-track-controller');
|
17150
18037
|
this.media = null;
|
17151
18038
|
this.tracks = [];
|
17152
18039
|
this.groupIds = null;
|
@@ -17155,10 +18042,10 @@ class SubtitleTrackController extends BasePlaylistController {
|
|
17155
18042
|
this.currentTrack = null;
|
17156
18043
|
this.selectDefaultTrack = true;
|
17157
18044
|
this.queuedDefaultTrack = -1;
|
17158
|
-
this.asyncPollTrackChange = () => this.pollTrackChange(0);
|
17159
18045
|
this.useTextTrackPolling = false;
|
17160
18046
|
this.subtitlePollingInterval = -1;
|
17161
18047
|
this._subtitleDisplay = true;
|
18048
|
+
this.asyncPollTrackChange = () => this.pollTrackChange(0);
|
17162
18049
|
this.onTextTracksChanged = () => {
|
17163
18050
|
if (!this.useTextTrackPolling) {
|
17164
18051
|
self.clearInterval(this.subtitlePollingInterval);
|
@@ -17192,6 +18079,7 @@ class SubtitleTrackController extends BasePlaylistController {
|
|
17192
18079
|
this.tracks.length = 0;
|
17193
18080
|
this.tracksInGroup.length = 0;
|
17194
18081
|
this.currentTrack = null;
|
18082
|
+
// @ts-ignore
|
17195
18083
|
this.onTextTracksChanged = this.asyncPollTrackChange = null;
|
17196
18084
|
super.destroy();
|
17197
18085
|
}
|
@@ -17652,8 +18540,9 @@ class BufferOperationQueue {
|
|
17652
18540
|
}
|
17653
18541
|
|
17654
18542
|
const VIDEO_CODEC_PROFILE_REPLACE = /(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\.[^.,]+)+/;
|
17655
|
-
class BufferController {
|
18543
|
+
class BufferController extends Logger {
|
17656
18544
|
constructor(hls) {
|
18545
|
+
super('buffer-controller', hls.logger);
|
17657
18546
|
// The level details used to determine duration, target-duration and live
|
17658
18547
|
this.details = null;
|
17659
18548
|
// cache the self generated object url to detect hijack of video tag
|
@@ -17683,9 +18572,6 @@ class BufferController {
|
|
17683
18572
|
this.tracks = {};
|
17684
18573
|
this.pendingTracks = {};
|
17685
18574
|
this.sourceBuffer = void 0;
|
17686
|
-
this.log = void 0;
|
17687
|
-
this.warn = void 0;
|
17688
|
-
this.error = void 0;
|
17689
18575
|
this._onEndStreaming = event => {
|
17690
18576
|
if (!this.hls) {
|
17691
18577
|
return;
|
@@ -17731,15 +18617,11 @@ class BufferController {
|
|
17731
18617
|
_objectUrl
|
17732
18618
|
} = this;
|
17733
18619
|
if (mediaSrc !== _objectUrl) {
|
17734
|
-
|
18620
|
+
this.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
|
17735
18621
|
}
|
17736
18622
|
};
|
17737
18623
|
this.hls = hls;
|
17738
|
-
const logPrefix = '[buffer-controller]';
|
17739
18624
|
this.appendSource = hls.config.preferManagedMediaSource;
|
17740
|
-
this.log = logger.log.bind(logger, logPrefix);
|
17741
|
-
this.warn = logger.warn.bind(logger, logPrefix);
|
17742
|
-
this.error = logger.error.bind(logger, logPrefix);
|
17743
18625
|
this._initSourceBuffer();
|
17744
18626
|
this.registerListeners();
|
17745
18627
|
}
|
@@ -17752,6 +18634,12 @@ class BufferController {
|
|
17752
18634
|
this.lastMpegAudioChunk = null;
|
17753
18635
|
// @ts-ignore
|
17754
18636
|
this.hls = null;
|
18637
|
+
// @ts-ignore
|
18638
|
+
this._onMediaSourceOpen = this._onMediaSourceClose = null;
|
18639
|
+
// @ts-ignore
|
18640
|
+
this._onMediaSourceEnded = null;
|
18641
|
+
// @ts-ignore
|
18642
|
+
this._onStartStreaming = this._onEndStreaming = null;
|
17755
18643
|
}
|
17756
18644
|
registerListeners() {
|
17757
18645
|
const {
|
@@ -17914,6 +18802,7 @@ class BufferController {
|
|
17914
18802
|
this.resetBuffer(type);
|
17915
18803
|
});
|
17916
18804
|
this._initSourceBuffer();
|
18805
|
+
this.hls.resumeBuffering();
|
17917
18806
|
}
|
17918
18807
|
resetBuffer(type) {
|
17919
18808
|
const sb = this.sourceBuffer[type];
|
@@ -21016,14 +21905,12 @@ class TimelineController {
|
|
21016
21905
|
this.cea608Parser1 = this.cea608Parser2 = undefined;
|
21017
21906
|
}
|
21018
21907
|
initCea608Parsers() {
|
21019
|
-
|
21020
|
-
|
21021
|
-
|
21022
|
-
|
21023
|
-
|
21024
|
-
|
21025
|
-
this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
|
21026
|
-
}
|
21908
|
+
const channel1 = new OutputFilter(this, 'textTrack1');
|
21909
|
+
const channel2 = new OutputFilter(this, 'textTrack2');
|
21910
|
+
const channel3 = new OutputFilter(this, 'textTrack3');
|
21911
|
+
const channel4 = new OutputFilter(this, 'textTrack4');
|
21912
|
+
this.cea608Parser1 = new Cea608Parser(1, channel1, channel2);
|
21913
|
+
this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
|
21027
21914
|
}
|
21028
21915
|
addCues(trackName, startTime, endTime, screen, cueRanges) {
|
21029
21916
|
// skip cues which overlap more than 50% with previously parsed time ranges
|
@@ -21261,7 +22148,7 @@ class TimelineController {
|
|
21261
22148
|
if (inUseTracks != null && inUseTracks.length) {
|
21262
22149
|
const unusedTextTracks = inUseTracks.filter(t => t !== null).map(t => t.label);
|
21263
22150
|
if (unusedTextTracks.length) {
|
21264
|
-
logger.warn(`Media element contains unused subtitle tracks: ${unusedTextTracks.join(', ')}. Replace media element for each source to clear TextTracks and captions menu.`);
|
22151
|
+
this.hls.logger.warn(`Media element contains unused subtitle tracks: ${unusedTextTracks.join(', ')}. Replace media element for each source to clear TextTracks and captions menu.`);
|
21265
22152
|
}
|
21266
22153
|
}
|
21267
22154
|
} else if (this.tracks.length) {
|
@@ -21306,26 +22193,23 @@ class TimelineController {
|
|
21306
22193
|
return level == null ? void 0 : level.attrs['CLOSED-CAPTIONS'];
|
21307
22194
|
}
|
21308
22195
|
onFragLoading(event, data) {
|
21309
|
-
this.initCea608Parsers();
|
21310
|
-
const {
|
21311
|
-
cea608Parser1,
|
21312
|
-
cea608Parser2,
|
21313
|
-
lastCc,
|
21314
|
-
lastSn,
|
21315
|
-
lastPartIndex
|
21316
|
-
} = this;
|
21317
|
-
if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
|
21318
|
-
return;
|
21319
|
-
}
|
21320
22196
|
// if this frag isn't contiguous, clear the parser so cues with bad start/end times aren't added to the textTrack
|
21321
|
-
if (data.frag.type === PlaylistLevelType.MAIN) {
|
22197
|
+
if (this.enabled && data.frag.type === PlaylistLevelType.MAIN) {
|
21322
22198
|
var _data$part$index, _data$part;
|
22199
|
+
const {
|
22200
|
+
cea608Parser1,
|
22201
|
+
cea608Parser2,
|
22202
|
+
lastSn
|
22203
|
+
} = this;
|
22204
|
+
if (!cea608Parser1 || !cea608Parser2) {
|
22205
|
+
return;
|
22206
|
+
}
|
21323
22207
|
const {
|
21324
22208
|
cc,
|
21325
22209
|
sn
|
21326
22210
|
} = data.frag;
|
21327
|
-
const partIndex = (_data$part$index =
|
21328
|
-
if (!(sn === lastSn + 1 || sn === lastSn && partIndex === lastPartIndex + 1 || cc === lastCc)) {
|
22211
|
+
const partIndex = (_data$part$index = (_data$part = data.part) == null ? void 0 : _data$part.index) != null ? _data$part$index : -1;
|
22212
|
+
if (!(sn === lastSn + 1 || sn === lastSn && partIndex === this.lastPartIndex + 1 || cc === this.lastCc)) {
|
21329
22213
|
cea608Parser1.reset();
|
21330
22214
|
cea608Parser2.reset();
|
21331
22215
|
}
|
@@ -21382,7 +22266,7 @@ class TimelineController {
|
|
21382
22266
|
frag: frag
|
21383
22267
|
});
|
21384
22268
|
}, error => {
|
21385
|
-
logger.log(`Failed to parse IMSC1: ${error}`);
|
22269
|
+
hls.logger.log(`Failed to parse IMSC1: ${error}`);
|
21386
22270
|
hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
|
21387
22271
|
success: false,
|
21388
22272
|
frag: frag,
|
@@ -21423,7 +22307,7 @@ class TimelineController {
|
|
21423
22307
|
this._fallbackToIMSC1(frag, payload);
|
21424
22308
|
}
|
21425
22309
|
// Something went wrong while parsing. Trigger event with success false.
|
21426
|
-
logger.log(`Failed to parse VTT cue: ${error}`);
|
22310
|
+
hls.logger.log(`Failed to parse VTT cue: ${error}`);
|
21427
22311
|
if (missingInitPTS && maxAvCC > frag.cc) {
|
21428
22312
|
return;
|
21429
22313
|
}
|
@@ -21484,12 +22368,7 @@ class TimelineController {
|
|
21484
22368
|
this.captionsTracks = {};
|
21485
22369
|
}
|
21486
22370
|
onFragParsingUserdata(event, data) {
|
21487
|
-
this.
|
21488
|
-
const {
|
21489
|
-
cea608Parser1,
|
21490
|
-
cea608Parser2
|
21491
|
-
} = this;
|
21492
|
-
if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
|
22371
|
+
if (!this.enabled || !this.config.enableCEA708Captions) {
|
21493
22372
|
return;
|
21494
22373
|
}
|
21495
22374
|
const {
|
@@ -21504,9 +22383,12 @@ class TimelineController {
|
|
21504
22383
|
for (let i = 0; i < samples.length; i++) {
|
21505
22384
|
const ccBytes = samples[i].bytes;
|
21506
22385
|
if (ccBytes) {
|
22386
|
+
if (!this.cea608Parser1) {
|
22387
|
+
this.initCea608Parsers();
|
22388
|
+
}
|
21507
22389
|
const ccdatas = this.extractCea608Data(ccBytes);
|
21508
|
-
cea608Parser1.addData(samples[i].pts, ccdatas[0]);
|
21509
|
-
cea608Parser2.addData(samples[i].pts, ccdatas[1]);
|
22390
|
+
this.cea608Parser1.addData(samples[i].pts, ccdatas[0]);
|
22391
|
+
this.cea608Parser2.addData(samples[i].pts, ccdatas[1]);
|
21510
22392
|
}
|
21511
22393
|
}
|
21512
22394
|
}
|
@@ -21702,7 +22584,7 @@ class CapLevelController {
|
|
21702
22584
|
const hls = this.hls;
|
21703
22585
|
const maxLevel = this.getMaxLevel(levels.length - 1);
|
21704
22586
|
if (maxLevel !== this.autoLevelCapping) {
|
21705
|
-
logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
|
22587
|
+
hls.logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
|
21706
22588
|
}
|
21707
22589
|
hls.autoLevelCapping = maxLevel;
|
21708
22590
|
if (hls.autoLevelCapping > this.autoLevelCapping && this.streamController) {
|
@@ -21880,10 +22762,10 @@ class FPSController {
|
|
21880
22762
|
totalDroppedFrames: droppedFrames
|
21881
22763
|
});
|
21882
22764
|
if (droppedFPS > 0) {
|
21883
|
-
// logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
|
22765
|
+
// hls.logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
|
21884
22766
|
if (currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
|
21885
22767
|
let currentLevel = hls.currentLevel;
|
21886
|
-
logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
|
22768
|
+
hls.logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
|
21887
22769
|
if (currentLevel > 0 && (hls.autoLevelCapping === -1 || hls.autoLevelCapping >= currentLevel)) {
|
21888
22770
|
currentLevel = currentLevel - 1;
|
21889
22771
|
hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, {
|
@@ -21915,7 +22797,6 @@ class FPSController {
|
|
21915
22797
|
}
|
21916
22798
|
}
|
21917
22799
|
|
21918
|
-
const LOGGER_PREFIX = '[eme]';
|
21919
22800
|
/**
|
21920
22801
|
* Controller to deal with encrypted media extensions (EME)
|
21921
22802
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/Encrypted_Media_Extensions_API
|
@@ -21923,8 +22804,9 @@ const LOGGER_PREFIX = '[eme]';
|
|
21923
22804
|
* @class
|
21924
22805
|
* @constructor
|
21925
22806
|
*/
|
21926
|
-
class EMEController {
|
22807
|
+
class EMEController extends Logger {
|
21927
22808
|
constructor(hls) {
|
22809
|
+
super('eme', hls.logger);
|
21928
22810
|
this.hls = void 0;
|
21929
22811
|
this.config = void 0;
|
21930
22812
|
this.media = null;
|
@@ -21934,12 +22816,100 @@ class EMEController {
|
|
21934
22816
|
this.mediaKeySessions = [];
|
21935
22817
|
this.keyIdToKeySessionPromise = {};
|
21936
22818
|
this.setMediaKeysQueue = EMEController.CDMCleanupPromise ? [EMEController.CDMCleanupPromise] : [];
|
21937
|
-
this.onMediaEncrypted =
|
21938
|
-
|
21939
|
-
|
21940
|
-
|
21941
|
-
|
21942
|
-
|
22819
|
+
this.onMediaEncrypted = event => {
|
22820
|
+
const {
|
22821
|
+
initDataType,
|
22822
|
+
initData
|
22823
|
+
} = event;
|
22824
|
+
this.debug(`"${event.type}" event: init data type: "${initDataType}"`);
|
22825
|
+
|
22826
|
+
// Ignore event when initData is null
|
22827
|
+
if (initData === null) {
|
22828
|
+
return;
|
22829
|
+
}
|
22830
|
+
let keyId;
|
22831
|
+
let keySystemDomain;
|
22832
|
+
if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) {
|
22833
|
+
// Match sinf keyId to playlist skd://keyId=
|
22834
|
+
const json = bin2str(new Uint8Array(initData));
|
22835
|
+
try {
|
22836
|
+
const sinf = base64Decode(JSON.parse(json).sinf);
|
22837
|
+
const tenc = parseSinf(new Uint8Array(sinf));
|
22838
|
+
if (!tenc) {
|
22839
|
+
return;
|
22840
|
+
}
|
22841
|
+
keyId = tenc.subarray(8, 24);
|
22842
|
+
keySystemDomain = KeySystems.FAIRPLAY;
|
22843
|
+
} catch (error) {
|
22844
|
+
this.warn('Failed to parse sinf "encrypted" event message initData');
|
22845
|
+
return;
|
22846
|
+
}
|
22847
|
+
} else {
|
22848
|
+
// Support clear-lead key-session creation (otherwise depend on playlist keys)
|
22849
|
+
const psshInfo = parsePssh(initData);
|
22850
|
+
if (psshInfo === null) {
|
22851
|
+
return;
|
22852
|
+
}
|
22853
|
+
if (psshInfo.version === 0 && psshInfo.systemId === KeySystemIds.WIDEVINE && psshInfo.data) {
|
22854
|
+
keyId = psshInfo.data.subarray(8, 24);
|
22855
|
+
}
|
22856
|
+
keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
|
22857
|
+
}
|
22858
|
+
if (!keySystemDomain || !keyId) {
|
22859
|
+
return;
|
22860
|
+
}
|
22861
|
+
const keyIdHex = Hex.hexDump(keyId);
|
22862
|
+
const {
|
22863
|
+
keyIdToKeySessionPromise,
|
22864
|
+
mediaKeySessions
|
22865
|
+
} = this;
|
22866
|
+
let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
|
22867
|
+
for (let i = 0; i < mediaKeySessions.length; i++) {
|
22868
|
+
// Match playlist key
|
22869
|
+
const keyContext = mediaKeySessions[i];
|
22870
|
+
const decryptdata = keyContext.decryptdata;
|
22871
|
+
if (decryptdata.pssh || !decryptdata.keyId) {
|
22872
|
+
continue;
|
22873
|
+
}
|
22874
|
+
const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
|
22875
|
+
if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
|
22876
|
+
keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
|
22877
|
+
delete keyIdToKeySessionPromise[oldKeyIdHex];
|
22878
|
+
decryptdata.pssh = new Uint8Array(initData);
|
22879
|
+
decryptdata.keyId = keyId;
|
22880
|
+
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
|
22881
|
+
return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
|
22882
|
+
});
|
22883
|
+
break;
|
22884
|
+
}
|
22885
|
+
}
|
22886
|
+
if (!keySessionContextPromise) {
|
22887
|
+
// Clear-lead key (not encountered in playlist)
|
22888
|
+
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
|
22889
|
+
keySystem,
|
22890
|
+
mediaKeys
|
22891
|
+
}) => {
|
22892
|
+
var _keySystemToKeySystem;
|
22893
|
+
this.throwIfDestroyed();
|
22894
|
+
const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
|
22895
|
+
decryptdata.pssh = new Uint8Array(initData);
|
22896
|
+
decryptdata.keyId = keyId;
|
22897
|
+
return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
|
22898
|
+
this.throwIfDestroyed();
|
22899
|
+
const keySessionContext = this.createMediaKeySessionContext({
|
22900
|
+
decryptdata,
|
22901
|
+
keySystem,
|
22902
|
+
mediaKeys
|
22903
|
+
});
|
22904
|
+
return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
|
22905
|
+
});
|
22906
|
+
});
|
22907
|
+
}
|
22908
|
+
keySessionContextPromise.catch(error => this.handleError(error));
|
22909
|
+
};
|
22910
|
+
this.onWaitingForKey = event => {
|
22911
|
+
this.log(`"${event.type}" event`);
|
22912
|
+
};
|
21943
22913
|
this.hls = hls;
|
21944
22914
|
this.config = hls.config;
|
21945
22915
|
this.registerListeners();
|
@@ -21953,9 +22923,9 @@ class EMEController {
|
|
21953
22923
|
config.licenseXhrSetup = config.licenseResponseCallback = undefined;
|
21954
22924
|
config.drmSystems = config.drmSystemOptions = {};
|
21955
22925
|
// @ts-ignore
|
21956
|
-
this.hls = this.
|
22926
|
+
this.hls = this.config = this.keyIdToKeySessionPromise = null;
|
21957
22927
|
// @ts-ignore
|
21958
|
-
this.
|
22928
|
+
this.onMediaEncrypted = this.onWaitingForKey = null;
|
21959
22929
|
}
|
21960
22930
|
registerListeners() {
|
21961
22931
|
this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
@@ -22219,100 +23189,6 @@ class EMEController {
|
|
22219
23189
|
}
|
22220
23190
|
return this.attemptKeySystemAccess(keySystemsToAttempt);
|
22221
23191
|
}
|
22222
|
-
_onMediaEncrypted(event) {
|
22223
|
-
const {
|
22224
|
-
initDataType,
|
22225
|
-
initData
|
22226
|
-
} = event;
|
22227
|
-
this.debug(`"${event.type}" event: init data type: "${initDataType}"`);
|
22228
|
-
|
22229
|
-
// Ignore event when initData is null
|
22230
|
-
if (initData === null) {
|
22231
|
-
return;
|
22232
|
-
}
|
22233
|
-
let keyId;
|
22234
|
-
let keySystemDomain;
|
22235
|
-
if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) {
|
22236
|
-
// Match sinf keyId to playlist skd://keyId=
|
22237
|
-
const json = bin2str(new Uint8Array(initData));
|
22238
|
-
try {
|
22239
|
-
const sinf = base64Decode(JSON.parse(json).sinf);
|
22240
|
-
const tenc = parseSinf(new Uint8Array(sinf));
|
22241
|
-
if (!tenc) {
|
22242
|
-
return;
|
22243
|
-
}
|
22244
|
-
keyId = tenc.subarray(8, 24);
|
22245
|
-
keySystemDomain = KeySystems.FAIRPLAY;
|
22246
|
-
} catch (error) {
|
22247
|
-
this.warn('Failed to parse sinf "encrypted" event message initData');
|
22248
|
-
return;
|
22249
|
-
}
|
22250
|
-
} else {
|
22251
|
-
// Support clear-lead key-session creation (otherwise depend on playlist keys)
|
22252
|
-
const psshInfo = parsePssh(initData);
|
22253
|
-
if (psshInfo === null) {
|
22254
|
-
return;
|
22255
|
-
}
|
22256
|
-
if (psshInfo.version === 0 && psshInfo.systemId === KeySystemIds.WIDEVINE && psshInfo.data) {
|
22257
|
-
keyId = psshInfo.data.subarray(8, 24);
|
22258
|
-
}
|
22259
|
-
keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
|
22260
|
-
}
|
22261
|
-
if (!keySystemDomain || !keyId) {
|
22262
|
-
return;
|
22263
|
-
}
|
22264
|
-
const keyIdHex = Hex.hexDump(keyId);
|
22265
|
-
const {
|
22266
|
-
keyIdToKeySessionPromise,
|
22267
|
-
mediaKeySessions
|
22268
|
-
} = this;
|
22269
|
-
let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
|
22270
|
-
for (let i = 0; i < mediaKeySessions.length; i++) {
|
22271
|
-
// Match playlist key
|
22272
|
-
const keyContext = mediaKeySessions[i];
|
22273
|
-
const decryptdata = keyContext.decryptdata;
|
22274
|
-
if (decryptdata.pssh || !decryptdata.keyId) {
|
22275
|
-
continue;
|
22276
|
-
}
|
22277
|
-
const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
|
22278
|
-
if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
|
22279
|
-
keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
|
22280
|
-
delete keyIdToKeySessionPromise[oldKeyIdHex];
|
22281
|
-
decryptdata.pssh = new Uint8Array(initData);
|
22282
|
-
decryptdata.keyId = keyId;
|
22283
|
-
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
|
22284
|
-
return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
|
22285
|
-
});
|
22286
|
-
break;
|
22287
|
-
}
|
22288
|
-
}
|
22289
|
-
if (!keySessionContextPromise) {
|
22290
|
-
// Clear-lead key (not encountered in playlist)
|
22291
|
-
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
|
22292
|
-
keySystem,
|
22293
|
-
mediaKeys
|
22294
|
-
}) => {
|
22295
|
-
var _keySystemToKeySystem;
|
22296
|
-
this.throwIfDestroyed();
|
22297
|
-
const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
|
22298
|
-
decryptdata.pssh = new Uint8Array(initData);
|
22299
|
-
decryptdata.keyId = keyId;
|
22300
|
-
return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
|
22301
|
-
this.throwIfDestroyed();
|
22302
|
-
const keySessionContext = this.createMediaKeySessionContext({
|
22303
|
-
decryptdata,
|
22304
|
-
keySystem,
|
22305
|
-
mediaKeys
|
22306
|
-
});
|
22307
|
-
return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
|
22308
|
-
});
|
22309
|
-
});
|
22310
|
-
}
|
22311
|
-
keySessionContextPromise.catch(error => this.handleError(error));
|
22312
|
-
}
|
22313
|
-
_onWaitingForKey(event) {
|
22314
|
-
this.log(`"${event.type}" event`);
|
22315
|
-
}
|
22316
23192
|
attemptSetMediaKeys(keySystem, mediaKeys) {
|
22317
23193
|
const queue = this.setMediaKeysQueue.slice();
|
22318
23194
|
this.log(`Setting media-keys for "${keySystem}"`);
|
@@ -22905,20 +23781,6 @@ class SfItem {
|
|
22905
23781
|
}
|
22906
23782
|
}
|
22907
23783
|
|
22908
|
-
/**
|
22909
|
-
* A class to represent structured field tokens when `Symbol` is not available.
|
22910
|
-
*
|
22911
|
-
* @group Structured Field
|
22912
|
-
*
|
22913
|
-
* @beta
|
22914
|
-
*/
|
22915
|
-
class SfToken {
|
22916
|
-
constructor(description) {
|
22917
|
-
this.description = void 0;
|
22918
|
-
this.description = description;
|
22919
|
-
}
|
22920
|
-
}
|
22921
|
-
|
22922
23784
|
const DICT = 'Dict';
|
22923
23785
|
|
22924
23786
|
function format(value) {
|
@@ -22942,29 +23804,27 @@ function throwError(action, src, type, cause) {
|
|
22942
23804
|
});
|
22943
23805
|
}
|
22944
23806
|
|
22945
|
-
|
22946
|
-
|
22947
|
-
const BOOLEAN = 'Boolean';
|
22948
|
-
|
22949
|
-
const BYTES = 'Byte Sequence';
|
22950
|
-
|
22951
|
-
const DECIMAL = 'Decimal';
|
22952
|
-
|
22953
|
-
const INTEGER = 'Integer';
|
22954
|
-
|
22955
|
-
function isInvalidInt(value) {
|
22956
|
-
return value < -999999999999999 || 999999999999999 < value;
|
23807
|
+
function serializeError(src, type, cause) {
|
23808
|
+
return throwError('serialize', src, type, cause);
|
22957
23809
|
}
|
22958
23810
|
|
22959
|
-
|
22960
|
-
|
22961
|
-
|
23811
|
+
/**
|
23812
|
+
* A class to represent structured field tokens when `Symbol` is not available.
|
23813
|
+
*
|
23814
|
+
* @group Structured Field
|
23815
|
+
*
|
23816
|
+
* @beta
|
23817
|
+
*/
|
23818
|
+
class SfToken {
|
23819
|
+
constructor(description) {
|
23820
|
+
this.description = void 0;
|
23821
|
+
this.description = description;
|
23822
|
+
}
|
23823
|
+
}
|
22962
23824
|
|
22963
|
-
const
|
23825
|
+
const BARE_ITEM = 'Bare Item';
|
22964
23826
|
|
22965
|
-
|
22966
|
-
return throwError('serialize', src, type, cause);
|
22967
|
-
}
|
23827
|
+
const BOOLEAN = 'Boolean';
|
22968
23828
|
|
22969
23829
|
// 4.1.9. Serializing a Boolean
|
22970
23830
|
//
|
@@ -23003,6 +23863,8 @@ function base64encode(binary) {
|
|
23003
23863
|
return btoa(String.fromCharCode(...binary));
|
23004
23864
|
}
|
23005
23865
|
|
23866
|
+
const BYTES = 'Byte Sequence';
|
23867
|
+
|
23006
23868
|
// 4.1.8. Serializing a Byte Sequence
|
23007
23869
|
//
|
23008
23870
|
// Given a Byte Sequence as input_bytes, return an ASCII string suitable
|
@@ -23034,6 +23896,12 @@ function serializeByteSequence(value) {
|
|
23034
23896
|
return `:${base64encode(value)}:`;
|
23035
23897
|
}
|
23036
23898
|
|
23899
|
+
const INTEGER = 'Integer';
|
23900
|
+
|
23901
|
+
function isInvalidInt(value) {
|
23902
|
+
return value < -999999999999999 || 999999999999999 < value;
|
23903
|
+
}
|
23904
|
+
|
23037
23905
|
// 4.1.4. Serializing an Integer
|
23038
23906
|
//
|
23039
23907
|
// Given an Integer as input_integer, return an ASCII string suitable
|
@@ -23099,6 +23967,8 @@ function roundToEven(value, precision) {
|
|
23099
23967
|
}
|
23100
23968
|
}
|
23101
23969
|
|
23970
|
+
const DECIMAL = 'Decimal';
|
23971
|
+
|
23102
23972
|
// 4.1.5. Serializing a Decimal
|
23103
23973
|
//
|
23104
23974
|
// Given a decimal number as input_decimal, return an ASCII string
|
@@ -23144,6 +24014,8 @@ function serializeDecimal(value) {
|
|
23144
24014
|
|
23145
24015
|
const STRING = 'String';
|
23146
24016
|
|
24017
|
+
const STRING_REGEX = /[\x00-\x1f\x7f]+/; // eslint-disable-line no-control-regex
|
24018
|
+
|
23147
24019
|
// 4.1.6. Serializing a String
|
23148
24020
|
//
|
23149
24021
|
// Given a String as input_string, return an ASCII string suitable for
|
@@ -23179,6 +24051,8 @@ function symbolToStr(symbol) {
|
|
23179
24051
|
return symbol.description || symbol.toString().slice(7, -1);
|
23180
24052
|
}
|
23181
24053
|
|
24054
|
+
const TOKEN = 'Token';
|
24055
|
+
|
23182
24056
|
function serializeToken(token) {
|
23183
24057
|
const value = symbolToStr(token);
|
23184
24058
|
if (/^([a-zA-Z*])([!#$%&'*+\-.^_`|~\w:/]*)$/.test(value) === false) {
|
@@ -23246,6 +24120,8 @@ function serializeBareItem(value) {
|
|
23246
24120
|
}
|
23247
24121
|
}
|
23248
24122
|
|
24123
|
+
const KEY = 'Key';
|
24124
|
+
|
23249
24125
|
// 4.1.1.3. Serializing a Key
|
23250
24126
|
//
|
23251
24127
|
// Given a key as input_key, return an ASCII string suitable for use in
|
@@ -23487,36 +24363,6 @@ function urlToRelativePath(url, base) {
|
|
23487
24363
|
return toPath.join('/');
|
23488
24364
|
}
|
23489
24365
|
|
23490
|
-
/**
|
23491
|
-
* Generate a random v4 UUID
|
23492
|
-
*
|
23493
|
-
* @returns A random v4 UUID
|
23494
|
-
*
|
23495
|
-
* @group Utils
|
23496
|
-
*
|
23497
|
-
* @beta
|
23498
|
-
*/
|
23499
|
-
function uuid() {
|
23500
|
-
try {
|
23501
|
-
return crypto.randomUUID();
|
23502
|
-
} catch (error) {
|
23503
|
-
try {
|
23504
|
-
const url = URL.createObjectURL(new Blob());
|
23505
|
-
const uuid = url.toString();
|
23506
|
-
URL.revokeObjectURL(url);
|
23507
|
-
return uuid.slice(uuid.lastIndexOf('/') + 1);
|
23508
|
-
} catch (error) {
|
23509
|
-
let dt = new Date().getTime();
|
23510
|
-
const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
23511
|
-
const r = (dt + Math.random() * 16) % 16 | 0;
|
23512
|
-
dt = Math.floor(dt / 16);
|
23513
|
-
return (c == 'x' ? r : r & 0x3 | 0x8).toString(16);
|
23514
|
-
});
|
23515
|
-
return uuid;
|
23516
|
-
}
|
23517
|
-
}
|
23518
|
-
}
|
23519
|
-
|
23520
24366
|
const toRounded = value => Math.round(value);
|
23521
24367
|
const toUrlSafe = (value, options) => {
|
23522
24368
|
if (options != null && options.baseUrl) {
|
@@ -23742,6 +24588,36 @@ function appendCmcdQuery(url, cmcd, options) {
|
|
23742
24588
|
return `${url}${separator}${query}`;
|
23743
24589
|
}
|
23744
24590
|
|
24591
|
+
/**
|
24592
|
+
* Generate a random v4 UUID
|
24593
|
+
*
|
24594
|
+
* @returns A random v4 UUID
|
24595
|
+
*
|
24596
|
+
* @group Utils
|
24597
|
+
*
|
24598
|
+
* @beta
|
24599
|
+
*/
|
24600
|
+
function uuid() {
|
24601
|
+
try {
|
24602
|
+
return crypto.randomUUID();
|
24603
|
+
} catch (error) {
|
24604
|
+
try {
|
24605
|
+
const url = URL.createObjectURL(new Blob());
|
24606
|
+
const uuid = url.toString();
|
24607
|
+
URL.revokeObjectURL(url);
|
24608
|
+
return uuid.slice(uuid.lastIndexOf('/') + 1);
|
24609
|
+
} catch (error) {
|
24610
|
+
let dt = new Date().getTime();
|
24611
|
+
const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
24612
|
+
const r = (dt + Math.random() * 16) % 16 | 0;
|
24613
|
+
dt = Math.floor(dt / 16);
|
24614
|
+
return (c == 'x' ? r : r & 0x3 | 0x8).toString(16);
|
24615
|
+
});
|
24616
|
+
return uuid;
|
24617
|
+
}
|
24618
|
+
}
|
24619
|
+
}
|
24620
|
+
|
23745
24621
|
/**
|
23746
24622
|
* Controller to deal with Common Media Client Data (CMCD)
|
23747
24623
|
* @see https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf
|
@@ -23785,7 +24661,7 @@ class CMCDController {
|
|
23785
24661
|
su: !this.initialized
|
23786
24662
|
});
|
23787
24663
|
} catch (error) {
|
23788
|
-
logger.warn('Could not generate manifest CMCD data.', error);
|
24664
|
+
this.hls.logger.warn('Could not generate manifest CMCD data.', error);
|
23789
24665
|
}
|
23790
24666
|
};
|
23791
24667
|
/**
|
@@ -23805,9 +24681,15 @@ class CMCDController {
|
|
23805
24681
|
data.tb = this.getTopBandwidth(ot) / 1000;
|
23806
24682
|
data.bl = this.getBufferLength(ot);
|
23807
24683
|
}
|
24684
|
+
const next = this.getNextFrag(fragment);
|
24685
|
+
if (next) {
|
24686
|
+
if (next.url && next.url !== fragment.url) {
|
24687
|
+
data.nor = next.url;
|
24688
|
+
}
|
24689
|
+
}
|
23808
24690
|
this.apply(context, data);
|
23809
24691
|
} catch (error) {
|
23810
|
-
logger.warn('Could not generate segment CMCD data.', error);
|
24692
|
+
this.hls.logger.warn('Could not generate segment CMCD data.', error);
|
23811
24693
|
}
|
23812
24694
|
};
|
23813
24695
|
this.hls = hls;
|
@@ -23897,7 +24779,7 @@ class CMCDController {
|
|
23897
24779
|
data.su = this.buffering;
|
23898
24780
|
}
|
23899
24781
|
|
23900
|
-
// TODO: Implement rtp, nrr,
|
24782
|
+
// TODO: Implement rtp, nrr, dl
|
23901
24783
|
|
23902
24784
|
const {
|
23903
24785
|
includeKeys
|
@@ -23908,15 +24790,28 @@ class CMCDController {
|
|
23908
24790
|
return acc;
|
23909
24791
|
}, {});
|
23910
24792
|
}
|
24793
|
+
const options = {
|
24794
|
+
baseUrl: context.url
|
24795
|
+
};
|
23911
24796
|
if (this.useHeaders) {
|
23912
24797
|
if (!context.headers) {
|
23913
24798
|
context.headers = {};
|
23914
24799
|
}
|
23915
|
-
appendCmcdHeaders(context.headers, data);
|
24800
|
+
appendCmcdHeaders(context.headers, data, options);
|
23916
24801
|
} else {
|
23917
|
-
context.url = appendCmcdQuery(context.url, data);
|
24802
|
+
context.url = appendCmcdQuery(context.url, data, options);
|
24803
|
+
}
|
24804
|
+
}
|
24805
|
+
getNextFrag(fragment) {
|
24806
|
+
var _this$hls$levels$frag;
|
24807
|
+
const levelDetails = (_this$hls$levels$frag = this.hls.levels[fragment.level]) == null ? void 0 : _this$hls$levels$frag.details;
|
24808
|
+
if (levelDetails) {
|
24809
|
+
const index = fragment.sn - levelDetails.startSN;
|
24810
|
+
return levelDetails.fragments[index + 1];
|
23918
24811
|
}
|
24812
|
+
return undefined;
|
23919
24813
|
}
|
24814
|
+
|
23920
24815
|
/**
|
23921
24816
|
* The CMCD object type.
|
23922
24817
|
*/
|
@@ -24045,10 +24940,10 @@ class CMCDController {
|
|
24045
24940
|
}
|
24046
24941
|
|
24047
24942
|
const PATHWAY_PENALTY_DURATION_MS = 300000;
|
24048
|
-
class ContentSteeringController {
|
24943
|
+
class ContentSteeringController extends Logger {
|
24049
24944
|
constructor(hls) {
|
24945
|
+
super('content-steering', hls.logger);
|
24050
24946
|
this.hls = void 0;
|
24051
|
-
this.log = void 0;
|
24052
24947
|
this.loader = null;
|
24053
24948
|
this.uri = null;
|
24054
24949
|
this.pathwayId = '.';
|
@@ -24063,7 +24958,6 @@ class ContentSteeringController {
|
|
24063
24958
|
this.subtitleTracks = null;
|
24064
24959
|
this.penalizedPathways = {};
|
24065
24960
|
this.hls = hls;
|
24066
|
-
this.log = logger.log.bind(logger, `[content-steering]:`);
|
24067
24961
|
this.registerListeners();
|
24068
24962
|
}
|
24069
24963
|
registerListeners() {
|
@@ -24187,7 +25081,7 @@ class ContentSteeringController {
|
|
24187
25081
|
errorAction.resolved = this.pathwayId !== errorPathway;
|
24188
25082
|
}
|
24189
25083
|
if (!errorAction.resolved) {
|
24190
|
-
|
25084
|
+
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)}`);
|
24191
25085
|
}
|
24192
25086
|
}
|
24193
25087
|
}
|
@@ -24358,7 +25252,7 @@ class ContentSteeringController {
|
|
24358
25252
|
onSuccess: (response, stats, context, networkDetails) => {
|
24359
25253
|
this.log(`Loaded steering manifest: "${url}"`);
|
24360
25254
|
const steeringData = response.data;
|
24361
|
-
if (steeringData.VERSION !== 1) {
|
25255
|
+
if ((steeringData == null ? void 0 : steeringData.VERSION) !== 1) {
|
24362
25256
|
this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
|
24363
25257
|
return;
|
24364
25258
|
}
|
@@ -25328,7 +26222,7 @@ function timelineConfig() {
|
|
25328
26222
|
/**
|
25329
26223
|
* @ignore
|
25330
26224
|
*/
|
25331
|
-
function mergeConfig(defaultConfig, userConfig) {
|
26225
|
+
function mergeConfig(defaultConfig, userConfig, logger) {
|
25332
26226
|
if ((userConfig.liveSyncDurationCount || userConfig.liveMaxLatencyDurationCount) && (userConfig.liveSyncDuration || userConfig.liveMaxLatencyDuration)) {
|
25333
26227
|
throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration");
|
25334
26228
|
}
|
@@ -25398,7 +26292,7 @@ function deepCpy(obj) {
|
|
25398
26292
|
/**
|
25399
26293
|
* @ignore
|
25400
26294
|
*/
|
25401
|
-
function enableStreamingMode(config) {
|
26295
|
+
function enableStreamingMode(config, logger) {
|
25402
26296
|
const currentLoader = config.loader;
|
25403
26297
|
if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
|
25404
26298
|
// If a developer has configured their own loader, respect that choice
|
@@ -25415,10 +26309,9 @@ function enableStreamingMode(config) {
|
|
25415
26309
|
}
|
25416
26310
|
}
|
25417
26311
|
|
25418
|
-
let chromeOrFirefox;
|
25419
26312
|
class LevelController extends BasePlaylistController {
|
25420
26313
|
constructor(hls, contentSteeringController) {
|
25421
|
-
super(hls, '
|
26314
|
+
super(hls, 'level-controller');
|
25422
26315
|
this._levels = [];
|
25423
26316
|
this._firstLevel = -1;
|
25424
26317
|
this._maxAutoLevel = -1;
|
@@ -25489,23 +26382,15 @@ class LevelController extends BasePlaylistController {
|
|
25489
26382
|
let videoCodecFound = false;
|
25490
26383
|
let audioCodecFound = false;
|
25491
26384
|
data.levels.forEach(levelParsed => {
|
25492
|
-
var
|
26385
|
+
var _videoCodec;
|
25493
26386
|
const attributes = levelParsed.attrs;
|
25494
|
-
|
25495
|
-
// erase audio codec info if browser does not support mp4a.40.34.
|
25496
|
-
// demuxer will autodetect codec and fallback to mpeg/audio
|
25497
26387
|
let {
|
25498
26388
|
audioCodec,
|
25499
26389
|
videoCodec
|
25500
26390
|
} = levelParsed;
|
25501
|
-
if (((_audioCodec = audioCodec) == null ? void 0 : _audioCodec.indexOf('mp4a.40.34')) !== -1) {
|
25502
|
-
chromeOrFirefox || (chromeOrFirefox = /chrome|firefox/i.test(navigator.userAgent));
|
25503
|
-
if (chromeOrFirefox) {
|
25504
|
-
levelParsed.audioCodec = audioCodec = undefined;
|
25505
|
-
}
|
25506
|
-
}
|
25507
26391
|
if (audioCodec) {
|
25508
|
-
|
26392
|
+
// Returns empty and set to undefined for 'mp4a.40.34' with fallback to 'audio/mpeg' SourceBuffer
|
26393
|
+
levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource) || undefined;
|
25509
26394
|
}
|
25510
26395
|
if (((_videoCodec = videoCodec) == null ? void 0 : _videoCodec.indexOf('avc1')) === 0) {
|
25511
26396
|
videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
|
@@ -25847,7 +26732,12 @@ class LevelController extends BasePlaylistController {
|
|
25847
26732
|
if (curLevel.fragmentError === 0) {
|
25848
26733
|
curLevel.loadError = 0;
|
25849
26734
|
}
|
25850
|
-
|
26735
|
+
// Ignore matching details populated by loading a Media Playlist directly
|
26736
|
+
let previousDetails = curLevel.details;
|
26737
|
+
if (previousDetails === data.details && previousDetails.advanced) {
|
26738
|
+
previousDetails = undefined;
|
26739
|
+
}
|
26740
|
+
this.playlistLoaded(level, data, previousDetails);
|
25851
26741
|
} else if ((_data$deliveryDirecti2 = data.deliveryDirectives) != null && _data$deliveryDirecti2.skip) {
|
25852
26742
|
// received a delta playlist update that cannot be merged
|
25853
26743
|
details.deltaUpdateFailed = true;
|
@@ -26091,6 +26981,8 @@ class KeyLoader {
|
|
26091
26981
|
}
|
26092
26982
|
return this.loadKeyEME(keyInfo, frag);
|
26093
26983
|
case 'AES-128':
|
26984
|
+
case 'AES-256':
|
26985
|
+
case 'AES-256-CTR':
|
26094
26986
|
return this.loadKeyHTTP(keyInfo, frag);
|
26095
26987
|
default:
|
26096
26988
|
return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`)));
|
@@ -26228,8 +27120,9 @@ const STALL_MINIMUM_DURATION_MS = 250;
|
|
26228
27120
|
const MAX_START_GAP_JUMP = 2.0;
|
26229
27121
|
const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
|
26230
27122
|
const SKIP_BUFFER_RANGE_START = 0.05;
|
26231
|
-
class GapController {
|
27123
|
+
class GapController extends Logger {
|
26232
27124
|
constructor(config, media, fragmentTracker, hls) {
|
27125
|
+
super('gap-controller', hls.logger);
|
26233
27126
|
this.config = void 0;
|
26234
27127
|
this.media = null;
|
26235
27128
|
this.fragmentTracker = void 0;
|
@@ -26239,6 +27132,7 @@ class GapController {
|
|
26239
27132
|
this.stalled = null;
|
26240
27133
|
this.moved = false;
|
26241
27134
|
this.seeking = false;
|
27135
|
+
this.ended = 0;
|
26242
27136
|
this.config = config;
|
26243
27137
|
this.media = media;
|
26244
27138
|
this.fragmentTracker = fragmentTracker;
|
@@ -26256,7 +27150,7 @@ class GapController {
|
|
26256
27150
|
*
|
26257
27151
|
* @param lastCurrentTime - Previously read playhead position
|
26258
27152
|
*/
|
26259
|
-
poll(lastCurrentTime, activeFrag) {
|
27153
|
+
poll(lastCurrentTime, activeFrag, levelDetails, state) {
|
26260
27154
|
const {
|
26261
27155
|
config,
|
26262
27156
|
media,
|
@@ -26275,6 +27169,7 @@ class GapController {
|
|
26275
27169
|
|
26276
27170
|
// The playhead is moving, no-op
|
26277
27171
|
if (currentTime !== lastCurrentTime) {
|
27172
|
+
this.ended = 0;
|
26278
27173
|
this.moved = true;
|
26279
27174
|
if (!seeking) {
|
26280
27175
|
this.nudgeRetry = 0;
|
@@ -26283,7 +27178,7 @@ class GapController {
|
|
26283
27178
|
// The playhead is now moving, but was previously stalled
|
26284
27179
|
if (this.stallReported) {
|
26285
27180
|
const _stalledDuration = self.performance.now() - stalled;
|
26286
|
-
|
27181
|
+
this.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
|
26287
27182
|
this.stallReported = false;
|
26288
27183
|
}
|
26289
27184
|
this.stalled = null;
|
@@ -26319,7 +27214,6 @@ class GapController {
|
|
26319
27214
|
// Skip start gaps if we haven't played, but the last poll detected the start of a stall
|
26320
27215
|
// The addition poll gives the browser a chance to jump the gap for us
|
26321
27216
|
if (!this.moved && this.stalled !== null) {
|
26322
|
-
var _level$details;
|
26323
27217
|
// There is no playable buffer (seeked, waiting for buffer)
|
26324
27218
|
const isBuffered = bufferInfo.len > 0;
|
26325
27219
|
if (!isBuffered && !nextStart) {
|
@@ -26331,9 +27225,8 @@ class GapController {
|
|
26331
27225
|
// When joining a live stream with audio tracks, account for live playlist window sliding by allowing
|
26332
27226
|
// a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
|
26333
27227
|
// that begins over 1 target duration after the video start position.
|
26334
|
-
const
|
26335
|
-
const
|
26336
|
-
const maxStartGapJump = isLive ? level.details.targetduration * 2 : MAX_START_GAP_JUMP;
|
27228
|
+
const isLive = !!(levelDetails != null && levelDetails.live);
|
27229
|
+
const maxStartGapJump = isLive ? levelDetails.targetduration * 2 : MAX_START_GAP_JUMP;
|
26337
27230
|
const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
|
26338
27231
|
if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
|
26339
27232
|
if (!media.paused) {
|
@@ -26351,6 +27244,17 @@ class GapController {
|
|
26351
27244
|
}
|
26352
27245
|
const stalledDuration = tnow - stalled;
|
26353
27246
|
if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) {
|
27247
|
+
// Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
|
27248
|
+
if (state === State.ENDED && !(levelDetails && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? void 0 : levelDetails.edge) || 0)) < 1) {
|
27249
|
+
if (stalledDuration < 1000 || this.ended) {
|
27250
|
+
return;
|
27251
|
+
}
|
27252
|
+
this.ended = currentTime;
|
27253
|
+
this.hls.trigger(Events.MEDIA_ENDED, {
|
27254
|
+
stalled: true
|
27255
|
+
});
|
27256
|
+
return;
|
27257
|
+
}
|
26354
27258
|
// Report stalling after trying to fix
|
26355
27259
|
this._reportStall(bufferInfo);
|
26356
27260
|
if (!this.media) {
|
@@ -26394,7 +27298,7 @@ class GapController {
|
|
26394
27298
|
// needs to cross some sort of threshold covering all source-buffers content
|
26395
27299
|
// to start playing properly.
|
26396
27300
|
if ((bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
|
26397
|
-
|
27301
|
+
this.warn('Trying to nudge playhead over buffer-hole');
|
26398
27302
|
// Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
|
26399
27303
|
// We only try to jump the hole if it's under the configured size
|
26400
27304
|
// Reset stalled so to rearm watchdog timer
|
@@ -26418,7 +27322,7 @@ class GapController {
|
|
26418
27322
|
// Report stalled error once
|
26419
27323
|
this.stallReported = true;
|
26420
27324
|
const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${JSON.stringify(bufferInfo)})`);
|
26421
|
-
|
27325
|
+
this.warn(error.message);
|
26422
27326
|
hls.trigger(Events.ERROR, {
|
26423
27327
|
type: ErrorTypes.MEDIA_ERROR,
|
26424
27328
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
@@ -26486,7 +27390,7 @@ class GapController {
|
|
26486
27390
|
}
|
26487
27391
|
}
|
26488
27392
|
const targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
|
26489
|
-
|
27393
|
+
this.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
|
26490
27394
|
this.moved = true;
|
26491
27395
|
this.stalled = null;
|
26492
27396
|
media.currentTime = targetTime;
|
@@ -26527,7 +27431,7 @@ class GapController {
|
|
26527
27431
|
const targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset;
|
26528
27432
|
// playback stalled in buffered area ... let's nudge currentTime to try to overcome this
|
26529
27433
|
const error = new Error(`Nudging 'currentTime' from ${currentTime} to ${targetTime}`);
|
26530
|
-
|
27434
|
+
this.warn(error.message);
|
26531
27435
|
media.currentTime = targetTime;
|
26532
27436
|
hls.trigger(Events.ERROR, {
|
26533
27437
|
type: ErrorTypes.MEDIA_ERROR,
|
@@ -26537,7 +27441,7 @@ class GapController {
|
|
26537
27441
|
});
|
26538
27442
|
} else {
|
26539
27443
|
const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`);
|
26540
|
-
|
27444
|
+
this.error(error.message);
|
26541
27445
|
hls.trigger(Events.ERROR, {
|
26542
27446
|
type: ErrorTypes.MEDIA_ERROR,
|
26543
27447
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
@@ -26552,7 +27456,7 @@ const TICK_INTERVAL = 100; // how often to tick in ms
|
|
26552
27456
|
|
26553
27457
|
class StreamController extends BaseStreamController {
|
26554
27458
|
constructor(hls, fragmentTracker, keyLoader) {
|
26555
|
-
super(hls, fragmentTracker, keyLoader, '
|
27459
|
+
super(hls, fragmentTracker, keyLoader, 'stream-controller', PlaylistLevelType.MAIN);
|
26556
27460
|
this.audioCodecSwap = false;
|
26557
27461
|
this.gapController = null;
|
26558
27462
|
this.level = -1;
|
@@ -26560,27 +27464,43 @@ class StreamController extends BaseStreamController {
|
|
26560
27464
|
this.altAudio = false;
|
26561
27465
|
this.audioOnly = false;
|
26562
27466
|
this.fragPlaying = null;
|
26563
|
-
this.onvplaying = null;
|
26564
|
-
this.onvseeked = null;
|
26565
27467
|
this.fragLastKbps = 0;
|
26566
27468
|
this.couldBacktrack = false;
|
26567
27469
|
this.backtrackFragment = null;
|
26568
27470
|
this.audioCodecSwitch = false;
|
26569
27471
|
this.videoBuffer = null;
|
26570
|
-
this.
|
27472
|
+
this.onMediaPlaying = () => {
|
27473
|
+
// tick to speed up FRAG_CHANGED triggering
|
27474
|
+
this.tick();
|
27475
|
+
};
|
27476
|
+
this.onMediaSeeked = () => {
|
27477
|
+
const media = this.media;
|
27478
|
+
const currentTime = media ? media.currentTime : null;
|
27479
|
+
if (isFiniteNumber(currentTime)) {
|
27480
|
+
this.log(`Media seeked to ${currentTime.toFixed(3)}`);
|
27481
|
+
}
|
27482
|
+
|
27483
|
+
// If seeked was issued before buffer was appended do not tick immediately
|
27484
|
+
const bufferInfo = this.getMainFwdBufferInfo();
|
27485
|
+
if (bufferInfo === null || bufferInfo.len === 0) {
|
27486
|
+
this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
|
27487
|
+
return;
|
27488
|
+
}
|
27489
|
+
|
27490
|
+
// tick to speed up FRAG_CHANGED triggering
|
27491
|
+
this.tick();
|
27492
|
+
};
|
27493
|
+
this.registerListeners();
|
26571
27494
|
}
|
26572
|
-
|
27495
|
+
registerListeners() {
|
27496
|
+
super.registerListeners();
|
26573
27497
|
const {
|
26574
27498
|
hls
|
26575
27499
|
} = this;
|
26576
|
-
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
26577
|
-
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
26578
|
-
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
26579
27500
|
hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
26580
27501
|
hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
|
26581
27502
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
26582
27503
|
hls.on(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
|
26583
|
-
hls.on(Events.ERROR, this.onError, this);
|
26584
27504
|
hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
26585
27505
|
hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
26586
27506
|
hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -26588,17 +27508,14 @@ class StreamController extends BaseStreamController {
|
|
26588
27508
|
hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
|
26589
27509
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
26590
27510
|
}
|
26591
|
-
|
27511
|
+
unregisterListeners() {
|
27512
|
+
super.unregisterListeners();
|
26592
27513
|
const {
|
26593
27514
|
hls
|
26594
27515
|
} = this;
|
26595
|
-
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
26596
|
-
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
26597
|
-
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
26598
27516
|
hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
26599
27517
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
26600
27518
|
hls.off(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
|
26601
|
-
hls.off(Events.ERROR, this.onError, this);
|
26602
27519
|
hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
26603
27520
|
hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
26604
27521
|
hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -26607,7 +27524,9 @@ class StreamController extends BaseStreamController {
|
|
26607
27524
|
hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
26608
27525
|
}
|
26609
27526
|
onHandlerDestroying() {
|
26610
|
-
|
27527
|
+
// @ts-ignore
|
27528
|
+
this.onMediaPlaying = this.onMediaSeeked = null;
|
27529
|
+
this.unregisterListeners();
|
26611
27530
|
super.onHandlerDestroying();
|
26612
27531
|
}
|
26613
27532
|
startLoad(startPosition) {
|
@@ -26932,20 +27851,17 @@ class StreamController extends BaseStreamController {
|
|
26932
27851
|
onMediaAttached(event, data) {
|
26933
27852
|
super.onMediaAttached(event, data);
|
26934
27853
|
const media = data.media;
|
26935
|
-
|
26936
|
-
|
26937
|
-
media.addEventListener('playing', this.onvplaying);
|
26938
|
-
media.addEventListener('seeked', this.onvseeked);
|
27854
|
+
media.addEventListener('playing', this.onMediaPlaying);
|
27855
|
+
media.addEventListener('seeked', this.onMediaSeeked);
|
26939
27856
|
this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
|
26940
27857
|
}
|
26941
27858
|
onMediaDetaching() {
|
26942
27859
|
const {
|
26943
27860
|
media
|
26944
27861
|
} = this;
|
26945
|
-
if (media
|
26946
|
-
media.removeEventListener('playing', this.
|
26947
|
-
media.removeEventListener('seeked', this.
|
26948
|
-
this.onvplaying = this.onvseeked = null;
|
27862
|
+
if (media) {
|
27863
|
+
media.removeEventListener('playing', this.onMediaPlaying);
|
27864
|
+
media.removeEventListener('seeked', this.onMediaSeeked);
|
26949
27865
|
this.videoBuffer = null;
|
26950
27866
|
}
|
26951
27867
|
this.fragPlaying = null;
|
@@ -26955,27 +27871,6 @@ class StreamController extends BaseStreamController {
|
|
26955
27871
|
}
|
26956
27872
|
super.onMediaDetaching();
|
26957
27873
|
}
|
26958
|
-
onMediaPlaying() {
|
26959
|
-
// tick to speed up FRAG_CHANGED triggering
|
26960
|
-
this.tick();
|
26961
|
-
}
|
26962
|
-
onMediaSeeked() {
|
26963
|
-
const media = this.media;
|
26964
|
-
const currentTime = media ? media.currentTime : null;
|
26965
|
-
if (isFiniteNumber(currentTime)) {
|
26966
|
-
this.log(`Media seeked to ${currentTime.toFixed(3)}`);
|
26967
|
-
}
|
26968
|
-
|
26969
|
-
// If seeked was issued before buffer was appended do not tick immediately
|
26970
|
-
const bufferInfo = this.getMainFwdBufferInfo();
|
26971
|
-
if (bufferInfo === null || bufferInfo.len === 0) {
|
26972
|
-
this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
|
26973
|
-
return;
|
26974
|
-
}
|
26975
|
-
|
26976
|
-
// tick to speed up FRAG_CHANGED triggering
|
26977
|
-
this.tick();
|
26978
|
-
}
|
26979
27874
|
onManifestLoading() {
|
26980
27875
|
// reset buffer on manifest loading
|
26981
27876
|
this.log('Trigger BUFFER_RESET');
|
@@ -27267,8 +28162,10 @@ class StreamController extends BaseStreamController {
|
|
27267
28162
|
}
|
27268
28163
|
if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) {
|
27269
28164
|
// Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
|
27270
|
-
const
|
27271
|
-
|
28165
|
+
const state = this.state;
|
28166
|
+
const activeFrag = state !== State.IDLE ? this.fragCurrent : null;
|
28167
|
+
const levelDetails = this.getLevelDetails();
|
28168
|
+
gapController.poll(this.lastCurrentTime, activeFrag, levelDetails, state);
|
27272
28169
|
}
|
27273
28170
|
this.lastCurrentTime = media.currentTime;
|
27274
28171
|
}
|
@@ -27706,7 +28603,7 @@ class Hls {
|
|
27706
28603
|
* Get the video-dev/hls.js package version.
|
27707
28604
|
*/
|
27708
28605
|
static get version() {
|
27709
|
-
return "1.5.
|
28606
|
+
return "1.5.7-0.canary.10015";
|
27710
28607
|
}
|
27711
28608
|
|
27712
28609
|
/**
|
@@ -27769,9 +28666,12 @@ class Hls {
|
|
27769
28666
|
* The configuration object provided on player instantiation.
|
27770
28667
|
*/
|
27771
28668
|
this.userConfig = void 0;
|
28669
|
+
/**
|
28670
|
+
* The logger functions used by this player instance, configured on player instantiation.
|
28671
|
+
*/
|
28672
|
+
this.logger = void 0;
|
27772
28673
|
this.coreComponents = void 0;
|
27773
28674
|
this.networkControllers = void 0;
|
27774
|
-
this.started = false;
|
27775
28675
|
this._emitter = new EventEmitter();
|
27776
28676
|
this._autoLevelCapping = -1;
|
27777
28677
|
this._maxHdcpLevel = null;
|
@@ -27788,11 +28688,11 @@ class Hls {
|
|
27788
28688
|
this._media = null;
|
27789
28689
|
this.url = null;
|
27790
28690
|
this.triggeringException = void 0;
|
27791
|
-
enableLogs(userConfig.debug || false, 'Hls instance');
|
27792
|
-
const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig);
|
28691
|
+
const logger = this.logger = enableLogs(userConfig.debug || false, 'Hls instance');
|
28692
|
+
const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig, logger);
|
27793
28693
|
this.userConfig = userConfig;
|
27794
28694
|
if (config.progressive) {
|
27795
|
-
enableStreamingMode(config);
|
28695
|
+
enableStreamingMode(config, logger);
|
27796
28696
|
}
|
27797
28697
|
|
27798
28698
|
// core controllers and network loaders
|
@@ -27891,7 +28791,7 @@ class Hls {
|
|
27891
28791
|
try {
|
27892
28792
|
return this.emit(event, event, eventObject);
|
27893
28793
|
} catch (error) {
|
27894
|
-
logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
|
28794
|
+
this.logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
|
27895
28795
|
// Prevent recursion in error event handlers that throw #5497
|
27896
28796
|
if (!this.triggeringException) {
|
27897
28797
|
this.triggeringException = true;
|
@@ -27917,7 +28817,7 @@ class Hls {
|
|
27917
28817
|
* Dispose of the instance
|
27918
28818
|
*/
|
27919
28819
|
destroy() {
|
27920
|
-
logger.log('destroy');
|
28820
|
+
this.logger.log('destroy');
|
27921
28821
|
this.trigger(Events.DESTROYING, undefined);
|
27922
28822
|
this.detachMedia();
|
27923
28823
|
this.removeAllListeners();
|
@@ -27938,7 +28838,7 @@ class Hls {
|
|
27938
28838
|
* Attaches Hls.js to a media element
|
27939
28839
|
*/
|
27940
28840
|
attachMedia(media) {
|
27941
|
-
logger.log('attachMedia');
|
28841
|
+
this.logger.log('attachMedia');
|
27942
28842
|
this._media = media;
|
27943
28843
|
this.trigger(Events.MEDIA_ATTACHING, {
|
27944
28844
|
media: media
|
@@ -27949,7 +28849,7 @@ class Hls {
|
|
27949
28849
|
* Detach Hls.js from the media
|
27950
28850
|
*/
|
27951
28851
|
detachMedia() {
|
27952
|
-
logger.log('detachMedia');
|
28852
|
+
this.logger.log('detachMedia');
|
27953
28853
|
this.trigger(Events.MEDIA_DETACHING, undefined);
|
27954
28854
|
this._media = null;
|
27955
28855
|
}
|
@@ -27966,7 +28866,7 @@ class Hls {
|
|
27966
28866
|
});
|
27967
28867
|
this._autoLevelCapping = -1;
|
27968
28868
|
this._maxHdcpLevel = null;
|
27969
|
-
logger.log(`loadSource:${loadingSource}`);
|
28869
|
+
this.logger.log(`loadSource:${loadingSource}`);
|
27970
28870
|
if (media && loadedSource && (loadedSource !== loadingSource || this.bufferController.hasSourceTypes())) {
|
27971
28871
|
this.detachMedia();
|
27972
28872
|
this.attachMedia(media);
|
@@ -27985,8 +28885,7 @@ class Hls {
|
|
27985
28885
|
* Defaults to -1 (None: starts from earliest point)
|
27986
28886
|
*/
|
27987
28887
|
startLoad(startPosition = -1) {
|
27988
|
-
logger.log(`startLoad(${startPosition})`);
|
27989
|
-
this.started = true;
|
28888
|
+
this.logger.log(`startLoad(${startPosition})`);
|
27990
28889
|
this.networkControllers.forEach(controller => {
|
27991
28890
|
controller.startLoad(startPosition);
|
27992
28891
|
});
|
@@ -27996,34 +28895,31 @@ class Hls {
|
|
27996
28895
|
* Stop loading of any stream data.
|
27997
28896
|
*/
|
27998
28897
|
stopLoad() {
|
27999
|
-
logger.log('stopLoad');
|
28000
|
-
this.started = false;
|
28898
|
+
this.logger.log('stopLoad');
|
28001
28899
|
this.networkControllers.forEach(controller => {
|
28002
28900
|
controller.stopLoad();
|
28003
28901
|
});
|
28004
28902
|
}
|
28005
28903
|
|
28006
28904
|
/**
|
28007
|
-
* Resumes stream controller segment loading
|
28905
|
+
* Resumes stream controller segment loading after `pauseBuffering` has been called.
|
28008
28906
|
*/
|
28009
28907
|
resumeBuffering() {
|
28010
|
-
|
28011
|
-
|
28012
|
-
|
28013
|
-
|
28014
|
-
|
28015
|
-
});
|
28016
|
-
}
|
28908
|
+
this.networkControllers.forEach(controller => {
|
28909
|
+
if (controller.resumeBuffering) {
|
28910
|
+
controller.resumeBuffering();
|
28911
|
+
}
|
28912
|
+
});
|
28017
28913
|
}
|
28018
28914
|
|
28019
28915
|
/**
|
28020
|
-
*
|
28916
|
+
* Prevents stream controller from loading new segments until `resumeBuffering` is called.
|
28021
28917
|
* This allows for media buffering to be paused without interupting playlist loading.
|
28022
28918
|
*/
|
28023
28919
|
pauseBuffering() {
|
28024
28920
|
this.networkControllers.forEach(controller => {
|
28025
|
-
if (
|
28026
|
-
controller.
|
28921
|
+
if (controller.pauseBuffering) {
|
28922
|
+
controller.pauseBuffering();
|
28027
28923
|
}
|
28028
28924
|
});
|
28029
28925
|
}
|
@@ -28032,7 +28928,7 @@ class Hls {
|
|
28032
28928
|
* Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
|
28033
28929
|
*/
|
28034
28930
|
swapAudioCodec() {
|
28035
|
-
logger.log('swapAudioCodec');
|
28931
|
+
this.logger.log('swapAudioCodec');
|
28036
28932
|
this.streamController.swapAudioCodec();
|
28037
28933
|
}
|
28038
28934
|
|
@@ -28043,7 +28939,7 @@ class Hls {
|
|
28043
28939
|
* Automatic recovery of media-errors by this process is configurable.
|
28044
28940
|
*/
|
28045
28941
|
recoverMediaError() {
|
28046
|
-
logger.log('recoverMediaError');
|
28942
|
+
this.logger.log('recoverMediaError');
|
28047
28943
|
const media = this._media;
|
28048
28944
|
this.detachMedia();
|
28049
28945
|
if (media) {
|
@@ -28073,7 +28969,7 @@ class Hls {
|
|
28073
28969
|
* 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.
|
28074
28970
|
*/
|
28075
28971
|
set currentLevel(newLevel) {
|
28076
|
-
logger.log(`set currentLevel:${newLevel}`);
|
28972
|
+
this.logger.log(`set currentLevel:${newLevel}`);
|
28077
28973
|
this.levelController.manualLevel = newLevel;
|
28078
28974
|
this.streamController.immediateLevelSwitch();
|
28079
28975
|
}
|
@@ -28092,7 +28988,7 @@ class Hls {
|
|
28092
28988
|
* @param newLevel - Pass -1 for automatic level selection
|
28093
28989
|
*/
|
28094
28990
|
set nextLevel(newLevel) {
|
28095
|
-
logger.log(`set nextLevel:${newLevel}`);
|
28991
|
+
this.logger.log(`set nextLevel:${newLevel}`);
|
28096
28992
|
this.levelController.manualLevel = newLevel;
|
28097
28993
|
this.streamController.nextLevelSwitch();
|
28098
28994
|
}
|
@@ -28111,7 +29007,7 @@ class Hls {
|
|
28111
29007
|
* @param newLevel - Pass -1 for automatic level selection
|
28112
29008
|
*/
|
28113
29009
|
set loadLevel(newLevel) {
|
28114
|
-
logger.log(`set loadLevel:${newLevel}`);
|
29010
|
+
this.logger.log(`set loadLevel:${newLevel}`);
|
28115
29011
|
this.levelController.manualLevel = newLevel;
|
28116
29012
|
}
|
28117
29013
|
|
@@ -28142,7 +29038,7 @@ class Hls {
|
|
28142
29038
|
* Sets "first-level", see getter.
|
28143
29039
|
*/
|
28144
29040
|
set firstLevel(newLevel) {
|
28145
|
-
logger.log(`set firstLevel:${newLevel}`);
|
29041
|
+
this.logger.log(`set firstLevel:${newLevel}`);
|
28146
29042
|
this.levelController.firstLevel = newLevel;
|
28147
29043
|
}
|
28148
29044
|
|
@@ -28167,7 +29063,7 @@ class Hls {
|
|
28167
29063
|
* (determined from download of first segment)
|
28168
29064
|
*/
|
28169
29065
|
set startLevel(newLevel) {
|
28170
|
-
logger.log(`set startLevel:${newLevel}`);
|
29066
|
+
this.logger.log(`set startLevel:${newLevel}`);
|
28171
29067
|
// if not in automatic start level detection, ensure startLevel is greater than minAutoLevel
|
28172
29068
|
if (newLevel !== -1) {
|
28173
29069
|
newLevel = Math.max(newLevel, this.minAutoLevel);
|
@@ -28242,7 +29138,7 @@ class Hls {
|
|
28242
29138
|
*/
|
28243
29139
|
set autoLevelCapping(newLevel) {
|
28244
29140
|
if (this._autoLevelCapping !== newLevel) {
|
28245
|
-
logger.log(`set autoLevelCapping:${newLevel}`);
|
29141
|
+
this.logger.log(`set autoLevelCapping:${newLevel}`);
|
28246
29142
|
this._autoLevelCapping = newLevel;
|
28247
29143
|
this.levelController.checkMaxAutoUpdated();
|
28248
29144
|
}
|