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