hls.js 1.5.6-0.canary.9999 → 1.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -1
- package/dist/hls-demo.js +0 -10
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +1169 -2069
- package/dist/hls.js.d.ts +51 -65
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +875 -1158
- package/dist/hls.light.js.map +1 -1
- package/dist/hls.light.min.js +1 -1
- package/dist/hls.light.min.js.map +1 -1
- package/dist/hls.light.mjs +709 -993
- package/dist/hls.light.mjs.map +1 -1
- package/dist/hls.min.js +1 -1
- package/dist/hls.min.js.map +1 -1
- package/dist/hls.mjs +869 -1756
- package/dist/hls.mjs.map +1 -1
- package/dist/hls.worker.js +1 -1
- package/dist/hls.worker.js.map +1 -1
- package/package.json +20 -20
- package/src/config.ts +2 -3
- package/src/controller/abr-controller.ts +40 -31
- package/src/controller/audio-stream-controller.ts +16 -15
- package/src/controller/audio-track-controller.ts +1 -1
- package/src/controller/base-playlist-controller.ts +8 -20
- package/src/controller/base-stream-controller.ts +33 -149
- package/src/controller/buffer-controller.ts +11 -11
- package/src/controller/cap-level-controller.ts +2 -1
- package/src/controller/cmcd-controller.ts +6 -27
- package/src/controller/content-steering-controller.ts +6 -8
- package/src/controller/eme-controller.ts +22 -9
- package/src/controller/error-controller.ts +8 -6
- package/src/controller/fps-controller.ts +3 -2
- package/src/controller/gap-controller.ts +16 -43
- package/src/controller/latency-controller.ts +11 -9
- package/src/controller/level-controller.ts +18 -12
- package/src/controller/stream-controller.ts +34 -27
- package/src/controller/subtitle-stream-controller.ts +14 -13
- package/src/controller/subtitle-track-controller.ts +3 -5
- package/src/controller/timeline-controller.ts +30 -23
- package/src/crypt/aes-crypto.ts +2 -21
- package/src/crypt/decrypter.ts +18 -32
- package/src/crypt/fast-aes-key.ts +5 -24
- package/src/demux/audio/adts.ts +4 -9
- package/src/demux/sample-aes.ts +0 -2
- package/src/demux/transmuxer-interface.ts +12 -4
- package/src/demux/transmuxer-worker.ts +4 -4
- package/src/demux/transmuxer.ts +3 -16
- package/src/demux/tsdemuxer.ts +37 -71
- package/src/demux/video/avc-video-parser.ts +119 -208
- package/src/demux/video/base-video-parser.ts +2 -134
- package/src/demux/video/exp-golomb.ts +208 -0
- package/src/events.ts +0 -7
- package/src/hls.ts +34 -42
- package/src/loader/fragment-loader.ts +2 -9
- package/src/loader/key-loader.ts +0 -2
- package/src/loader/level-key.ts +9 -10
- package/src/loader/playlist-loader.ts +5 -4
- package/src/remux/mp4-generator.ts +1 -196
- package/src/remux/mp4-remuxer.ts +7 -23
- package/src/task-loop.ts +2 -5
- package/src/types/component-api.ts +0 -2
- package/src/types/demuxer.ts +0 -3
- package/src/types/events.ts +0 -4
- package/src/utils/codecs.ts +4 -33
- package/src/utils/logger.ts +24 -54
- package/src/utils/mp4-tools.ts +6 -4
- package/src/crypt/decrypter-aes-mode.ts +0 -4
- package/src/demux/video/hevc-video-parser.ts +0 -746
- package/src/utils/encryption-methods-util.ts +0 -21
package/dist/hls.mjs
CHANGED
@@ -256,7 +256,6 @@ let Events = /*#__PURE__*/function (Events) {
|
|
256
256
|
Events["MEDIA_ATTACHED"] = "hlsMediaAttached";
|
257
257
|
Events["MEDIA_DETACHING"] = "hlsMediaDetaching";
|
258
258
|
Events["MEDIA_DETACHED"] = "hlsMediaDetached";
|
259
|
-
Events["MEDIA_ENDED"] = "hlsMediaEnded";
|
260
259
|
Events["BUFFER_RESET"] = "hlsBufferReset";
|
261
260
|
Events["BUFFER_CODECS"] = "hlsBufferCodecs";
|
262
261
|
Events["BUFFER_CREATED"] = "hlsBufferCreated";
|
@@ -370,6 +369,58 @@ let ErrorDetails = /*#__PURE__*/function (ErrorDetails) {
|
|
370
369
|
return ErrorDetails;
|
371
370
|
}({});
|
372
371
|
|
372
|
+
const noop = function noop() {};
|
373
|
+
const fakeLogger = {
|
374
|
+
trace: noop,
|
375
|
+
debug: noop,
|
376
|
+
log: noop,
|
377
|
+
warn: noop,
|
378
|
+
info: noop,
|
379
|
+
error: noop
|
380
|
+
};
|
381
|
+
let exportedLogger = fakeLogger;
|
382
|
+
|
383
|
+
// let lastCallTime;
|
384
|
+
// function formatMsgWithTimeInfo(type, msg) {
|
385
|
+
// const now = Date.now();
|
386
|
+
// const diff = lastCallTime ? '+' + (now - lastCallTime) : '0';
|
387
|
+
// lastCallTime = now;
|
388
|
+
// msg = (new Date(now)).toISOString() + ' | [' + type + '] > ' + msg + ' ( ' + diff + ' ms )';
|
389
|
+
// return msg;
|
390
|
+
// }
|
391
|
+
|
392
|
+
function consolePrintFn(type) {
|
393
|
+
const func = self.console[type];
|
394
|
+
if (func) {
|
395
|
+
return func.bind(self.console, `[${type}] >`);
|
396
|
+
}
|
397
|
+
return noop;
|
398
|
+
}
|
399
|
+
function exportLoggerFunctions(debugConfig, ...functions) {
|
400
|
+
functions.forEach(function (type) {
|
401
|
+
exportedLogger[type] = debugConfig[type] ? debugConfig[type].bind(debugConfig) : consolePrintFn(type);
|
402
|
+
});
|
403
|
+
}
|
404
|
+
function enableLogs(debugConfig, id) {
|
405
|
+
// check that console is available
|
406
|
+
if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
|
407
|
+
exportLoggerFunctions(debugConfig,
|
408
|
+
// Remove out from list here to hard-disable a log-level
|
409
|
+
// 'trace',
|
410
|
+
'debug', 'log', 'info', 'warn', 'error');
|
411
|
+
// Some browsers don't allow to use bind on console object anyway
|
412
|
+
// fallback to default if needed
|
413
|
+
try {
|
414
|
+
exportedLogger.log(`Debug logs enabled for "${id}" in hls.js version ${"1.5.6"}`);
|
415
|
+
} catch (e) {
|
416
|
+
exportedLogger = fakeLogger;
|
417
|
+
}
|
418
|
+
} else {
|
419
|
+
exportedLogger = fakeLogger;
|
420
|
+
}
|
421
|
+
}
|
422
|
+
const logger = exportedLogger;
|
423
|
+
|
373
424
|
const DECIMAL_RESOLUTION_REGEX = /^(\d+)x(\d+)$/;
|
374
425
|
const ATTR_LIST_REGEX = /(.+?)=(".*?"|.*?)(?:,|$)/g;
|
375
426
|
|
@@ -451,79 +502,6 @@ class AttrList {
|
|
451
502
|
}
|
452
503
|
}
|
453
504
|
|
454
|
-
class Logger {
|
455
|
-
constructor(label, logger) {
|
456
|
-
this.trace = void 0;
|
457
|
-
this.debug = void 0;
|
458
|
-
this.log = void 0;
|
459
|
-
this.warn = void 0;
|
460
|
-
this.info = void 0;
|
461
|
-
this.error = void 0;
|
462
|
-
const lb = `[${label}]:`;
|
463
|
-
this.trace = noop;
|
464
|
-
this.debug = logger.debug.bind(null, lb);
|
465
|
-
this.log = logger.log.bind(null, lb);
|
466
|
-
this.warn = logger.warn.bind(null, lb);
|
467
|
-
this.info = logger.info.bind(null, lb);
|
468
|
-
this.error = logger.error.bind(null, lb);
|
469
|
-
}
|
470
|
-
}
|
471
|
-
const noop = function noop() {};
|
472
|
-
const fakeLogger = {
|
473
|
-
trace: noop,
|
474
|
-
debug: noop,
|
475
|
-
log: noop,
|
476
|
-
warn: noop,
|
477
|
-
info: noop,
|
478
|
-
error: noop
|
479
|
-
};
|
480
|
-
function createLogger() {
|
481
|
-
return _extends({}, fakeLogger);
|
482
|
-
}
|
483
|
-
|
484
|
-
// let lastCallTime;
|
485
|
-
// function formatMsgWithTimeInfo(type, msg) {
|
486
|
-
// const now = Date.now();
|
487
|
-
// const diff = lastCallTime ? '+' + (now - lastCallTime) : '0';
|
488
|
-
// lastCallTime = now;
|
489
|
-
// msg = (new Date(now)).toISOString() + ' | [' + type + '] > ' + msg + ' ( ' + diff + ' ms )';
|
490
|
-
// return msg;
|
491
|
-
// }
|
492
|
-
|
493
|
-
function consolePrintFn(type, id) {
|
494
|
-
const func = self.console[type];
|
495
|
-
return func ? func.bind(self.console, `${id ? '[' + id + '] ' : ''}[${type}] >`) : noop;
|
496
|
-
}
|
497
|
-
function getLoggerFn(key, debugConfig, id) {
|
498
|
-
return debugConfig[key] ? debugConfig[key].bind(debugConfig) : consolePrintFn(key, id);
|
499
|
-
}
|
500
|
-
const exportedLogger = createLogger();
|
501
|
-
function enableLogs(debugConfig, context, id) {
|
502
|
-
// check that console is available
|
503
|
-
const newLogger = createLogger();
|
504
|
-
if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
|
505
|
-
const keys = [
|
506
|
-
// Remove out from list here to hard-disable a log-level
|
507
|
-
// 'trace',
|
508
|
-
'debug', 'log', 'info', 'warn', 'error'];
|
509
|
-
keys.forEach(key => {
|
510
|
-
newLogger[key] = getLoggerFn(key, debugConfig, id);
|
511
|
-
});
|
512
|
-
// Some browsers don't allow to use bind on console object anyway
|
513
|
-
// fallback to default if needed
|
514
|
-
try {
|
515
|
-
newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.5.6-0.canary.9999"}`);
|
516
|
-
} catch (e) {
|
517
|
-
/* log fn threw an exception. All logger methods are no-ops. */
|
518
|
-
return createLogger();
|
519
|
-
}
|
520
|
-
}
|
521
|
-
// global exported logger uses the log methods from last call to `enableLogs`
|
522
|
-
_extends(exportedLogger, newLogger);
|
523
|
-
return newLogger;
|
524
|
-
}
|
525
|
-
const logger = exportedLogger;
|
526
|
-
|
527
505
|
// Avoid exporting const enum so that these values can be inlined
|
528
506
|
|
529
507
|
function isDateRangeCueAttribute(attrName) {
|
@@ -1058,26 +1036,6 @@ function strToUtf8array(str) {
|
|
1058
1036
|
return Uint8Array.from(unescape(encodeURIComponent(str)), c => c.charCodeAt(0));
|
1059
1037
|
}
|
1060
1038
|
|
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
|
-
|
1081
1039
|
/** returns `undefined` is `self` is missing, e.g. in node */
|
1082
1040
|
const optionalSelf = typeof self !== 'undefined' ? self : undefined;
|
1083
1041
|
|
@@ -1663,11 +1621,13 @@ function parseSegmentIndex(sidx) {
|
|
1663
1621
|
let earliestPresentationTime = 0;
|
1664
1622
|
let firstOffset = 0;
|
1665
1623
|
if (version === 0) {
|
1666
|
-
earliestPresentationTime = readUint32(sidx, index
|
1667
|
-
firstOffset = readUint32(sidx, index
|
1624
|
+
earliestPresentationTime = readUint32(sidx, index);
|
1625
|
+
firstOffset = readUint32(sidx, index + 4);
|
1626
|
+
index += 8;
|
1668
1627
|
} else {
|
1669
|
-
earliestPresentationTime = readUint64(sidx, index
|
1670
|
-
firstOffset = readUint64(sidx, index
|
1628
|
+
earliestPresentationTime = readUint64(sidx, index);
|
1629
|
+
firstOffset = readUint64(sidx, index + 8);
|
1630
|
+
index += 16;
|
1671
1631
|
}
|
1672
1632
|
|
1673
1633
|
// skip reserved
|
@@ -2728,12 +2688,12 @@ class LevelKey {
|
|
2728
2688
|
this.keyFormatVersions = formatversions;
|
2729
2689
|
this.iv = iv;
|
2730
2690
|
this.encrypted = method ? method !== 'NONE' : false;
|
2731
|
-
this.isCommonEncryption = this.encrypted &&
|
2691
|
+
this.isCommonEncryption = this.encrypted && method !== 'AES-128';
|
2732
2692
|
}
|
2733
2693
|
isSupported() {
|
2734
2694
|
// If it's Segment encryption or No encryption, just select that key system
|
2735
2695
|
if (this.method) {
|
2736
|
-
if (
|
2696
|
+
if (this.method === 'AES-128' || this.method === 'NONE') {
|
2737
2697
|
return true;
|
2738
2698
|
}
|
2739
2699
|
if (this.keyFormat === 'identity') {
|
@@ -2755,13 +2715,14 @@ class LevelKey {
|
|
2755
2715
|
if (!this.encrypted || !this.uri) {
|
2756
2716
|
return null;
|
2757
2717
|
}
|
2758
|
-
if (
|
2718
|
+
if (this.method === 'AES-128' && this.uri && !this.iv) {
|
2759
2719
|
if (typeof sn !== 'number') {
|
2760
2720
|
// We are fetching decryption data for a initialization segment
|
2761
|
-
// If the segment was encrypted with AES-128
|
2721
|
+
// If the segment was encrypted with AES-128
|
2762
2722
|
// It must have an IV defined. We cannot substitute the Segment Number in.
|
2763
|
-
|
2764
|
-
|
2723
|
+
if (this.method === 'AES-128' && !this.iv) {
|
2724
|
+
logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
|
2725
|
+
}
|
2765
2726
|
// Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
|
2766
2727
|
sn = 0;
|
2767
2728
|
}
|
@@ -3040,28 +3001,23 @@ function getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource =
|
|
3040
3001
|
if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) {
|
3041
3002
|
return CODEC_COMPATIBLE_NAMES[lowerCaseCodec];
|
3042
3003
|
}
|
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
|
3043
3008
|
const codecsToCheck = {
|
3044
|
-
// Idealy fLaC and Opus would be first (spec-compliant) but
|
3045
|
-
// some browsers will report that fLaC is supported then fail.
|
3046
|
-
// see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
|
3047
3009
|
flac: ['flac', 'fLaC', 'FLAC'],
|
3048
|
-
opus: ['opus', 'Opus']
|
3049
|
-
// Replace audio codec info if browser does not support mp4a.40.34,
|
3050
|
-
// and demuxer can fallback to 'audio/mpeg' or 'audio/mp4;codecs="mp3"'
|
3051
|
-
'mp4a.40.34': ['mp3']
|
3010
|
+
opus: ['opus', 'Opus']
|
3052
3011
|
}[lowerCaseCodec];
|
3053
3012
|
for (let i = 0; i < codecsToCheck.length; i++) {
|
3054
|
-
var _getMediaSource;
|
3055
3013
|
if (isCodecMediaSourceSupported(codecsToCheck[i], 'audio', preferManagedMediaSource)) {
|
3056
3014
|
CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
|
3057
3015
|
return codecsToCheck[i];
|
3058
|
-
} else if (codecsToCheck[i] === 'mp3' && (_getMediaSource = getMediaSource(preferManagedMediaSource)) != null && _getMediaSource.isTypeSupported('audio/mpeg')) {
|
3059
|
-
return '';
|
3060
3016
|
}
|
3061
3017
|
}
|
3062
3018
|
return lowerCaseCodec;
|
3063
3019
|
}
|
3064
|
-
const AUDIO_CODEC_REGEXP = /flac|opus
|
3020
|
+
const AUDIO_CODEC_REGEXP = /flac|opus/i;
|
3065
3021
|
function getCodecCompatibleName(codec, preferManagedMediaSource = true) {
|
3066
3022
|
return codec.replace(AUDIO_CODEC_REGEXP, m => getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource));
|
3067
3023
|
}
|
@@ -3084,16 +3040,6 @@ function convertAVC1ToAVCOTI(codec) {
|
|
3084
3040
|
}
|
3085
3041
|
return codec;
|
3086
3042
|
}
|
3087
|
-
function getM2TSSupportedAudioTypes(preferManagedMediaSource) {
|
3088
|
-
const MediaSource = getMediaSource(preferManagedMediaSource) || {
|
3089
|
-
isTypeSupported: () => false
|
3090
|
-
};
|
3091
|
-
return {
|
3092
|
-
mpeg: MediaSource.isTypeSupported('audio/mpeg'),
|
3093
|
-
mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
|
3094
|
-
ac3: MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
|
3095
|
-
};
|
3096
|
-
}
|
3097
3043
|
|
3098
3044
|
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;
|
3099
3045
|
const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g;
|
@@ -3943,10 +3889,10 @@ class PlaylistLoader {
|
|
3943
3889
|
const loaderContext = loader.context;
|
3944
3890
|
if (loaderContext && loaderContext.url === context.url && loaderContext.level === context.level) {
|
3945
3891
|
// same URL can't overlap
|
3946
|
-
|
3892
|
+
logger.trace('[playlist-loader]: playlist request ongoing');
|
3947
3893
|
return;
|
3948
3894
|
}
|
3949
|
-
|
3895
|
+
logger.log(`[playlist-loader]: aborting previous loader for type: ${context.type}`);
|
3950
3896
|
loader.abort();
|
3951
3897
|
}
|
3952
3898
|
|
@@ -4056,7 +4002,7 @@ class PlaylistLoader {
|
|
4056
4002
|
// alt audio rendition in which quality levels (main)
|
4057
4003
|
// contains both audio+video. but with mixed audio track not signaled
|
4058
4004
|
if (!embeddedAudioFound && levels[0].audioCodec && !levels[0].attrs.AUDIO) {
|
4059
|
-
|
4005
|
+
logger.log('[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one');
|
4060
4006
|
audioTracks.unshift({
|
4061
4007
|
type: 'main',
|
4062
4008
|
name: 'main',
|
@@ -4155,7 +4101,7 @@ class PlaylistLoader {
|
|
4155
4101
|
message += ` id: ${context.id} group-id: "${context.groupId}"`;
|
4156
4102
|
}
|
4157
4103
|
const error = new Error(message);
|
4158
|
-
|
4104
|
+
logger.warn(`[playlist-loader]: ${message}`);
|
4159
4105
|
let details = ErrorDetails.UNKNOWN;
|
4160
4106
|
let fatal = false;
|
4161
4107
|
const loader = this.getInternalLoader(context);
|
@@ -4760,47 +4706,7 @@ class LatencyController {
|
|
4760
4706
|
this.currentTime = 0;
|
4761
4707
|
this.stallCount = 0;
|
4762
4708
|
this._latency = null;
|
4763
|
-
this.
|
4764
|
-
const {
|
4765
|
-
media,
|
4766
|
-
levelDetails
|
4767
|
-
} = this;
|
4768
|
-
if (!media || !levelDetails) {
|
4769
|
-
return;
|
4770
|
-
}
|
4771
|
-
this.currentTime = media.currentTime;
|
4772
|
-
const latency = this.computeLatency();
|
4773
|
-
if (latency === null) {
|
4774
|
-
return;
|
4775
|
-
}
|
4776
|
-
this._latency = latency;
|
4777
|
-
|
4778
|
-
// Adapt playbackRate to meet target latency in low-latency mode
|
4779
|
-
const {
|
4780
|
-
lowLatencyMode,
|
4781
|
-
maxLiveSyncPlaybackRate
|
4782
|
-
} = this.config;
|
4783
|
-
if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
|
4784
|
-
return;
|
4785
|
-
}
|
4786
|
-
const targetLatency = this.targetLatency;
|
4787
|
-
if (targetLatency === null) {
|
4788
|
-
return;
|
4789
|
-
}
|
4790
|
-
const distanceFromTarget = latency - targetLatency;
|
4791
|
-
// Only adjust playbackRate when within one target duration of targetLatency
|
4792
|
-
// and more than one second from under-buffering.
|
4793
|
-
// Playback further than one target duration from target can be considered DVR playback.
|
4794
|
-
const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
|
4795
|
-
const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
|
4796
|
-
if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
|
4797
|
-
const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
|
4798
|
-
const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
|
4799
|
-
media.playbackRate = Math.min(max, Math.max(1, rate));
|
4800
|
-
} else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
|
4801
|
-
media.playbackRate = 1;
|
4802
|
-
}
|
4803
|
-
};
|
4709
|
+
this.timeupdateHandler = () => this.timeupdate();
|
4804
4710
|
this.hls = hls;
|
4805
4711
|
this.config = hls.config;
|
4806
4712
|
this.registerListeners();
|
@@ -4892,7 +4798,7 @@ class LatencyController {
|
|
4892
4798
|
this.onMediaDetaching();
|
4893
4799
|
this.levelDetails = null;
|
4894
4800
|
// @ts-ignore
|
4895
|
-
this.hls = null;
|
4801
|
+
this.hls = this.timeupdateHandler = null;
|
4896
4802
|
}
|
4897
4803
|
registerListeners() {
|
4898
4804
|
this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
@@ -4910,11 +4816,11 @@ class LatencyController {
|
|
4910
4816
|
}
|
4911
4817
|
onMediaAttached(event, data) {
|
4912
4818
|
this.media = data.media;
|
4913
|
-
this.media.addEventListener('timeupdate', this.
|
4819
|
+
this.media.addEventListener('timeupdate', this.timeupdateHandler);
|
4914
4820
|
}
|
4915
4821
|
onMediaDetaching() {
|
4916
4822
|
if (this.media) {
|
4917
|
-
this.media.removeEventListener('timeupdate', this.
|
4823
|
+
this.media.removeEventListener('timeupdate', this.timeupdateHandler);
|
4918
4824
|
this.media = null;
|
4919
4825
|
}
|
4920
4826
|
}
|
@@ -4928,10 +4834,10 @@ class LatencyController {
|
|
4928
4834
|
}) {
|
4929
4835
|
this.levelDetails = details;
|
4930
4836
|
if (details.advanced) {
|
4931
|
-
this.
|
4837
|
+
this.timeupdate();
|
4932
4838
|
}
|
4933
4839
|
if (!details.live && this.media) {
|
4934
|
-
this.media.removeEventListener('timeupdate', this.
|
4840
|
+
this.media.removeEventListener('timeupdate', this.timeupdateHandler);
|
4935
4841
|
}
|
4936
4842
|
}
|
4937
4843
|
onError(event, data) {
|
@@ -4941,7 +4847,48 @@ class LatencyController {
|
|
4941
4847
|
}
|
4942
4848
|
this.stallCount++;
|
4943
4849
|
if ((_this$levelDetails = this.levelDetails) != null && _this$levelDetails.live) {
|
4944
|
-
|
4850
|
+
logger.warn('[playback-rate-controller]: Stall detected, adjusting target latency');
|
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;
|
4945
4892
|
}
|
4946
4893
|
}
|
4947
4894
|
estimateLiveEdge() {
|
@@ -5713,13 +5660,18 @@ var ErrorActionFlags = {
|
|
5713
5660
|
MoveAllAlternatesMatchingHDCP: 2,
|
5714
5661
|
SwitchToSDR: 4
|
5715
5662
|
}; // Reserved for future use
|
5716
|
-
class ErrorController
|
5663
|
+
class ErrorController {
|
5717
5664
|
constructor(hls) {
|
5718
|
-
super('error-controller', hls.logger);
|
5719
5665
|
this.hls = void 0;
|
5720
5666
|
this.playlistError = 0;
|
5721
5667
|
this.penalizedRenditions = {};
|
5668
|
+
this.log = void 0;
|
5669
|
+
this.warn = void 0;
|
5670
|
+
this.error = void 0;
|
5722
5671
|
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]:`);
|
5723
5675
|
this.registerListeners();
|
5724
5676
|
}
|
5725
5677
|
registerListeners() {
|
@@ -6071,13 +6023,16 @@ class ErrorController extends Logger {
|
|
6071
6023
|
}
|
6072
6024
|
}
|
6073
6025
|
|
6074
|
-
class BasePlaylistController
|
6026
|
+
class BasePlaylistController {
|
6075
6027
|
constructor(hls, logPrefix) {
|
6076
|
-
super(logPrefix, hls.logger);
|
6077
6028
|
this.hls = void 0;
|
6078
6029
|
this.timer = -1;
|
6079
6030
|
this.requestScheduled = -1;
|
6080
6031
|
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}:`);
|
6081
6036
|
this.hls = hls;
|
6082
6037
|
}
|
6083
6038
|
destroy() {
|
@@ -6110,7 +6065,7 @@ class BasePlaylistController extends Logger {
|
|
6110
6065
|
try {
|
6111
6066
|
uri = new self.URL(attr.URI, previous.url).href;
|
6112
6067
|
} catch (error) {
|
6113
|
-
|
6068
|
+
logger.warn(`Could not construct new URL for Rendition Report: ${error}`);
|
6114
6069
|
uri = attr.URI || '';
|
6115
6070
|
}
|
6116
6071
|
// Use exact match. Otherwise, the last partial match, if any, will be used
|
@@ -6197,12 +6152,7 @@ class BasePlaylistController extends Logger {
|
|
6197
6152
|
const cdnAge = lastAdvanced + details.ageHeader;
|
6198
6153
|
let currentGoal = Math.min(cdnAge - details.partTarget, details.targetduration * 1.5);
|
6199
6154
|
if (currentGoal > 0) {
|
6200
|
-
if (
|
6201
|
-
// Omit segment and part directives when the last response was more than 3 target durations ago,
|
6202
|
-
this.log(`Playlist last advanced ${lastAdvanced.toFixed(2)}s ago. Omitting segment and part directives.`);
|
6203
|
-
msn = undefined;
|
6204
|
-
part = undefined;
|
6205
|
-
} else if (previousDetails != null && previousDetails.tuneInGoal && cdnAge - details.partTarget > previousDetails.tuneInGoal) {
|
6155
|
+
if (previousDetails && currentGoal > previousDetails.tuneInGoal) {
|
6206
6156
|
// If we attempted to get the next or latest playlist update, but currentGoal increased,
|
6207
6157
|
// then we either can't catchup, or the "age" header cannot be trusted.
|
6208
6158
|
this.warn(`CDN Tune-in goal increased from: ${previousDetails.tuneInGoal} to: ${currentGoal} with playlist age: ${details.age}`);
|
@@ -6874,9 +6824,8 @@ function searchDownAndUpList(arr, searchIndex, predicate) {
|
|
6874
6824
|
return -1;
|
6875
6825
|
}
|
6876
6826
|
|
6877
|
-
class AbrController
|
6827
|
+
class AbrController {
|
6878
6828
|
constructor(_hls) {
|
6879
|
-
super('abr', _hls.logger);
|
6880
6829
|
this.hls = void 0;
|
6881
6830
|
this.lastLevelLoadSec = 0;
|
6882
6831
|
this.lastLoadedFragLevel = -1;
|
@@ -6990,7 +6939,7 @@ class AbrController extends Logger {
|
|
6990
6939
|
this.resetEstimator(nextLoadLevelBitrate);
|
6991
6940
|
}
|
6992
6941
|
this.clearTimer();
|
6993
|
-
|
6942
|
+
logger.warn(`[abr] Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
|
6994
6943
|
Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s
|
6995
6944
|
Estimated load time for current fragment: ${fragLoadedDelay.toFixed(3)} s
|
6996
6945
|
Estimated load time for down switch fragment: ${fragLevelNextLoadedDelay.toFixed(3)} s
|
@@ -7010,7 +6959,7 @@ class AbrController extends Logger {
|
|
7010
6959
|
}
|
7011
6960
|
resetEstimator(abrEwmaDefaultEstimate) {
|
7012
6961
|
if (abrEwmaDefaultEstimate) {
|
7013
|
-
|
6962
|
+
logger.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
|
7014
6963
|
this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate;
|
7015
6964
|
}
|
7016
6965
|
this.firstSelection = -1;
|
@@ -7242,7 +7191,7 @@ class AbrController extends Logger {
|
|
7242
7191
|
}
|
7243
7192
|
const firstLevel = this.hls.firstLevel;
|
7244
7193
|
const clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel);
|
7245
|
-
|
7194
|
+
logger.warn(`[abr] Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
|
7246
7195
|
return clamped;
|
7247
7196
|
}
|
7248
7197
|
get forcedAutoLevel() {
|
@@ -7280,8 +7229,7 @@ class AbrController extends Logger {
|
|
7280
7229
|
return nextABRAutoLevel;
|
7281
7230
|
}
|
7282
7231
|
getAutoLevelKey() {
|
7283
|
-
|
7284
|
-
return `${this.getBwEstimate()}_${(_this$hls$mainForward = this.hls.mainForwardBufferInfo) == null ? void 0 : _this$hls$mainForward.len}`;
|
7232
|
+
return `${this.getBwEstimate()}_${this.getStarvationDelay().toFixed(2)}`;
|
7285
7233
|
}
|
7286
7234
|
getNextABRAutoLevel() {
|
7287
7235
|
const {
|
@@ -7292,18 +7240,12 @@ class AbrController extends Logger {
|
|
7292
7240
|
const {
|
7293
7241
|
maxAutoLevel,
|
7294
7242
|
config,
|
7295
|
-
minAutoLevel
|
7296
|
-
media
|
7243
|
+
minAutoLevel
|
7297
7244
|
} = hls;
|
7298
7245
|
const currentFragDuration = partCurrent ? partCurrent.duration : fragCurrent ? fragCurrent.duration : 0;
|
7299
|
-
|
7300
|
-
// playbackRate is the absolute value of the playback rate; if media.playbackRate is 0, we use 1 to load as
|
7301
|
-
// if we're playing back at the normal rate.
|
7302
|
-
const playbackRate = media && media.playbackRate !== 0 ? Math.abs(media.playbackRate) : 1.0;
|
7303
7246
|
const avgbw = this.getBwEstimate();
|
7304
7247
|
// bufferStarvationDelay is the wall-clock time left until the playback buffer is exhausted.
|
7305
|
-
const
|
7306
|
-
const bufferStarvationDelay = (bufferInfo ? bufferInfo.len : 0) / playbackRate;
|
7248
|
+
const bufferStarvationDelay = this.getStarvationDelay();
|
7307
7249
|
let bwFactor = config.abrBandWidthFactor;
|
7308
7250
|
let bwUpFactor = config.abrBandWidthUpFactor;
|
7309
7251
|
|
@@ -7327,13 +7269,13 @@ class AbrController extends Logger {
|
|
7327
7269
|
// cap maxLoadingDelay and ensure it is not bigger 'than bitrate test' frag duration
|
7328
7270
|
const maxLoadingDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxLoadingDelay) : config.maxLoadingDelay;
|
7329
7271
|
maxStarvationDelay = maxLoadingDelay - bitrateTestDelay;
|
7330
|
-
|
7272
|
+
logger.info(`[abr] bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
|
7331
7273
|
// don't use conservative factor on bitrate test
|
7332
7274
|
bwFactor = bwUpFactor = 1;
|
7333
7275
|
}
|
7334
7276
|
}
|
7335
7277
|
const bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor);
|
7336
|
-
|
7278
|
+
logger.info(`[abr] ${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
|
7337
7279
|
if (bestLevel > -1) {
|
7338
7280
|
return bestLevel;
|
7339
7281
|
}
|
@@ -7346,6 +7288,18 @@ class AbrController extends Logger {
|
|
7346
7288
|
// or if bitrate is not lower, continue to use loadLevel
|
7347
7289
|
return hls.loadLevel;
|
7348
7290
|
}
|
7291
|
+
getStarvationDelay() {
|
7292
|
+
const hls = this.hls;
|
7293
|
+
const media = hls.media;
|
7294
|
+
if (!media) {
|
7295
|
+
return Infinity;
|
7296
|
+
}
|
7297
|
+
// playbackRate is the absolute value of the playback rate; if media.playbackRate is 0, we use 1 to load as
|
7298
|
+
// if we're playing back at the normal rate.
|
7299
|
+
const playbackRate = media && media.playbackRate !== 0 ? Math.abs(media.playbackRate) : 1.0;
|
7300
|
+
const bufferInfo = hls.mainForwardBufferInfo;
|
7301
|
+
return (bufferInfo ? bufferInfo.len : 0) / playbackRate;
|
7302
|
+
}
|
7349
7303
|
getBwEstimate() {
|
7350
7304
|
return this.bwEstimator.canEstimate() ? this.bwEstimator.getEstimate() : this.hls.config.abrEwmaDefaultEstimate;
|
7351
7305
|
}
|
@@ -7395,7 +7349,7 @@ class AbrController extends Logger {
|
|
7395
7349
|
currentVideoRange = preferHDR ? videoRanges[videoRanges.length - 1] : videoRanges[0];
|
7396
7350
|
currentFrameRate = minFramerate;
|
7397
7351
|
currentBw = Math.max(currentBw, minBitrate);
|
7398
|
-
|
7352
|
+
logger.log(`[abr] picked start tier ${JSON.stringify(startTier)}`);
|
7399
7353
|
} else {
|
7400
7354
|
currentCodecSet = level == null ? void 0 : level.codecSet;
|
7401
7355
|
currentVideoRange = level == null ? void 0 : level.videoRange;
|
@@ -7415,15 +7369,18 @@ class AbrController extends Logger {
|
|
7415
7369
|
if (typeof (mediaCapabilities == null ? void 0 : mediaCapabilities.decodingInfo) === 'function' && requiresMediaCapabilitiesDecodingInfo(levelInfo, audioTracksByGroup, currentVideoRange, currentFrameRate, currentBw, audioPreference)) {
|
7416
7370
|
levelInfo.supportedPromise = getMediaDecodingInfoPromise(levelInfo, audioTracksByGroup, mediaCapabilities);
|
7417
7371
|
levelInfo.supportedPromise.then(decodingInfo => {
|
7372
|
+
if (!this.hls) {
|
7373
|
+
return;
|
7374
|
+
}
|
7418
7375
|
levelInfo.supportedResult = decodingInfo;
|
7419
7376
|
const levels = this.hls.levels;
|
7420
7377
|
const index = levels.indexOf(levelInfo);
|
7421
7378
|
if (decodingInfo.error) {
|
7422
|
-
|
7379
|
+
logger.warn(`[abr] MediaCapabilities decodingInfo error: "${decodingInfo.error}" for level ${index} ${JSON.stringify(decodingInfo)}`);
|
7423
7380
|
} else if (!decodingInfo.supported) {
|
7424
|
-
|
7381
|
+
logger.warn(`[abr] Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify(decodingInfo)}`);
|
7425
7382
|
if (index > -1 && levels.length > 1) {
|
7426
|
-
|
7383
|
+
logger.log(`[abr] Removing unsupported level ${index}`);
|
7427
7384
|
this.hls.removeLevel(index);
|
7428
7385
|
}
|
7429
7386
|
}
|
@@ -7470,9 +7427,9 @@ class AbrController extends Logger {
|
|
7470
7427
|
const forcedAutoLevel = this.forcedAutoLevel;
|
7471
7428
|
if (i !== loadLevel && (forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)) {
|
7472
7429
|
if (levelsSkipped.length) {
|
7473
|
-
|
7430
|
+
logger.trace(`[abr] Skipped level(s) ${levelsSkipped.join(',')} of ${maxAutoLevel} max with CODECS and VIDEO-RANGE:"${levels[levelsSkipped[0]].codecs}" ${levels[levelsSkipped[0]].videoRange}; not compatible with "${level.codecs}" ${currentVideoRange}`);
|
7474
7431
|
}
|
7475
|
-
|
7432
|
+
logger.info(`[abr] switch candidate:${selectionBaseLevel}->${i} adjustedbw(${Math.round(adjustedbw)})-bitrate=${Math.round(adjustedbw - bitrate)} ttfb:${ttfbEstimateSec.toFixed(1)} avgDuration:${avgDuration.toFixed(1)} maxFetchDuration:${maxFetchDuration.toFixed(1)} fetchDuration:${fetchDuration.toFixed(1)} firstSelection:${firstSelection} codecSet:${currentCodecSet} videoRange:${currentVideoRange} hls.loadLevel:${loadLevel}`);
|
7476
7433
|
}
|
7477
7434
|
if (firstSelection) {
|
7478
7435
|
this.firstSelection = i;
|
@@ -7526,9 +7483,8 @@ class AbrController extends Logger {
|
|
7526
7483
|
* we are limiting the task execution per call stack to exactly one, but scheduling/post-poning further
|
7527
7484
|
* task processing on the next main loop iteration (also known as "next tick" in the Node/JS runtime lingo).
|
7528
7485
|
*/
|
7529
|
-
class TaskLoop
|
7530
|
-
constructor(
|
7531
|
-
super(label, logger);
|
7486
|
+
class TaskLoop {
|
7487
|
+
constructor() {
|
7532
7488
|
this._boundTick = void 0;
|
7533
7489
|
this._tickTimer = null;
|
7534
7490
|
this._tickInterval = null;
|
@@ -8619,8 +8575,8 @@ function createLoaderContext(frag, part = null) {
|
|
8619
8575
|
var _frag$decryptdata;
|
8620
8576
|
let byteRangeStart = start;
|
8621
8577
|
let byteRangeEnd = end;
|
8622
|
-
if (frag.sn === 'initSegment' &&
|
8623
|
-
// MAP segment encrypted with method 'AES-128'
|
8578
|
+
if (frag.sn === 'initSegment' && ((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method) === 'AES-128') {
|
8579
|
+
// MAP segment encrypted with method 'AES-128', when served with HTTP Range,
|
8624
8580
|
// has the unencrypted size specified in the range.
|
8625
8581
|
// Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
|
8626
8582
|
const fragmentLen = end - start;
|
@@ -8653,9 +8609,6 @@ function createGapLoadError(frag, part) {
|
|
8653
8609
|
(part ? part : frag).stats.aborted = true;
|
8654
8610
|
return new LoadError(errorData);
|
8655
8611
|
}
|
8656
|
-
function isMethodFullSegmentAesCbc(method) {
|
8657
|
-
return method === 'AES-128' || method === 'AES-256';
|
8658
|
-
}
|
8659
8612
|
class LoadError extends Error {
|
8660
8613
|
constructor(data) {
|
8661
8614
|
super(data.error.message);
|
@@ -8665,61 +8618,33 @@ class LoadError extends Error {
|
|
8665
8618
|
}
|
8666
8619
|
|
8667
8620
|
class AESCrypto {
|
8668
|
-
constructor(subtle, iv
|
8621
|
+
constructor(subtle, iv) {
|
8669
8622
|
this.subtle = void 0;
|
8670
8623
|
this.aesIV = void 0;
|
8671
|
-
this.aesMode = void 0;
|
8672
8624
|
this.subtle = subtle;
|
8673
8625
|
this.aesIV = iv;
|
8674
|
-
this.aesMode = aesMode;
|
8675
8626
|
}
|
8676
8627
|
decrypt(data, key) {
|
8677
|
-
|
8678
|
-
|
8679
|
-
|
8680
|
-
|
8681
|
-
iv: this.aesIV
|
8682
|
-
}, key, data);
|
8683
|
-
case DecrypterAesMode.ctr:
|
8684
|
-
return this.subtle.decrypt({
|
8685
|
-
name: 'AES-CTR',
|
8686
|
-
counter: this.aesIV,
|
8687
|
-
length: 64
|
8688
|
-
},
|
8689
|
-
//64 : NIST SP800-38A standard suggests that the counter should occupy half of the counter block
|
8690
|
-
key, data);
|
8691
|
-
default:
|
8692
|
-
throw new Error(`[AESCrypto] invalid aes mode ${this.aesMode}`);
|
8693
|
-
}
|
8628
|
+
return this.subtle.decrypt({
|
8629
|
+
name: 'AES-CBC',
|
8630
|
+
iv: this.aesIV
|
8631
|
+
}, key, data);
|
8694
8632
|
}
|
8695
8633
|
}
|
8696
8634
|
|
8697
8635
|
class FastAESKey {
|
8698
|
-
constructor(subtle, key
|
8636
|
+
constructor(subtle, key) {
|
8699
8637
|
this.subtle = void 0;
|
8700
8638
|
this.key = void 0;
|
8701
|
-
this.aesMode = void 0;
|
8702
8639
|
this.subtle = subtle;
|
8703
8640
|
this.key = key;
|
8704
|
-
this.aesMode = aesMode;
|
8705
8641
|
}
|
8706
8642
|
expandKey() {
|
8707
|
-
const subtleAlgoName = getSubtleAlgoName(this.aesMode);
|
8708
8643
|
return this.subtle.importKey('raw', this.key, {
|
8709
|
-
name:
|
8644
|
+
name: 'AES-CBC'
|
8710
8645
|
}, false, ['encrypt', 'decrypt']);
|
8711
8646
|
}
|
8712
8647
|
}
|
8713
|
-
function getSubtleAlgoName(aesMode) {
|
8714
|
-
switch (aesMode) {
|
8715
|
-
case DecrypterAesMode.cbc:
|
8716
|
-
return 'AES-CBC';
|
8717
|
-
case DecrypterAesMode.ctr:
|
8718
|
-
return 'AES-CTR';
|
8719
|
-
default:
|
8720
|
-
throw new Error(`[FastAESKey] invalid aes mode ${aesMode}`);
|
8721
|
-
}
|
8722
|
-
}
|
8723
8648
|
|
8724
8649
|
// PKCS7
|
8725
8650
|
function removePadding(array) {
|
@@ -8969,8 +8894,7 @@ class Decrypter {
|
|
8969
8894
|
this.currentIV = null;
|
8970
8895
|
this.currentResult = null;
|
8971
8896
|
this.useSoftware = void 0;
|
8972
|
-
this.
|
8973
|
-
this.enableSoftwareAES = config.enableSoftwareAES;
|
8897
|
+
this.useSoftware = config.enableSoftwareAES;
|
8974
8898
|
this.removePKCS7Padding = removePKCS7Padding;
|
8975
8899
|
// built in decryptor expects PKCS7 padding
|
8976
8900
|
if (removePKCS7Padding) {
|
@@ -8983,7 +8907,9 @@ class Decrypter {
|
|
8983
8907
|
/* no-op */
|
8984
8908
|
}
|
8985
8909
|
}
|
8986
|
-
|
8910
|
+
if (this.subtle === null) {
|
8911
|
+
this.useSoftware = true;
|
8912
|
+
}
|
8987
8913
|
}
|
8988
8914
|
destroy() {
|
8989
8915
|
this.subtle = null;
|
@@ -9021,10 +8947,10 @@ class Decrypter {
|
|
9021
8947
|
this.softwareDecrypter = null;
|
9022
8948
|
}
|
9023
8949
|
}
|
9024
|
-
decrypt(data, key, iv
|
8950
|
+
decrypt(data, key, iv) {
|
9025
8951
|
if (this.useSoftware) {
|
9026
8952
|
return new Promise((resolve, reject) => {
|
9027
|
-
this.softwareDecrypt(new Uint8Array(data), key, iv
|
8953
|
+
this.softwareDecrypt(new Uint8Array(data), key, iv);
|
9028
8954
|
const decryptResult = this.flush();
|
9029
8955
|
if (decryptResult) {
|
9030
8956
|
resolve(decryptResult.buffer);
|
@@ -9033,21 +8959,17 @@ class Decrypter {
|
|
9033
8959
|
}
|
9034
8960
|
});
|
9035
8961
|
}
|
9036
|
-
return this.webCryptoDecrypt(new Uint8Array(data), key, iv
|
8962
|
+
return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
|
9037
8963
|
}
|
9038
8964
|
|
9039
8965
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
9040
8966
|
// data is handled in the flush() call
|
9041
|
-
softwareDecrypt(data, key, iv
|
8967
|
+
softwareDecrypt(data, key, iv) {
|
9042
8968
|
const {
|
9043
8969
|
currentIV,
|
9044
8970
|
currentResult,
|
9045
8971
|
remainderData
|
9046
8972
|
} = this;
|
9047
|
-
if (aesMode !== DecrypterAesMode.cbc || key.byteLength !== 16) {
|
9048
|
-
logger.warn('SoftwareDecrypt: can only handle AES-128-CBC');
|
9049
|
-
return null;
|
9050
|
-
}
|
9051
8973
|
this.logOnce('JS AES decrypt');
|
9052
8974
|
// The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
|
9053
8975
|
// This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached
|
@@ -9080,11 +9002,11 @@ class Decrypter {
|
|
9080
9002
|
}
|
9081
9003
|
return result;
|
9082
9004
|
}
|
9083
|
-
webCryptoDecrypt(data, key, iv
|
9005
|
+
webCryptoDecrypt(data, key, iv) {
|
9084
9006
|
const subtle = this.subtle;
|
9085
9007
|
if (this.key !== key || !this.fastAesKey) {
|
9086
9008
|
this.key = key;
|
9087
|
-
this.fastAesKey = new FastAESKey(subtle, key
|
9009
|
+
this.fastAesKey = new FastAESKey(subtle, key);
|
9088
9010
|
}
|
9089
9011
|
return this.fastAesKey.expandKey().then(aesKey => {
|
9090
9012
|
// decrypt using web crypto
|
@@ -9092,25 +9014,22 @@ class Decrypter {
|
|
9092
9014
|
return Promise.reject(new Error('web crypto not initialized'));
|
9093
9015
|
}
|
9094
9016
|
this.logOnce('WebCrypto AES decrypt');
|
9095
|
-
const crypto = new AESCrypto(subtle, new Uint8Array(iv)
|
9017
|
+
const crypto = new AESCrypto(subtle, new Uint8Array(iv));
|
9096
9018
|
return crypto.decrypt(data.buffer, aesKey);
|
9097
9019
|
}).catch(err => {
|
9098
9020
|
logger.warn(`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`);
|
9099
|
-
return this.onWebCryptoError(data, key, iv
|
9021
|
+
return this.onWebCryptoError(data, key, iv);
|
9100
9022
|
});
|
9101
9023
|
}
|
9102
|
-
onWebCryptoError(data, key, iv
|
9103
|
-
|
9104
|
-
|
9105
|
-
|
9106
|
-
|
9107
|
-
|
9108
|
-
|
9109
|
-
if (decryptResult) {
|
9110
|
-
return decryptResult.buffer;
|
9111
|
-
}
|
9024
|
+
onWebCryptoError(data, key, iv) {
|
9025
|
+
this.useSoftware = true;
|
9026
|
+
this.logEnabled = true;
|
9027
|
+
this.softwareDecrypt(data, key, iv);
|
9028
|
+
const decryptResult = this.flush();
|
9029
|
+
if (decryptResult) {
|
9030
|
+
return decryptResult.buffer;
|
9112
9031
|
}
|
9113
|
-
throw new Error('WebCrypto
|
9032
|
+
throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data');
|
9114
9033
|
}
|
9115
9034
|
getValidChunk(data) {
|
9116
9035
|
let currentChunk = data;
|
@@ -9161,7 +9080,7 @@ const State = {
|
|
9161
9080
|
};
|
9162
9081
|
class BaseStreamController extends TaskLoop {
|
9163
9082
|
constructor(hls, fragmentTracker, keyLoader, logPrefix, playlistType) {
|
9164
|
-
super(
|
9083
|
+
super();
|
9165
9084
|
this.hls = void 0;
|
9166
9085
|
this.fragPrevious = null;
|
9167
9086
|
this.fragCurrent = null;
|
@@ -9186,98 +9105,22 @@ class BaseStreamController extends TaskLoop {
|
|
9186
9105
|
this.startFragRequested = false;
|
9187
9106
|
this.decrypter = void 0;
|
9188
9107
|
this.initPTS = [];
|
9189
|
-
this.
|
9190
|
-
this.
|
9191
|
-
this.
|
9192
|
-
|
9193
|
-
|
9194
|
-
fragCurrent,
|
9195
|
-
media,
|
9196
|
-
mediaBuffer,
|
9197
|
-
state
|
9198
|
-
} = this;
|
9199
|
-
const currentTime = media ? media.currentTime : 0;
|
9200
|
-
const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
|
9201
|
-
this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
|
9202
|
-
if (this.state === State.ENDED) {
|
9203
|
-
this.resetLoadingState();
|
9204
|
-
} else if (fragCurrent) {
|
9205
|
-
// Seeking while frag load is in progress
|
9206
|
-
const tolerance = config.maxFragLookUpTolerance;
|
9207
|
-
const fragStartOffset = fragCurrent.start - tolerance;
|
9208
|
-
const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
|
9209
|
-
// if seeking out of buffered range or into new one
|
9210
|
-
if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
|
9211
|
-
const pastFragment = currentTime > fragEndOffset;
|
9212
|
-
// if the seek position is outside the current fragment range
|
9213
|
-
if (currentTime < fragStartOffset || pastFragment) {
|
9214
|
-
if (pastFragment && fragCurrent.loader) {
|
9215
|
-
this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
|
9216
|
-
fragCurrent.abortRequests();
|
9217
|
-
this.resetLoadingState();
|
9218
|
-
}
|
9219
|
-
this.fragPrevious = null;
|
9220
|
-
}
|
9221
|
-
}
|
9222
|
-
}
|
9223
|
-
if (media) {
|
9224
|
-
// Remove gap fragments
|
9225
|
-
this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
|
9226
|
-
this.lastCurrentTime = currentTime;
|
9227
|
-
if (!this.loadingParts) {
|
9228
|
-
const bufferEnd = Math.max(bufferInfo.end, currentTime);
|
9229
|
-
const shouldLoadParts = this.shouldLoadParts(this.getLevelDetails(), bufferEnd);
|
9230
|
-
if (shouldLoadParts) {
|
9231
|
-
this.log(`LL-Part loading ON after seeking to ${currentTime.toFixed(2)} with buffer @${bufferEnd.toFixed(2)}`);
|
9232
|
-
this.loadingParts = shouldLoadParts;
|
9233
|
-
}
|
9234
|
-
}
|
9235
|
-
}
|
9236
|
-
|
9237
|
-
// in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
|
9238
|
-
if (!this.loadedmetadata && !bufferInfo.len) {
|
9239
|
-
this.nextLoadPosition = this.startPosition = currentTime;
|
9240
|
-
}
|
9241
|
-
|
9242
|
-
// Async tick to speed up processing
|
9243
|
-
this.tickImmediate();
|
9244
|
-
};
|
9245
|
-
this.onMediaEnded = () => {
|
9246
|
-
// reset startPosition and lastCurrentTime to restart playback @ stream beginning
|
9247
|
-
this.startPosition = this.lastCurrentTime = 0;
|
9248
|
-
if (this.playlistType === PlaylistLevelType.MAIN) {
|
9249
|
-
this.hls.trigger(Events.MEDIA_ENDED, {
|
9250
|
-
stalled: false
|
9251
|
-
});
|
9252
|
-
}
|
9253
|
-
};
|
9108
|
+
this.onvseeking = null;
|
9109
|
+
this.onvended = null;
|
9110
|
+
this.logPrefix = '';
|
9111
|
+
this.log = void 0;
|
9112
|
+
this.warn = void 0;
|
9254
9113
|
this.playlistType = playlistType;
|
9114
|
+
this.logPrefix = logPrefix;
|
9115
|
+
this.log = logger.log.bind(logger, `${logPrefix}:`);
|
9116
|
+
this.warn = logger.warn.bind(logger, `${logPrefix}:`);
|
9255
9117
|
this.hls = hls;
|
9256
9118
|
this.fragmentLoader = new FragmentLoader(hls.config);
|
9257
9119
|
this.keyLoader = keyLoader;
|
9258
9120
|
this.fragmentTracker = fragmentTracker;
|
9259
9121
|
this.config = hls.config;
|
9260
9122
|
this.decrypter = new Decrypter(hls.config);
|
9261
|
-
}
|
9262
|
-
registerListeners() {
|
9263
|
-
const {
|
9264
|
-
hls
|
9265
|
-
} = this;
|
9266
|
-
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
9267
|
-
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
9268
|
-
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
9269
9123
|
hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
9270
|
-
hls.on(Events.ERROR, this.onError, this);
|
9271
|
-
}
|
9272
|
-
unregisterListeners() {
|
9273
|
-
const {
|
9274
|
-
hls
|
9275
|
-
} = this;
|
9276
|
-
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
9277
|
-
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
9278
|
-
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
9279
|
-
hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
9280
|
-
hls.off(Events.ERROR, this.onError, this);
|
9281
9124
|
}
|
9282
9125
|
doTick() {
|
9283
9126
|
this.onTickEnd();
|
@@ -9301,12 +9144,6 @@ class BaseStreamController extends TaskLoop {
|
|
9301
9144
|
this.clearNextTick();
|
9302
9145
|
this.state = State.STOPPED;
|
9303
9146
|
}
|
9304
|
-
pauseBuffering() {
|
9305
|
-
this.buffering = false;
|
9306
|
-
}
|
9307
|
-
resumeBuffering() {
|
9308
|
-
this.buffering = true;
|
9309
|
-
}
|
9310
9147
|
_streamEnded(bufferInfo, levelDetails) {
|
9311
9148
|
// If playlist is live, there is another buffered range after the current range, nothing buffered, media is detached,
|
9312
9149
|
// of nothing loading/loaded return false
|
@@ -9337,8 +9174,10 @@ class BaseStreamController extends TaskLoop {
|
|
9337
9174
|
}
|
9338
9175
|
onMediaAttached(event, data) {
|
9339
9176
|
const media = this.media = this.mediaBuffer = data.media;
|
9340
|
-
|
9341
|
-
|
9177
|
+
this.onvseeking = this.onMediaSeeking.bind(this);
|
9178
|
+
this.onvended = this.onMediaEnded.bind(this);
|
9179
|
+
media.addEventListener('seeking', this.onvseeking);
|
9180
|
+
media.addEventListener('ended', this.onvended);
|
9342
9181
|
const config = this.config;
|
9343
9182
|
if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
|
9344
9183
|
this.startLoad(config.startPosition);
|
@@ -9352,9 +9191,10 @@ class BaseStreamController extends TaskLoop {
|
|
9352
9191
|
}
|
9353
9192
|
|
9354
9193
|
// remove video listeners
|
9355
|
-
if (media) {
|
9356
|
-
media.removeEventListener('seeking', this.
|
9357
|
-
media.removeEventListener('ended', this.
|
9194
|
+
if (media && this.onvseeking && this.onvended) {
|
9195
|
+
media.removeEventListener('seeking', this.onvseeking);
|
9196
|
+
media.removeEventListener('ended', this.onvended);
|
9197
|
+
this.onvseeking = this.onvended = null;
|
9358
9198
|
}
|
9359
9199
|
if (this.keyLoader) {
|
9360
9200
|
this.keyLoader.detach();
|
@@ -9364,8 +9204,56 @@ class BaseStreamController extends TaskLoop {
|
|
9364
9204
|
this.fragmentTracker.removeAllFragments();
|
9365
9205
|
this.stopLoad();
|
9366
9206
|
}
|
9367
|
-
|
9368
|
-
|
9207
|
+
onMediaSeeking() {
|
9208
|
+
const {
|
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
|
+
}
|
9369
9257
|
onManifestLoaded(event, data) {
|
9370
9258
|
this.startTimeOffset = data.startTimeOffset;
|
9371
9259
|
this.initPTS = [];
|
@@ -9375,7 +9263,7 @@ class BaseStreamController extends TaskLoop {
|
|
9375
9263
|
this.stopLoad();
|
9376
9264
|
super.onHandlerDestroying();
|
9377
9265
|
// @ts-ignore
|
9378
|
-
this.hls =
|
9266
|
+
this.hls = null;
|
9379
9267
|
}
|
9380
9268
|
onHandlerDestroyed() {
|
9381
9269
|
this.state = State.STOPPED;
|
@@ -9506,10 +9394,10 @@ class BaseStreamController extends TaskLoop {
|
|
9506
9394
|
const decryptData = frag.decryptdata;
|
9507
9395
|
|
9508
9396
|
// check to see if the payload needs to be decrypted
|
9509
|
-
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv &&
|
9397
|
+
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
|
9510
9398
|
const startTime = self.performance.now();
|
9511
9399
|
// decrypt init segment data
|
9512
|
-
return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer
|
9400
|
+
return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
|
9513
9401
|
hls.trigger(Events.ERROR, {
|
9514
9402
|
type: ErrorTypes.MEDIA_ERROR,
|
9515
9403
|
details: ErrorDetails.FRAG_DECRYPT_ERROR,
|
@@ -9621,7 +9509,7 @@ class BaseStreamController extends TaskLoop {
|
|
9621
9509
|
}
|
9622
9510
|
let keyLoadingPromise = null;
|
9623
9511
|
if (frag.encrypted && !((_frag$decryptdata = frag.decryptdata) != null && _frag$decryptdata.key)) {
|
9624
|
-
this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.
|
9512
|
+
this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.logPrefix === '[stream-controller]' ? 'level' : 'track'} ${frag.level}`);
|
9625
9513
|
this.state = State.KEY_LOADING;
|
9626
9514
|
this.fragCurrent = frag;
|
9627
9515
|
keyLoadingPromise = this.keyLoader.load(frag).then(keyLoadedData => {
|
@@ -9642,16 +9530,8 @@ class BaseStreamController extends TaskLoop {
|
|
9642
9530
|
} else if (!frag.encrypted && details.encryptedFragments.length) {
|
9643
9531
|
this.keyLoader.loadClear(frag, details.encryptedFragments);
|
9644
9532
|
}
|
9645
|
-
const fragPrevious = this.fragPrevious;
|
9646
|
-
if (frag.sn !== 'initSegment' && (!fragPrevious || frag.sn !== fragPrevious.sn)) {
|
9647
|
-
const shouldLoadParts = this.shouldLoadParts(level.details, frag.end);
|
9648
|
-
if (shouldLoadParts !== this.loadingParts) {
|
9649
|
-
this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} loading sn ${fragPrevious == null ? void 0 : fragPrevious.sn}->${frag.sn}`);
|
9650
|
-
this.loadingParts = shouldLoadParts;
|
9651
|
-
}
|
9652
|
-
}
|
9653
9533
|
targetBufferTime = Math.max(frag.start, targetBufferTime || 0);
|
9654
|
-
if (this.
|
9534
|
+
if (this.config.lowLatencyMode && frag.sn !== 'initSegment') {
|
9655
9535
|
const partList = details.partList;
|
9656
9536
|
if (partList && progressCallback) {
|
9657
9537
|
if (targetBufferTime > frag.end && details.fragmentHint) {
|
@@ -9660,7 +9540,7 @@ class BaseStreamController extends TaskLoop {
|
|
9660
9540
|
const partIndex = this.getNextPart(partList, frag, targetBufferTime);
|
9661
9541
|
if (partIndex > -1) {
|
9662
9542
|
const part = partList[partIndex];
|
9663
|
-
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.
|
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.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
|
9664
9544
|
this.nextLoadPosition = part.start + part.duration;
|
9665
9545
|
this.state = State.FRAG_LOADING;
|
9666
9546
|
let _result;
|
@@ -9689,14 +9569,7 @@ class BaseStreamController extends TaskLoop {
|
|
9689
9569
|
}
|
9690
9570
|
}
|
9691
9571
|
}
|
9692
|
-
|
9693
|
-
this.log(`LL-Part loading OFF after next part miss @${targetBufferTime.toFixed(2)}`);
|
9694
|
-
this.loadingParts = false;
|
9695
|
-
} else if (!frag.url) {
|
9696
|
-
// Selected fragment hint for part but not loading parts
|
9697
|
-
return Promise.resolve(null);
|
9698
|
-
}
|
9699
|
-
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))}`);
|
9572
|
+
this.log(`Loading fragment ${frag.sn} cc: ${frag.cc} ${details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''}${this.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
|
9700
9573
|
// Don't update nextLoadPosition for fragments which are not buffered
|
9701
9574
|
if (isFiniteNumber(frag.sn) && !this.bitrateTest) {
|
9702
9575
|
this.nextLoadPosition = frag.start + frag.duration;
|
@@ -9794,36 +9667,8 @@ class BaseStreamController extends TaskLoop {
|
|
9794
9667
|
if (part) {
|
9795
9668
|
part.stats.parsing.end = now;
|
9796
9669
|
}
|
9797
|
-
// See if part loading should be disabled/enabled based on buffer and playback position.
|
9798
|
-
if (frag.sn !== 'initSegment') {
|
9799
|
-
const levelDetails = this.getLevelDetails();
|
9800
|
-
const loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
|
9801
|
-
const shouldLoadParts = loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
|
9802
|
-
if (shouldLoadParts !== this.loadingParts) {
|
9803
|
-
this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} after parsing segment ending @${frag.end.toFixed(2)}`);
|
9804
|
-
this.loadingParts = shouldLoadParts;
|
9805
|
-
}
|
9806
|
-
}
|
9807
9670
|
this.updateLevelTiming(frag, part, level, chunkMeta.partial);
|
9808
9671
|
}
|
9809
|
-
shouldLoadParts(details, bufferEnd) {
|
9810
|
-
if (this.config.lowLatencyMode) {
|
9811
|
-
if (!details) {
|
9812
|
-
return this.loadingParts;
|
9813
|
-
}
|
9814
|
-
if (details != null && details.partList) {
|
9815
|
-
var _details$fragmentHint;
|
9816
|
-
// Buffer must be ahead of first part + duration of parts after last segment
|
9817
|
-
// and playback must be at or past segment adjacent to part list
|
9818
|
-
const firstPart = details.partList[0];
|
9819
|
-
const safePartStart = firstPart.end + (((_details$fragmentHint = details.fragmentHint) == null ? void 0 : _details$fragmentHint.duration) || 0);
|
9820
|
-
if (bufferEnd >= safePartStart && this.lastCurrentTime > firstPart.start - firstPart.fragment.duration) {
|
9821
|
-
return true;
|
9822
|
-
}
|
9823
|
-
}
|
9824
|
-
}
|
9825
|
-
return false;
|
9826
|
-
}
|
9827
9672
|
getCurrentContext(chunkMeta) {
|
9828
9673
|
const {
|
9829
9674
|
levels,
|
@@ -9972,8 +9817,7 @@ class BaseStreamController extends TaskLoop {
|
|
9972
9817
|
config
|
9973
9818
|
} = this;
|
9974
9819
|
const start = fragments[0].start;
|
9975
|
-
|
9976
|
-
let frag = null;
|
9820
|
+
let frag;
|
9977
9821
|
if (levelDetails.live) {
|
9978
9822
|
const initialLiveManifestSize = config.initialLiveManifestSize;
|
9979
9823
|
if (fragLen < initialLiveManifestSize) {
|
@@ -9985,10 +9829,6 @@ class BaseStreamController extends TaskLoop {
|
|
9985
9829
|
// Do not load using live logic if the starting frag is requested - we want to use getFragmentAtPosition() so that
|
9986
9830
|
// we get the fragment matching that start time
|
9987
9831
|
if (!levelDetails.PTSKnown && !this.startFragRequested && this.startPosition === -1 || pos < start) {
|
9988
|
-
if (canLoadParts && !this.loadingParts) {
|
9989
|
-
this.log(`LL-Part loading ON for initial live fragment`);
|
9990
|
-
this.loadingParts = true;
|
9991
|
-
}
|
9992
9832
|
frag = this.getInitialLiveFragment(levelDetails, fragments);
|
9993
9833
|
this.startPosition = this.nextLoadPosition = frag ? this.hls.liveSyncPosition || frag.start : pos;
|
9994
9834
|
}
|
@@ -9999,7 +9839,7 @@ class BaseStreamController extends TaskLoop {
|
|
9999
9839
|
|
10000
9840
|
// If we haven't run into any special cases already, just load the fragment most closely matching the requested position
|
10001
9841
|
if (!frag) {
|
10002
|
-
const end =
|
9842
|
+
const end = config.lowLatencyMode ? levelDetails.partEnd : levelDetails.fragmentEnd;
|
10003
9843
|
frag = this.getFragmentAtPosition(pos, end, levelDetails);
|
10004
9844
|
}
|
10005
9845
|
return this.mapToInitFragWhenRequired(frag);
|
@@ -10121,7 +9961,7 @@ class BaseStreamController extends TaskLoop {
|
|
10121
9961
|
} = levelDetails;
|
10122
9962
|
const tolerance = config.maxFragLookUpTolerance;
|
10123
9963
|
const partList = levelDetails.partList;
|
10124
|
-
const loadingParts = !!(
|
9964
|
+
const loadingParts = !!(config.lowLatencyMode && partList != null && partList.length && fragmentHint);
|
10125
9965
|
if (loadingParts && fragmentHint && !this.bitrateTest) {
|
10126
9966
|
// Include incomplete fragment with parts at end
|
10127
9967
|
fragments = fragments.concat(fragmentHint);
|
@@ -10314,7 +10154,7 @@ class BaseStreamController extends TaskLoop {
|
|
10314
10154
|
errorAction.resolved = true;
|
10315
10155
|
}
|
10316
10156
|
} else {
|
10317
|
-
|
10157
|
+
logger.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
|
10318
10158
|
return;
|
10319
10159
|
}
|
10320
10160
|
} else if ((errorAction == null ? void 0 : errorAction.action) === NetworkErrorAction.SendAlternateToPenaltyBox) {
|
@@ -10723,7 +10563,6 @@ const initPTSFn = (timestamp, timeOffset, initPTS) => {
|
|
10723
10563
|
*/
|
10724
10564
|
function getAudioConfig(observer, data, offset, audioCodec) {
|
10725
10565
|
let adtsObjectType;
|
10726
|
-
let originalAdtsObjectType;
|
10727
10566
|
let adtsExtensionSamplingIndex;
|
10728
10567
|
let adtsChannelConfig;
|
10729
10568
|
let config;
|
@@ -10731,7 +10570,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
10731
10570
|
const manifestCodec = audioCodec;
|
10732
10571
|
const adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
|
10733
10572
|
// byte 2
|
10734
|
-
adtsObjectType =
|
10573
|
+
adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
|
10735
10574
|
const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
|
10736
10575
|
if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
|
10737
10576
|
const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
|
@@ -10748,8 +10587,8 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
10748
10587
|
// byte 3
|
10749
10588
|
adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6;
|
10750
10589
|
logger.log(`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`);
|
10751
|
-
//
|
10752
|
-
if (/firefox
|
10590
|
+
// firefox: freq less than 24kHz = AAC SBR (HE-AAC)
|
10591
|
+
if (/firefox/i.test(userAgent)) {
|
10753
10592
|
if (adtsSamplingIndex >= 6) {
|
10754
10593
|
adtsObjectType = 5;
|
10755
10594
|
config = new Array(4);
|
@@ -10843,7 +10682,6 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
10843
10682
|
samplerate: adtsSamplingRates[adtsSamplingIndex],
|
10844
10683
|
channelCount: adtsChannelConfig,
|
10845
10684
|
codec: 'mp4a.40.' + adtsObjectType,
|
10846
|
-
parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
|
10847
10685
|
manifestCodec
|
10848
10686
|
};
|
10849
10687
|
}
|
@@ -10898,8 +10736,7 @@ function initTrackConfig(track, observer, data, offset, audioCodec) {
|
|
10898
10736
|
track.channelCount = config.channelCount;
|
10899
10737
|
track.codec = config.codec;
|
10900
10738
|
track.manifestCodec = config.manifestCodec;
|
10901
|
-
track.
|
10902
|
-
logger.log(`parsed codec:${track.parsedCodec}, codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
|
10739
|
+
logger.log(`parsed codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
|
10903
10740
|
}
|
10904
10741
|
}
|
10905
10742
|
function getFrameDuration(samplerate) {
|
@@ -11490,110 +11327,6 @@ class BaseVideoParser {
|
|
11490
11327
|
logger.log(VideoSample.pts + '/' + VideoSample.dts + ':' + VideoSample.debug);
|
11491
11328
|
}
|
11492
11329
|
}
|
11493
|
-
parseNALu(track, array) {
|
11494
|
-
const len = array.byteLength;
|
11495
|
-
let state = track.naluState || 0;
|
11496
|
-
const lastState = state;
|
11497
|
-
const units = [];
|
11498
|
-
let i = 0;
|
11499
|
-
let value;
|
11500
|
-
let overflow;
|
11501
|
-
let unitType;
|
11502
|
-
let lastUnitStart = -1;
|
11503
|
-
let lastUnitType = 0;
|
11504
|
-
// logger.log('PES:' + Hex.hexDump(array));
|
11505
|
-
|
11506
|
-
if (state === -1) {
|
11507
|
-
// special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
|
11508
|
-
lastUnitStart = 0;
|
11509
|
-
// NALu type is value read from offset 0
|
11510
|
-
lastUnitType = this.getNALuType(array, 0);
|
11511
|
-
state = 0;
|
11512
|
-
i = 1;
|
11513
|
-
}
|
11514
|
-
while (i < len) {
|
11515
|
-
value = array[i++];
|
11516
|
-
// optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
|
11517
|
-
if (!state) {
|
11518
|
-
state = value ? 0 : 1;
|
11519
|
-
continue;
|
11520
|
-
}
|
11521
|
-
if (state === 1) {
|
11522
|
-
state = value ? 0 : 2;
|
11523
|
-
continue;
|
11524
|
-
}
|
11525
|
-
// here we have state either equal to 2 or 3
|
11526
|
-
if (!value) {
|
11527
|
-
state = 3;
|
11528
|
-
} else if (value === 1) {
|
11529
|
-
overflow = i - state - 1;
|
11530
|
-
if (lastUnitStart >= 0) {
|
11531
|
-
const unit = {
|
11532
|
-
data: array.subarray(lastUnitStart, overflow),
|
11533
|
-
type: lastUnitType
|
11534
|
-
};
|
11535
|
-
// logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
|
11536
|
-
units.push(unit);
|
11537
|
-
} else {
|
11538
|
-
// lastUnitStart is undefined => this is the first start code found in this PES packet
|
11539
|
-
// first check if start code delimiter is overlapping between 2 PES packets,
|
11540
|
-
// ie it started in last packet (lastState not zero)
|
11541
|
-
// and ended at the beginning of this PES packet (i <= 4 - lastState)
|
11542
|
-
const lastUnit = this.getLastNalUnit(track.samples);
|
11543
|
-
if (lastUnit) {
|
11544
|
-
if (lastState && i <= 4 - lastState) {
|
11545
|
-
// start delimiter overlapping between PES packets
|
11546
|
-
// strip start delimiter bytes from the end of last NAL unit
|
11547
|
-
// check if lastUnit had a state different from zero
|
11548
|
-
if (lastUnit.state) {
|
11549
|
-
// strip last bytes
|
11550
|
-
lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
|
11551
|
-
}
|
11552
|
-
}
|
11553
|
-
// If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
|
11554
|
-
|
11555
|
-
if (overflow > 0) {
|
11556
|
-
// logger.log('first NALU found with overflow:' + overflow);
|
11557
|
-
lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
|
11558
|
-
lastUnit.state = 0;
|
11559
|
-
}
|
11560
|
-
}
|
11561
|
-
}
|
11562
|
-
// check if we can read unit type
|
11563
|
-
if (i < len) {
|
11564
|
-
unitType = this.getNALuType(array, i);
|
11565
|
-
// logger.log('find NALU @ offset:' + i + ',type:' + unitType);
|
11566
|
-
lastUnitStart = i;
|
11567
|
-
lastUnitType = unitType;
|
11568
|
-
state = 0;
|
11569
|
-
} else {
|
11570
|
-
// not enough byte to read unit type. let's read it on next PES parsing
|
11571
|
-
state = -1;
|
11572
|
-
}
|
11573
|
-
} else {
|
11574
|
-
state = 0;
|
11575
|
-
}
|
11576
|
-
}
|
11577
|
-
if (lastUnitStart >= 0 && state >= 0) {
|
11578
|
-
const unit = {
|
11579
|
-
data: array.subarray(lastUnitStart, len),
|
11580
|
-
type: lastUnitType,
|
11581
|
-
state: state
|
11582
|
-
};
|
11583
|
-
units.push(unit);
|
11584
|
-
// logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
|
11585
|
-
}
|
11586
|
-
// no NALu found
|
11587
|
-
if (units.length === 0) {
|
11588
|
-
// append pes.data to previous NAL unit
|
11589
|
-
const lastUnit = this.getLastNalUnit(track.samples);
|
11590
|
-
if (lastUnit) {
|
11591
|
-
lastUnit.data = appendUint8Array(lastUnit.data, array);
|
11592
|
-
}
|
11593
|
-
}
|
11594
|
-
track.naluState = state;
|
11595
|
-
return units;
|
11596
|
-
}
|
11597
11330
|
}
|
11598
11331
|
|
11599
11332
|
/**
|
@@ -11736,171 +11469,21 @@ class ExpGolomb {
|
|
11736
11469
|
readUInt() {
|
11737
11470
|
return this.readBits(32);
|
11738
11471
|
}
|
11739
|
-
}
|
11740
|
-
|
11741
|
-
class AvcVideoParser extends BaseVideoParser {
|
11742
|
-
parsePES(track, textTrack, pes, last, duration) {
|
11743
|
-
const units = this.parseNALu(track, pes.data);
|
11744
|
-
let VideoSample = this.VideoSample;
|
11745
|
-
let push;
|
11746
|
-
let spsfound = false;
|
11747
|
-
// free pes.data to save up some memory
|
11748
|
-
pes.data = null;
|
11749
|
-
|
11750
|
-
// if new NAL units found and last sample still there, let's push ...
|
11751
|
-
// this helps parsing streams with missing AUD (only do this if AUD never found)
|
11752
|
-
if (VideoSample && units.length && !track.audFound) {
|
11753
|
-
this.pushAccessUnit(VideoSample, track);
|
11754
|
-
VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
|
11755
|
-
}
|
11756
|
-
units.forEach(unit => {
|
11757
|
-
var _VideoSample2;
|
11758
|
-
switch (unit.type) {
|
11759
|
-
// NDR
|
11760
|
-
case 1:
|
11761
|
-
{
|
11762
|
-
let iskey = false;
|
11763
|
-
push = true;
|
11764
|
-
const data = unit.data;
|
11765
|
-
// only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
|
11766
|
-
if (spsfound && data.length > 4) {
|
11767
|
-
// retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
|
11768
|
-
const sliceType = this.readSliceType(data);
|
11769
|
-
// 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
|
11770
|
-
// SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
|
11771
|
-
// An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
|
11772
|
-
// I slice: A slice that is not an SI slice that is decoded using intra prediction only.
|
11773
|
-
// if (sliceType === 2 || sliceType === 7) {
|
11774
|
-
if (sliceType === 2 || sliceType === 4 || sliceType === 7 || sliceType === 9) {
|
11775
|
-
iskey = true;
|
11776
|
-
}
|
11777
|
-
}
|
11778
|
-
if (iskey) {
|
11779
|
-
var _VideoSample;
|
11780
|
-
// if we have non-keyframe data already, that cannot belong to the same frame as a keyframe, so force a push
|
11781
|
-
if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
|
11782
|
-
this.pushAccessUnit(VideoSample, track);
|
11783
|
-
VideoSample = this.VideoSample = null;
|
11784
|
-
}
|
11785
|
-
}
|
11786
|
-
if (!VideoSample) {
|
11787
|
-
VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
|
11788
|
-
}
|
11789
|
-
VideoSample.frame = true;
|
11790
|
-
VideoSample.key = iskey;
|
11791
|
-
break;
|
11792
|
-
// IDR
|
11793
|
-
}
|
11794
|
-
case 5:
|
11795
|
-
push = true;
|
11796
|
-
// handle PES not starting with AUD
|
11797
|
-
// if we have frame data already, that cannot belong to the same frame, so force a push
|
11798
|
-
if ((_VideoSample2 = VideoSample) != null && _VideoSample2.frame && !VideoSample.key) {
|
11799
|
-
this.pushAccessUnit(VideoSample, track);
|
11800
|
-
VideoSample = this.VideoSample = null;
|
11801
|
-
}
|
11802
|
-
if (!VideoSample) {
|
11803
|
-
VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
|
11804
|
-
}
|
11805
|
-
VideoSample.key = true;
|
11806
|
-
VideoSample.frame = true;
|
11807
|
-
break;
|
11808
|
-
// SEI
|
11809
|
-
case 6:
|
11810
|
-
{
|
11811
|
-
push = true;
|
11812
|
-
parseSEIMessageFromNALu(unit.data, 1, pes.pts, textTrack.samples);
|
11813
|
-
break;
|
11814
|
-
// SPS
|
11815
|
-
}
|
11816
|
-
case 7:
|
11817
|
-
{
|
11818
|
-
var _track$pixelRatio, _track$pixelRatio2;
|
11819
|
-
push = true;
|
11820
|
-
spsfound = true;
|
11821
|
-
const sps = unit.data;
|
11822
|
-
const config = this.readSPS(sps);
|
11823
|
-
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]) {
|
11824
|
-
track.width = config.width;
|
11825
|
-
track.height = config.height;
|
11826
|
-
track.pixelRatio = config.pixelRatio;
|
11827
|
-
track.sps = [sps];
|
11828
|
-
track.duration = duration;
|
11829
|
-
const codecarray = sps.subarray(1, 4);
|
11830
|
-
let codecstring = 'avc1.';
|
11831
|
-
for (let i = 0; i < 3; i++) {
|
11832
|
-
let h = codecarray[i].toString(16);
|
11833
|
-
if (h.length < 2) {
|
11834
|
-
h = '0' + h;
|
11835
|
-
}
|
11836
|
-
codecstring += h;
|
11837
|
-
}
|
11838
|
-
track.codec = codecstring;
|
11839
|
-
}
|
11840
|
-
break;
|
11841
|
-
}
|
11842
|
-
// PPS
|
11843
|
-
case 8:
|
11844
|
-
push = true;
|
11845
|
-
track.pps = [unit.data];
|
11846
|
-
break;
|
11847
|
-
// AUD
|
11848
|
-
case 9:
|
11849
|
-
push = true;
|
11850
|
-
track.audFound = true;
|
11851
|
-
if (VideoSample) {
|
11852
|
-
this.pushAccessUnit(VideoSample, track);
|
11853
|
-
}
|
11854
|
-
VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
|
11855
|
-
break;
|
11856
|
-
// Filler Data
|
11857
|
-
case 12:
|
11858
|
-
push = true;
|
11859
|
-
break;
|
11860
|
-
default:
|
11861
|
-
push = false;
|
11862
|
-
if (VideoSample) {
|
11863
|
-
VideoSample.debug += 'unknown NAL ' + unit.type + ' ';
|
11864
|
-
}
|
11865
|
-
break;
|
11866
|
-
}
|
11867
|
-
if (VideoSample && push) {
|
11868
|
-
const units = VideoSample.units;
|
11869
|
-
units.push(unit);
|
11870
|
-
}
|
11871
|
-
});
|
11872
|
-
// if last PES packet, push samples
|
11873
|
-
if (last && VideoSample) {
|
11874
|
-
this.pushAccessUnit(VideoSample, track);
|
11875
|
-
this.VideoSample = null;
|
11876
|
-
}
|
11877
|
-
}
|
11878
|
-
getNALuType(data, offset) {
|
11879
|
-
return data[offset] & 0x1f;
|
11880
|
-
}
|
11881
|
-
readSliceType(data) {
|
11882
|
-
const eg = new ExpGolomb(data);
|
11883
|
-
// skip NALu type
|
11884
|
-
eg.readUByte();
|
11885
|
-
// discard first_mb_in_slice
|
11886
|
-
eg.readUEG();
|
11887
|
-
// return slice_type
|
11888
|
-
return eg.readUEG();
|
11889
|
-
}
|
11890
11472
|
|
11891
11473
|
/**
|
11892
|
-
*
|
11474
|
+
* Advance the ExpGolomb decoder past a scaling list. The scaling
|
11475
|
+
* list is optionally transmitted as part of a sequence parameter
|
11893
11476
|
* set and is not relevant to transmuxing.
|
11894
11477
|
* @param count the number of entries in this scaling list
|
11895
11478
|
* @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
|
11896
11479
|
*/
|
11897
|
-
skipScalingList(count
|
11480
|
+
skipScalingList(count) {
|
11898
11481
|
let lastScale = 8;
|
11899
11482
|
let nextScale = 8;
|
11900
11483
|
let deltaScale;
|
11901
11484
|
for (let j = 0; j < count; j++) {
|
11902
11485
|
if (nextScale !== 0) {
|
11903
|
-
deltaScale =
|
11486
|
+
deltaScale = this.readEG();
|
11904
11487
|
nextScale = (lastScale + deltaScale + 256) % 256;
|
11905
11488
|
}
|
11906
11489
|
lastScale = nextScale === 0 ? lastScale : nextScale;
|
@@ -11915,8 +11498,7 @@ class AvcVideoParser extends BaseVideoParser {
|
|
11915
11498
|
* sequence parameter set, including the dimensions of the
|
11916
11499
|
* associated video frames.
|
11917
11500
|
*/
|
11918
|
-
readSPS(
|
11919
|
-
const eg = new ExpGolomb(sps);
|
11501
|
+
readSPS() {
|
11920
11502
|
let frameCropLeftOffset = 0;
|
11921
11503
|
let frameCropRightOffset = 0;
|
11922
11504
|
let frameCropTopOffset = 0;
|
@@ -11924,13 +11506,13 @@ class AvcVideoParser extends BaseVideoParser {
|
|
11924
11506
|
let numRefFramesInPicOrderCntCycle;
|
11925
11507
|
let scalingListCount;
|
11926
11508
|
let i;
|
11927
|
-
const readUByte =
|
11928
|
-
const readBits =
|
11929
|
-
const readUEG =
|
11930
|
-
const readBoolean =
|
11931
|
-
const skipBits =
|
11932
|
-
const skipEG =
|
11933
|
-
const skipUEG =
|
11509
|
+
const readUByte = this.readUByte.bind(this);
|
11510
|
+
const readBits = this.readBits.bind(this);
|
11511
|
+
const readUEG = this.readUEG.bind(this);
|
11512
|
+
const readBoolean = this.readBoolean.bind(this);
|
11513
|
+
const skipBits = this.skipBits.bind(this);
|
11514
|
+
const skipEG = this.skipEG.bind(this);
|
11515
|
+
const skipUEG = this.skipUEG.bind(this);
|
11934
11516
|
const skipScalingList = this.skipScalingList.bind(this);
|
11935
11517
|
readUByte();
|
11936
11518
|
const profileIdc = readUByte(); // profile_idc
|
@@ -11955,9 +11537,9 @@ class AvcVideoParser extends BaseVideoParser {
|
|
11955
11537
|
if (readBoolean()) {
|
11956
11538
|
// seq_scaling_list_present_flag[ i ]
|
11957
11539
|
if (i < 6) {
|
11958
|
-
skipScalingList(16
|
11540
|
+
skipScalingList(16);
|
11959
11541
|
} else {
|
11960
|
-
skipScalingList(64
|
11542
|
+
skipScalingList(64);
|
11961
11543
|
}
|
11962
11544
|
}
|
11963
11545
|
}
|
@@ -12062,15 +11644,19 @@ class AvcVideoParser extends BaseVideoParser {
|
|
12062
11644
|
pixelRatio: pixelRatio
|
12063
11645
|
};
|
12064
11646
|
}
|
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
|
+
}
|
12065
11655
|
}
|
12066
11656
|
|
12067
|
-
class
|
12068
|
-
|
12069
|
-
|
12070
|
-
this.initVPS = null;
|
12071
|
-
}
|
12072
|
-
parsePES(track, textTrack, pes, last, duration) {
|
12073
|
-
const units = this.parseNALu(track, pes.data);
|
11657
|
+
class AvcVideoParser extends BaseVideoParser {
|
11658
|
+
parseAVCPES(track, textTrack, pes, last, duration) {
|
11659
|
+
const units = this.parseAVCNALu(track, pes.data);
|
12074
11660
|
let VideoSample = this.VideoSample;
|
12075
11661
|
let push;
|
12076
11662
|
let spsfound = false;
|
@@ -12086,49 +11672,42 @@ class HevcVideoParser extends BaseVideoParser {
|
|
12086
11672
|
units.forEach(unit => {
|
12087
11673
|
var _VideoSample2;
|
12088
11674
|
switch (unit.type) {
|
12089
|
-
//
|
12090
|
-
case 0:
|
11675
|
+
// NDR
|
12091
11676
|
case 1:
|
12092
|
-
|
12093
|
-
|
12094
|
-
|
12095
|
-
|
12096
|
-
|
12097
|
-
|
12098
|
-
|
12099
|
-
|
12100
|
-
|
12101
|
-
|
12102
|
-
|
12103
|
-
|
12104
|
-
|
12105
|
-
|
12106
|
-
|
12107
|
-
|
12108
|
-
case 16:
|
12109
|
-
case 17:
|
12110
|
-
case 18:
|
12111
|
-
case 21:
|
12112
|
-
push = true;
|
12113
|
-
if (spsfound) {
|
12114
|
-
var _VideoSample;
|
12115
|
-
// handle PES not starting with AUD
|
12116
|
-
// if we have frame data already, that cannot belong to the same frame, so force a push
|
12117
|
-
if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
|
12118
|
-
this.pushAccessUnit(VideoSample, track);
|
12119
|
-
VideoSample = this.VideoSample = null;
|
11677
|
+
{
|
11678
|
+
let iskey = false;
|
11679
|
+
push = true;
|
11680
|
+
const data = unit.data;
|
11681
|
+
// only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
|
11682
|
+
if (spsfound && data.length > 4) {
|
11683
|
+
// retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
|
11684
|
+
const sliceType = new ExpGolomb(data).readSliceType();
|
11685
|
+
// 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
|
11686
|
+
// SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
|
11687
|
+
// An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
|
11688
|
+
// I slice: A slice that is not an SI slice that is decoded using intra prediction only.
|
11689
|
+
// if (sliceType === 2 || sliceType === 7) {
|
11690
|
+
if (sliceType === 2 || sliceType === 4 || sliceType === 7 || sliceType === 9) {
|
11691
|
+
iskey = true;
|
11692
|
+
}
|
12120
11693
|
}
|
11694
|
+
if (iskey) {
|
11695
|
+
var _VideoSample;
|
11696
|
+
// if we have non-keyframe data already, that cannot belong to the same frame as a keyframe, so force a push
|
11697
|
+
if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
|
11698
|
+
this.pushAccessUnit(VideoSample, track);
|
11699
|
+
VideoSample = this.VideoSample = null;
|
11700
|
+
}
|
11701
|
+
}
|
11702
|
+
if (!VideoSample) {
|
11703
|
+
VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
|
11704
|
+
}
|
11705
|
+
VideoSample.frame = true;
|
11706
|
+
VideoSample.key = iskey;
|
11707
|
+
break;
|
11708
|
+
// IDR
|
12121
11709
|
}
|
12122
|
-
|
12123
|
-
VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
|
12124
|
-
}
|
12125
|
-
VideoSample.key = true;
|
12126
|
-
VideoSample.frame = true;
|
12127
|
-
break;
|
12128
|
-
|
12129
|
-
// IDR
|
12130
|
-
case 19:
|
12131
|
-
case 20:
|
11710
|
+
case 5:
|
12132
11711
|
push = true;
|
12133
11712
|
// handle PES not starting with AUD
|
12134
11713
|
// if we have frame data already, that cannot belong to the same frame, so force a push
|
@@ -12142,76 +11721,48 @@ class HevcVideoParser extends BaseVideoParser {
|
|
12142
11721
|
VideoSample.key = true;
|
12143
11722
|
VideoSample.frame = true;
|
12144
11723
|
break;
|
12145
|
-
|
12146
11724
|
// SEI
|
12147
|
-
case
|
12148
|
-
|
12149
|
-
|
12150
|
-
|
12151
|
-
|
12152
|
-
|
12153
|
-
|
12154
|
-
// VPS
|
12155
|
-
case 32:
|
12156
|
-
push = true;
|
12157
|
-
if (!track.vps) {
|
12158
|
-
const config = this.readVPS(unit.data);
|
12159
|
-
track.params = _objectSpread2({}, config);
|
12160
|
-
this.initVPS = unit.data;
|
11725
|
+
case 6:
|
11726
|
+
{
|
11727
|
+
push = true;
|
11728
|
+
parseSEIMessageFromNALu(unit.data, 1, pes.pts, textTrack.samples);
|
11729
|
+
break;
|
11730
|
+
// SPS
|
12161
11731
|
}
|
12162
|
-
|
12163
|
-
|
12164
|
-
|
12165
|
-
|
12166
|
-
|
12167
|
-
|
12168
|
-
|
12169
|
-
|
12170
|
-
if (track.
|
12171
|
-
this.initVPS = track.vps[0];
|
12172
|
-
track.sps = track.pps = undefined;
|
12173
|
-
}
|
12174
|
-
if (!track.sps) {
|
12175
|
-
const config = this.readSPS(unit.data);
|
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]) {
|
12176
11741
|
track.width = config.width;
|
12177
11742
|
track.height = config.height;
|
12178
11743
|
track.pixelRatio = config.pixelRatio;
|
11744
|
+
track.sps = [sps];
|
12179
11745
|
track.duration = duration;
|
12180
|
-
|
12181
|
-
|
12182
|
-
for (
|
12183
|
-
|
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;
|
12184
11754
|
}
|
11755
|
+
track.codec = codecstring;
|
12185
11756
|
}
|
12186
|
-
|
12187
|
-
track.sps.push(unit.data);
|
12188
|
-
}
|
12189
|
-
}
|
12190
|
-
if (!VideoSample) {
|
12191
|
-
VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
|
11757
|
+
break;
|
12192
11758
|
}
|
12193
|
-
VideoSample.key = true;
|
12194
|
-
break;
|
12195
|
-
|
12196
11759
|
// PPS
|
12197
|
-
case
|
11760
|
+
case 8:
|
12198
11761
|
push = true;
|
12199
|
-
|
12200
|
-
if (!track.pps) {
|
12201
|
-
track.pps = [];
|
12202
|
-
const config = this.readPPS(unit.data);
|
12203
|
-
for (const prop in config) {
|
12204
|
-
track.params[prop] = config[prop];
|
12205
|
-
}
|
12206
|
-
}
|
12207
|
-
if (this.initVPS !== null || track.pps.length === 0) {
|
12208
|
-
track.pps.push(unit.data);
|
12209
|
-
}
|
12210
|
-
}
|
11762
|
+
track.pps = [unit.data];
|
12211
11763
|
break;
|
12212
|
-
|
12213
|
-
|
12214
|
-
case 35:
|
11764
|
+
// AUD
|
11765
|
+
case 9:
|
12215
11766
|
push = true;
|
12216
11767
|
track.audFound = true;
|
12217
11768
|
if (VideoSample) {
|
@@ -12219,10 +11770,14 @@ class HevcVideoParser extends BaseVideoParser {
|
|
12219
11770
|
}
|
12220
11771
|
VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
|
12221
11772
|
break;
|
11773
|
+
// Filler Data
|
11774
|
+
case 12:
|
11775
|
+
push = true;
|
11776
|
+
break;
|
12222
11777
|
default:
|
12223
11778
|
push = false;
|
12224
11779
|
if (VideoSample) {
|
12225
|
-
VideoSample.debug += 'unknown
|
11780
|
+
VideoSample.debug += 'unknown NAL ' + unit.type + ' ';
|
12226
11781
|
}
|
12227
11782
|
break;
|
12228
11783
|
}
|
@@ -12237,423 +11792,109 @@ class HevcVideoParser extends BaseVideoParser {
|
|
12237
11792
|
this.VideoSample = null;
|
12238
11793
|
}
|
12239
11794
|
}
|
12240
|
-
|
12241
|
-
|
12242
|
-
|
12243
|
-
|
12244
|
-
const
|
12245
|
-
let
|
12246
|
-
|
12247
|
-
|
12248
|
-
|
12249
|
-
|
12250
|
-
|
12251
|
-
|
12252
|
-
}
|
12253
|
-
dst[dstIdx] = arr[i];
|
12254
|
-
dstIdx++;
|
12255
|
-
}
|
12256
|
-
return new Uint8Array(dst.buffer, 0, dstIdx);
|
12257
|
-
}
|
12258
|
-
readVPS(vps) {
|
12259
|
-
const eg = new ExpGolomb(vps);
|
12260
|
-
// remove header
|
12261
|
-
eg.readUByte();
|
12262
|
-
eg.readUByte();
|
12263
|
-
eg.readBits(4); // video_parameter_set_id
|
12264
|
-
eg.skipBits(2);
|
12265
|
-
eg.readBits(6); // max_layers_minus1
|
12266
|
-
const max_sub_layers_minus1 = eg.readBits(3);
|
12267
|
-
const temporal_id_nesting_flag = eg.readBoolean();
|
12268
|
-
// ...vui fps can be here, but empty fps value is not critical for metadata
|
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));
|
12269
11807
|
|
12270
|
-
|
12271
|
-
|
12272
|
-
|
12273
|
-
|
12274
|
-
|
12275
|
-
|
12276
|
-
|
12277
|
-
|
12278
|
-
|
12279
|
-
|
12280
|
-
|
12281
|
-
|
12282
|
-
|
12283
|
-
|
12284
|
-
|
12285
|
-
|
12286
|
-
|
12287
|
-
|
12288
|
-
const general_profile_compatibility_flags_2 = eg.readUByte();
|
12289
|
-
const general_profile_compatibility_flags_3 = eg.readUByte();
|
12290
|
-
const general_profile_compatibility_flags_4 = eg.readUByte();
|
12291
|
-
const general_constraint_indicator_flags_1 = eg.readUByte();
|
12292
|
-
const general_constraint_indicator_flags_2 = eg.readUByte();
|
12293
|
-
const general_constraint_indicator_flags_3 = eg.readUByte();
|
12294
|
-
const general_constraint_indicator_flags_4 = eg.readUByte();
|
12295
|
-
const general_constraint_indicator_flags_5 = eg.readUByte();
|
12296
|
-
const general_constraint_indicator_flags_6 = eg.readUByte();
|
12297
|
-
const general_level_idc = eg.readUByte();
|
12298
|
-
const sub_layer_profile_present_flags = [];
|
12299
|
-
const sub_layer_level_present_flags = [];
|
12300
|
-
for (let i = 0; i < max_sub_layers_minus1; i++) {
|
12301
|
-
sub_layer_profile_present_flags.push(eg.readBoolean());
|
12302
|
-
sub_layer_level_present_flags.push(eg.readBoolean());
|
12303
|
-
}
|
12304
|
-
if (max_sub_layers_minus1 > 0) {
|
12305
|
-
for (let i = max_sub_layers_minus1; i < 8; i++) {
|
12306
|
-
eg.readBits(2);
|
12307
|
-
}
|
12308
|
-
}
|
12309
|
-
for (let i = 0; i < max_sub_layers_minus1; i++) {
|
12310
|
-
if (sub_layer_profile_present_flags[i]) {
|
12311
|
-
eg.readUByte(); // sub_layer_profile_space, sub_layer_tier_flag, sub_layer_profile_idc
|
12312
|
-
eg.readUByte();
|
12313
|
-
eg.readUByte();
|
12314
|
-
eg.readUByte();
|
12315
|
-
eg.readUByte(); // sub_layer_profile_compatibility_flag
|
12316
|
-
eg.readUByte();
|
12317
|
-
eg.readUByte();
|
12318
|
-
eg.readUByte();
|
12319
|
-
eg.readUByte();
|
12320
|
-
eg.readUByte();
|
12321
|
-
eg.readUByte();
|
12322
|
-
}
|
12323
|
-
if (sub_layer_level_present_flags[i]) {
|
12324
|
-
eg.readUByte();
|
12325
|
-
}
|
12326
|
-
}
|
12327
|
-
eg.readUEG(); // seq_parameter_set_id
|
12328
|
-
const chroma_format_idc = eg.readUEG();
|
12329
|
-
if (chroma_format_idc == 3) {
|
12330
|
-
eg.skipBits(1); //separate_colour_plane_flag
|
12331
|
-
}
|
12332
|
-
const pic_width_in_luma_samples = eg.readUEG();
|
12333
|
-
const pic_height_in_luma_samples = eg.readUEG();
|
12334
|
-
const conformance_window_flag = eg.readBoolean();
|
12335
|
-
let pic_left_offset = 0,
|
12336
|
-
pic_right_offset = 0,
|
12337
|
-
pic_top_offset = 0,
|
12338
|
-
pic_bottom_offset = 0;
|
12339
|
-
if (conformance_window_flag) {
|
12340
|
-
pic_left_offset += eg.readUEG();
|
12341
|
-
pic_right_offset += eg.readUEG();
|
12342
|
-
pic_top_offset += eg.readUEG();
|
12343
|
-
pic_bottom_offset += eg.readUEG();
|
12344
|
-
}
|
12345
|
-
const bit_depth_luma_minus8 = eg.readUEG();
|
12346
|
-
const bit_depth_chroma_minus8 = eg.readUEG();
|
12347
|
-
const log2_max_pic_order_cnt_lsb_minus4 = eg.readUEG();
|
12348
|
-
const sub_layer_ordering_info_present_flag = eg.readBoolean();
|
12349
|
-
for (let i = sub_layer_ordering_info_present_flag ? 0 : max_sub_layers_minus1; i <= max_sub_layers_minus1; i++) {
|
12350
|
-
eg.skipUEG(); // max_dec_pic_buffering_minus1[i]
|
12351
|
-
eg.skipUEG(); // max_num_reorder_pics[i]
|
12352
|
-
eg.skipUEG(); // max_latency_increase_plus1[i]
|
12353
|
-
}
|
12354
|
-
eg.skipUEG(); // log2_min_luma_coding_block_size_minus3
|
12355
|
-
eg.skipUEG(); // log2_diff_max_min_luma_coding_block_size
|
12356
|
-
eg.skipUEG(); // log2_min_transform_block_size_minus2
|
12357
|
-
eg.skipUEG(); // log2_diff_max_min_transform_block_size
|
12358
|
-
eg.skipUEG(); // max_transform_hierarchy_depth_inter
|
12359
|
-
eg.skipUEG(); // max_transform_hierarchy_depth_intra
|
12360
|
-
const scaling_list_enabled_flag = eg.readBoolean();
|
12361
|
-
if (scaling_list_enabled_flag) {
|
12362
|
-
const sps_scaling_list_data_present_flag = eg.readBoolean();
|
12363
|
-
if (sps_scaling_list_data_present_flag) {
|
12364
|
-
for (let sizeId = 0; sizeId < 4; sizeId++) {
|
12365
|
-
for (let matrixId = 0; matrixId < (sizeId === 3 ? 2 : 6); matrixId++) {
|
12366
|
-
const scaling_list_pred_mode_flag = eg.readBoolean();
|
12367
|
-
if (!scaling_list_pred_mode_flag) {
|
12368
|
-
eg.readUEG(); // scaling_list_pred_matrix_id_delta
|
12369
|
-
} else {
|
12370
|
-
const coefNum = Math.min(64, 1 << 4 + (sizeId << 1));
|
12371
|
-
if (sizeId > 1) {
|
12372
|
-
eg.readEG();
|
12373
|
-
}
|
12374
|
-
for (let i = 0; i < coefNum; i++) {
|
12375
|
-
eg.readEG();
|
12376
|
-
}
|
12377
|
-
}
|
12378
|
-
}
|
12379
|
-
}
|
11808
|
+
if (state === -1) {
|
11809
|
+
// special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
|
11810
|
+
lastUnitStart = 0;
|
11811
|
+
// NALu type is value read from offset 0
|
11812
|
+
lastUnitType = array[0] & 0x1f;
|
11813
|
+
state = 0;
|
11814
|
+
i = 1;
|
11815
|
+
}
|
11816
|
+
while (i < len) {
|
11817
|
+
value = array[i++];
|
11818
|
+
// optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
|
11819
|
+
if (!state) {
|
11820
|
+
state = value ? 0 : 1;
|
11821
|
+
continue;
|
11822
|
+
}
|
11823
|
+
if (state === 1) {
|
11824
|
+
state = value ? 0 : 2;
|
11825
|
+
continue;
|
12380
11826
|
}
|
12381
|
-
|
12382
|
-
|
12383
|
-
|
12384
|
-
|
12385
|
-
|
12386
|
-
|
12387
|
-
|
12388
|
-
|
12389
|
-
|
12390
|
-
|
12391
|
-
|
12392
|
-
|
12393
|
-
|
12394
|
-
|
12395
|
-
|
12396
|
-
|
12397
|
-
|
12398
|
-
|
12399
|
-
|
12400
|
-
|
12401
|
-
|
12402
|
-
|
12403
|
-
|
12404
|
-
|
12405
|
-
|
12406
|
-
|
12407
|
-
let use_delta_flag = false;
|
12408
|
-
if (!used_by_curr_pic_flag) {
|
12409
|
-
use_delta_flag = eg.readBoolean();
|
12410
|
-
}
|
12411
|
-
if (used_by_curr_pic_flag || use_delta_flag) {
|
12412
|
-
next_num_delta_pocs++;
|
12413
|
-
}
|
12414
|
-
}
|
12415
|
-
num_delta_pocs = next_num_delta_pocs;
|
12416
|
-
} else {
|
12417
|
-
const num_negative_pics = eg.readUEG();
|
12418
|
-
const num_positive_pics = eg.readUEG();
|
12419
|
-
num_delta_pocs = num_negative_pics + num_positive_pics;
|
12420
|
-
for (let j = 0; j < num_negative_pics; j++) {
|
12421
|
-
eg.readUEG();
|
12422
|
-
eg.readBoolean();
|
12423
|
-
}
|
12424
|
-
for (let j = 0; j < num_positive_pics; j++) {
|
12425
|
-
eg.readUEG();
|
12426
|
-
eg.readBoolean();
|
12427
|
-
}
|
12428
|
-
}
|
12429
|
-
}
|
12430
|
-
const long_term_ref_pics_present_flag = eg.readBoolean();
|
12431
|
-
if (long_term_ref_pics_present_flag) {
|
12432
|
-
const num_long_term_ref_pics_sps = eg.readUEG();
|
12433
|
-
for (let i = 0; i < num_long_term_ref_pics_sps; i++) {
|
12434
|
-
for (let j = 0; j < log2_max_pic_order_cnt_lsb_minus4 + 4; j++) {
|
12435
|
-
eg.readBits(1);
|
12436
|
-
}
|
12437
|
-
eg.readBits(1);
|
12438
|
-
}
|
12439
|
-
}
|
12440
|
-
let min_spatial_segmentation_idc = 0;
|
12441
|
-
let sar_width = 1,
|
12442
|
-
sar_height = 1;
|
12443
|
-
let fps_fixed = true,
|
12444
|
-
fps_den = 1,
|
12445
|
-
fps_num = 0;
|
12446
|
-
eg.readBoolean(); // sps_temporal_mvp_enabled_flag
|
12447
|
-
eg.readBoolean(); // strong_intra_smoothing_enabled_flag
|
12448
|
-
let default_display_window_flag = false;
|
12449
|
-
const vui_parameters_present_flag = eg.readBoolean();
|
12450
|
-
if (vui_parameters_present_flag) {
|
12451
|
-
const aspect_ratio_info_present_flag = eg.readBoolean();
|
12452
|
-
if (aspect_ratio_info_present_flag) {
|
12453
|
-
const aspect_ratio_idc = eg.readUByte();
|
12454
|
-
const sar_width_table = [1, 12, 10, 16, 40, 24, 20, 32, 80, 18, 15, 64, 160, 4, 3, 2];
|
12455
|
-
const sar_height_table = [1, 11, 11, 11, 33, 11, 11, 11, 33, 11, 11, 33, 99, 3, 2, 1];
|
12456
|
-
if (aspect_ratio_idc > 0 && aspect_ratio_idc < 16) {
|
12457
|
-
sar_width = sar_width_table[aspect_ratio_idc - 1];
|
12458
|
-
sar_height = sar_height_table[aspect_ratio_idc - 1];
|
12459
|
-
} else if (aspect_ratio_idc === 255) {
|
12460
|
-
sar_width = eg.readBits(16);
|
12461
|
-
sar_height = eg.readBits(16);
|
12462
|
-
}
|
12463
|
-
}
|
12464
|
-
const overscan_info_present_flag = eg.readBoolean();
|
12465
|
-
if (overscan_info_present_flag) {
|
12466
|
-
eg.readBoolean();
|
12467
|
-
}
|
12468
|
-
const video_signal_type_present_flag = eg.readBoolean();
|
12469
|
-
if (video_signal_type_present_flag) {
|
12470
|
-
eg.readBits(3);
|
12471
|
-
eg.readBoolean();
|
12472
|
-
const colour_description_present_flag = eg.readBoolean();
|
12473
|
-
if (colour_description_present_flag) {
|
12474
|
-
eg.readUByte();
|
12475
|
-
eg.readUByte();
|
12476
|
-
eg.readUByte();
|
12477
|
-
}
|
12478
|
-
}
|
12479
|
-
const chroma_loc_info_present_flag = eg.readBoolean();
|
12480
|
-
if (chroma_loc_info_present_flag) {
|
12481
|
-
eg.readUEG();
|
12482
|
-
eg.readUEG();
|
12483
|
-
}
|
12484
|
-
eg.readBoolean(); // neutral_chroma_indication_flag
|
12485
|
-
eg.readBoolean(); // field_seq_flag
|
12486
|
-
eg.readBoolean(); // frame_field_info_present_flag
|
12487
|
-
default_display_window_flag = eg.readBoolean();
|
12488
|
-
if (default_display_window_flag) {
|
12489
|
-
pic_left_offset += eg.readUEG();
|
12490
|
-
pic_right_offset += eg.readUEG();
|
12491
|
-
pic_top_offset += eg.readUEG();
|
12492
|
-
pic_bottom_offset += eg.readUEG();
|
12493
|
-
}
|
12494
|
-
const vui_timing_info_present_flag = eg.readBoolean();
|
12495
|
-
if (vui_timing_info_present_flag) {
|
12496
|
-
fps_den = eg.readBits(32);
|
12497
|
-
fps_num = eg.readBits(32);
|
12498
|
-
const vui_poc_proportional_to_timing_flag = eg.readBoolean();
|
12499
|
-
if (vui_poc_proportional_to_timing_flag) {
|
12500
|
-
eg.readUEG();
|
12501
|
-
}
|
12502
|
-
const vui_hrd_parameters_present_flag = eg.readBoolean();
|
12503
|
-
if (vui_hrd_parameters_present_flag) {
|
12504
|
-
//const commonInfPresentFlag = true;
|
12505
|
-
//if (commonInfPresentFlag) {
|
12506
|
-
const nal_hrd_parameters_present_flag = eg.readBoolean();
|
12507
|
-
const vcl_hrd_parameters_present_flag = eg.readBoolean();
|
12508
|
-
let sub_pic_hrd_params_present_flag = false;
|
12509
|
-
if (nal_hrd_parameters_present_flag || vcl_hrd_parameters_present_flag) {
|
12510
|
-
sub_pic_hrd_params_present_flag = eg.readBoolean();
|
12511
|
-
if (sub_pic_hrd_params_present_flag) {
|
12512
|
-
eg.readUByte();
|
12513
|
-
eg.readBits(5);
|
12514
|
-
eg.readBoolean();
|
12515
|
-
eg.readBits(5);
|
12516
|
-
}
|
12517
|
-
eg.readBits(4); // bit_rate_scale
|
12518
|
-
eg.readBits(4); // cpb_size_scale
|
12519
|
-
if (sub_pic_hrd_params_present_flag) {
|
12520
|
-
eg.readBits(4);
|
12521
|
-
}
|
12522
|
-
eg.readBits(5);
|
12523
|
-
eg.readBits(5);
|
12524
|
-
eg.readBits(5);
|
12525
|
-
}
|
12526
|
-
//}
|
12527
|
-
for (let i = 0; i <= max_sub_layers_minus1; i++) {
|
12528
|
-
fps_fixed = eg.readBoolean(); // fixed_pic_rate_general_flag
|
12529
|
-
const fixed_pic_rate_within_cvs_flag = fps_fixed || eg.readBoolean();
|
12530
|
-
let low_delay_hrd_flag = false;
|
12531
|
-
if (fixed_pic_rate_within_cvs_flag) {
|
12532
|
-
eg.readEG();
|
12533
|
-
} else {
|
12534
|
-
low_delay_hrd_flag = eg.readBoolean();
|
12535
|
-
}
|
12536
|
-
const cpb_cnt = low_delay_hrd_flag ? 1 : eg.readUEG() + 1;
|
12537
|
-
if (nal_hrd_parameters_present_flag) {
|
12538
|
-
for (let j = 0; j < cpb_cnt; j++) {
|
12539
|
-
eg.readUEG();
|
12540
|
-
eg.readUEG();
|
12541
|
-
if (sub_pic_hrd_params_present_flag) {
|
12542
|
-
eg.readUEG();
|
12543
|
-
eg.readUEG();
|
12544
|
-
}
|
12545
|
-
eg.skipBits(1);
|
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);
|
12546
11853
|
}
|
12547
11854
|
}
|
12548
|
-
|
12549
|
-
|
12550
|
-
|
12551
|
-
|
12552
|
-
|
12553
|
-
|
12554
|
-
eg.readUEG();
|
12555
|
-
}
|
12556
|
-
eg.skipBits(1);
|
12557
|
-
}
|
11855
|
+
// If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
|
11856
|
+
|
11857
|
+
if (overflow > 0) {
|
11858
|
+
// logger.log('first NALU found with overflow:' + overflow);
|
11859
|
+
lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
|
11860
|
+
lastUnit.state = 0;
|
12558
11861
|
}
|
12559
11862
|
}
|
12560
11863
|
}
|
12561
|
-
|
12562
|
-
|
12563
|
-
|
12564
|
-
|
12565
|
-
|
12566
|
-
|
12567
|
-
|
12568
|
-
|
12569
|
-
|
12570
|
-
|
12571
|
-
height = pic_height_in_luma_samples;
|
12572
|
-
if (conformance_window_flag || default_display_window_flag) {
|
12573
|
-
let chroma_scale_w = 1,
|
12574
|
-
chroma_scale_h = 1;
|
12575
|
-
if (chroma_format_idc === 1) {
|
12576
|
-
// YUV 420
|
12577
|
-
chroma_scale_w = chroma_scale_h = 2;
|
12578
|
-
} else if (chroma_format_idc == 2) {
|
12579
|
-
// YUV 422
|
12580
|
-
chroma_scale_w = 2;
|
12581
|
-
}
|
12582
|
-
width = pic_width_in_luma_samples - chroma_scale_w * pic_right_offset - chroma_scale_w * pic_left_offset;
|
12583
|
-
height = pic_height_in_luma_samples - chroma_scale_h * pic_bottom_offset - chroma_scale_h * pic_top_offset;
|
12584
|
-
}
|
12585
|
-
const profile_space_string = general_profile_space ? ['A', 'B', 'C'][general_profile_space] : '';
|
12586
|
-
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;
|
12587
|
-
let profile_compatibility_rev = 0;
|
12588
|
-
for (let i = 0; i < 32; i++) {
|
12589
|
-
profile_compatibility_rev = (profile_compatibility_rev | (profile_compatibility_buf >> i & 1) << 31 - i) >>> 0; // reverse bit position (and cast as UInt32)
|
12590
|
-
}
|
12591
|
-
let profile_compatibility_flags_string = profile_compatibility_rev.toString(16);
|
12592
|
-
if (general_profile_idc === 1 && profile_compatibility_flags_string === '2') {
|
12593
|
-
profile_compatibility_flags_string = '6';
|
12594
|
-
}
|
12595
|
-
const tier_flag_string = general_tier_flag ? 'H' : 'L';
|
12596
|
-
return {
|
12597
|
-
codecString: `hvc1.${profile_space_string}${general_profile_idc}.${profile_compatibility_flags_string}.${tier_flag_string}${general_level_idc}.B0`,
|
12598
|
-
params: {
|
12599
|
-
general_tier_flag,
|
12600
|
-
general_profile_idc,
|
12601
|
-
general_profile_space,
|
12602
|
-
general_profile_compatibility_flags: [general_profile_compatibility_flags_1, general_profile_compatibility_flags_2, general_profile_compatibility_flags_3, general_profile_compatibility_flags_4],
|
12603
|
-
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],
|
12604
|
-
general_level_idc,
|
12605
|
-
bit_depth: bit_depth_luma_minus8 + 8,
|
12606
|
-
bit_depth_luma_minus8,
|
12607
|
-
bit_depth_chroma_minus8,
|
12608
|
-
min_spatial_segmentation_idc,
|
12609
|
-
chroma_format_idc: chroma_format_idc,
|
12610
|
-
frame_rate: {
|
12611
|
-
fixed: fps_fixed,
|
12612
|
-
fps: fps_num / fps_den
|
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;
|
12613
11874
|
}
|
12614
|
-
}
|
12615
|
-
|
12616
|
-
|
12617
|
-
pixelRatio: [sar_width, sar_height]
|
12618
|
-
};
|
12619
|
-
}
|
12620
|
-
readPPS(pps) {
|
12621
|
-
const eg = new ExpGolomb(this.ebsp2rbsp(pps));
|
12622
|
-
eg.readUByte();
|
12623
|
-
eg.readUByte();
|
12624
|
-
eg.skipUEG(); // pic_parameter_set_id
|
12625
|
-
eg.skipUEG(); // seq_parameter_set_id
|
12626
|
-
eg.skipBits(2); // dependent_slice_segments_enabled_flag, output_flag_present_flag
|
12627
|
-
eg.skipBits(3); // num_extra_slice_header_bits
|
12628
|
-
eg.skipBits(2); // sign_data_hiding_enabled_flag, cabac_init_present_flag
|
12629
|
-
eg.skipUEG();
|
12630
|
-
eg.skipUEG();
|
12631
|
-
eg.skipEG(); // init_qp_minus26
|
12632
|
-
eg.skipBits(2); // constrained_intra_pred_flag, transform_skip_enabled_flag
|
12633
|
-
const cu_qp_delta_enabled_flag = eg.readBoolean();
|
12634
|
-
if (cu_qp_delta_enabled_flag) {
|
12635
|
-
eg.skipUEG();
|
12636
|
-
}
|
12637
|
-
eg.skipEG(); // cb_qp_offset
|
12638
|
-
eg.skipEG(); // cr_qp_offset
|
12639
|
-
eg.skipBits(4); // pps_slice_chroma_qp_offsets_present_flag, weighted_pred_flag, weighted_bipred_flag, transquant_bypass_enabled_flag
|
12640
|
-
const tiles_enabled_flag = eg.readBoolean();
|
12641
|
-
const entropy_coding_sync_enabled_flag = eg.readBoolean();
|
12642
|
-
let parallelismType = 1; // slice-based parallel decoding
|
12643
|
-
if (entropy_coding_sync_enabled_flag && tiles_enabled_flag) {
|
12644
|
-
parallelismType = 0; // mixed-type parallel decoding
|
12645
|
-
} else if (entropy_coding_sync_enabled_flag) {
|
12646
|
-
parallelismType = 3; // wavefront-based parallel decoding
|
12647
|
-
} else if (tiles_enabled_flag) {
|
12648
|
-
parallelismType = 2; // tile-based parallel decoding
|
11875
|
+
} else {
|
11876
|
+
state = 0;
|
11877
|
+
}
|
12649
11878
|
}
|
12650
|
-
|
12651
|
-
|
12652
|
-
|
12653
|
-
|
12654
|
-
|
12655
|
-
|
12656
|
-
|
11879
|
+
if (lastUnitStart >= 0 && state >= 0) {
|
11880
|
+
const unit = {
|
11881
|
+
data: array.subarray(lastUnitStart, len),
|
11882
|
+
type: lastUnitType,
|
11883
|
+
state: state
|
11884
|
+
};
|
11885
|
+
units.push(unit);
|
11886
|
+
// logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
|
11887
|
+
}
|
11888
|
+
// no NALu found
|
11889
|
+
if (units.length === 0) {
|
11890
|
+
// append pes.data to previous NAL unit
|
11891
|
+
const lastUnit = this.getLastNalUnit(track.samples);
|
11892
|
+
if (lastUnit) {
|
11893
|
+
lastUnit.data = appendUint8Array(lastUnit.data, array);
|
11894
|
+
}
|
11895
|
+
}
|
11896
|
+
track.naluState = state;
|
11897
|
+
return units;
|
12657
11898
|
}
|
12658
11899
|
}
|
12659
11900
|
|
@@ -12671,7 +11912,7 @@ class SampleAesDecrypter {
|
|
12671
11912
|
});
|
12672
11913
|
}
|
12673
11914
|
decryptBuffer(encryptedData) {
|
12674
|
-
return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer
|
11915
|
+
return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer);
|
12675
11916
|
}
|
12676
11917
|
|
12677
11918
|
// AAC - encrypt all full 16 bytes blocks starting from offset 16
|
@@ -12785,7 +12026,7 @@ class TSDemuxer {
|
|
12785
12026
|
this.observer = observer;
|
12786
12027
|
this.config = config;
|
12787
12028
|
this.typeSupported = typeSupported;
|
12788
|
-
this.videoParser =
|
12029
|
+
this.videoParser = new AvcVideoParser();
|
12789
12030
|
}
|
12790
12031
|
static probe(data) {
|
12791
12032
|
const syncOffset = TSDemuxer.syncOffset(data);
|
@@ -12950,21 +12191,7 @@ class TSDemuxer {
|
|
12950
12191
|
case videoPid:
|
12951
12192
|
if (stt) {
|
12952
12193
|
if (videoData && (pes = parsePES(videoData))) {
|
12953
|
-
|
12954
|
-
switch (videoTrack.segmentCodec) {
|
12955
|
-
case 'avc':
|
12956
|
-
this.videoParser = new AvcVideoParser();
|
12957
|
-
break;
|
12958
|
-
case 'hevc':
|
12959
|
-
{
|
12960
|
-
this.videoParser = new HevcVideoParser();
|
12961
|
-
}
|
12962
|
-
break;
|
12963
|
-
}
|
12964
|
-
}
|
12965
|
-
if (this.videoParser !== null) {
|
12966
|
-
this.videoParser.parsePES(videoTrack, textTrack, pes, false, this._duration);
|
12967
|
-
}
|
12194
|
+
this.videoParser.parseAVCPES(videoTrack, textTrack, pes, false, this._duration);
|
12968
12195
|
}
|
12969
12196
|
videoData = {
|
12970
12197
|
data: [],
|
@@ -13131,22 +12358,8 @@ class TSDemuxer {
|
|
13131
12358
|
// try to parse last PES packets
|
13132
12359
|
let pes;
|
13133
12360
|
if (videoData && (pes = parsePES(videoData))) {
|
13134
|
-
|
13135
|
-
|
13136
|
-
case 'avc':
|
13137
|
-
this.videoParser = new AvcVideoParser();
|
13138
|
-
break;
|
13139
|
-
case 'hevc':
|
13140
|
-
{
|
13141
|
-
this.videoParser = new HevcVideoParser();
|
13142
|
-
}
|
13143
|
-
break;
|
13144
|
-
}
|
13145
|
-
}
|
13146
|
-
if (this.videoParser !== null) {
|
13147
|
-
this.videoParser.parsePES(videoTrack, textTrack, pes, true, this._duration);
|
13148
|
-
videoTrack.pesData = null;
|
13149
|
-
}
|
12361
|
+
this.videoParser.parseAVCPES(videoTrack, textTrack, pes, true, this._duration);
|
12362
|
+
videoTrack.pesData = null;
|
13150
12363
|
} else {
|
13151
12364
|
// either avcData null or PES truncated, keep it for next frag parsing
|
13152
12365
|
videoTrack.pesData = videoData;
|
@@ -13479,14 +12692,7 @@ function parsePMT(data, offset, typeSupported, isSampleAes) {
|
|
13479
12692
|
logger.warn('Unsupported EC-3 in M2TS found');
|
13480
12693
|
break;
|
13481
12694
|
case 0x24:
|
13482
|
-
|
13483
|
-
{
|
13484
|
-
if (result.videoPid === -1) {
|
13485
|
-
result.videoPid = pid;
|
13486
|
-
result.segmentVideoCodec = 'hevc';
|
13487
|
-
logger.log('HEVC in M2TS found');
|
13488
|
-
}
|
13489
|
-
}
|
12695
|
+
logger.warn('Unsupported HEVC in M2TS found');
|
13490
12696
|
break;
|
13491
12697
|
}
|
13492
12698
|
// move to the next table entry
|
@@ -13709,8 +12915,6 @@ class MP4 {
|
|
13709
12915
|
avc1: [],
|
13710
12916
|
// codingname
|
13711
12917
|
avcC: [],
|
13712
|
-
hvc1: [],
|
13713
|
-
hvcC: [],
|
13714
12918
|
btrt: [],
|
13715
12919
|
dinf: [],
|
13716
12920
|
dref: [],
|
@@ -14135,10 +13339,8 @@ class MP4 {
|
|
14135
13339
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.ac3(track));
|
14136
13340
|
}
|
14137
13341
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track));
|
14138
|
-
} else if (track.segmentCodec === 'avc') {
|
14139
|
-
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
|
14140
13342
|
} else {
|
14141
|
-
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.
|
13343
|
+
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
|
14142
13344
|
}
|
14143
13345
|
}
|
14144
13346
|
static tkhd(track) {
|
@@ -14276,84 +13478,6 @@ class MP4 {
|
|
14276
13478
|
const result = appendUint8Array(MP4.FTYP, movie);
|
14277
13479
|
return result;
|
14278
13480
|
}
|
14279
|
-
static hvc1(track) {
|
14280
|
-
const ps = track.params;
|
14281
|
-
const units = [track.vps, track.sps, track.pps];
|
14282
|
-
const NALuLengthSize = 4;
|
14283
|
-
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]);
|
14284
|
-
|
14285
|
-
// compute hvcC size in bytes
|
14286
|
-
let length = config.length;
|
14287
|
-
for (let i = 0; i < units.length; i += 1) {
|
14288
|
-
length += 3;
|
14289
|
-
for (let j = 0; j < units[i].length; j += 1) {
|
14290
|
-
length += 2 + units[i][j].length;
|
14291
|
-
}
|
14292
|
-
}
|
14293
|
-
const hvcC = new Uint8Array(length);
|
14294
|
-
hvcC.set(config, 0);
|
14295
|
-
length = config.length;
|
14296
|
-
// append parameter set units: one vps, one or more sps and pps
|
14297
|
-
const iMax = units.length - 1;
|
14298
|
-
for (let i = 0; i < units.length; i += 1) {
|
14299
|
-
hvcC.set(new Uint8Array([32 + i | (i === iMax ? 128 : 0), 0x00, units[i].length]), length);
|
14300
|
-
length += 3;
|
14301
|
-
for (let j = 0; j < units[i].length; j += 1) {
|
14302
|
-
hvcC.set(new Uint8Array([units[i][j].length >> 8, units[i][j].length & 255]), length);
|
14303
|
-
length += 2;
|
14304
|
-
hvcC.set(units[i][j], length);
|
14305
|
-
length += units[i][j].length;
|
14306
|
-
}
|
14307
|
-
}
|
14308
|
-
const hvcc = MP4.box(MP4.types.hvcC, hvcC);
|
14309
|
-
const width = track.width;
|
14310
|
-
const height = track.height;
|
14311
|
-
const hSpacing = track.pixelRatio[0];
|
14312
|
-
const vSpacing = track.pixelRatio[1];
|
14313
|
-
return MP4.box(MP4.types.hvc1, new Uint8Array([0x00, 0x00, 0x00,
|
14314
|
-
// reserved
|
14315
|
-
0x00, 0x00, 0x00,
|
14316
|
-
// reserved
|
14317
|
-
0x00, 0x01,
|
14318
|
-
// data_reference_index
|
14319
|
-
0x00, 0x00,
|
14320
|
-
// pre_defined
|
14321
|
-
0x00, 0x00,
|
14322
|
-
// reserved
|
14323
|
-
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
14324
|
-
// pre_defined
|
14325
|
-
width >> 8 & 0xff, width & 0xff,
|
14326
|
-
// width
|
14327
|
-
height >> 8 & 0xff, height & 0xff,
|
14328
|
-
// height
|
14329
|
-
0x00, 0x48, 0x00, 0x00,
|
14330
|
-
// horizresolution
|
14331
|
-
0x00, 0x48, 0x00, 0x00,
|
14332
|
-
// vertresolution
|
14333
|
-
0x00, 0x00, 0x00, 0x00,
|
14334
|
-
// reserved
|
14335
|
-
0x00, 0x01,
|
14336
|
-
// frame_count
|
14337
|
-
0x12, 0x64, 0x61, 0x69, 0x6c,
|
14338
|
-
// dailymotion/hls.js
|
14339
|
-
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,
|
14340
|
-
// compressorname
|
14341
|
-
0x00, 0x18,
|
14342
|
-
// depth = 24
|
14343
|
-
0x11, 0x11]),
|
14344
|
-
// pre_defined = -1
|
14345
|
-
hvcc, MP4.box(MP4.types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80,
|
14346
|
-
// bufferSizeDB
|
14347
|
-
0x00, 0x2d, 0xc6, 0xc0,
|
14348
|
-
// maxBitrate
|
14349
|
-
0x00, 0x2d, 0xc6, 0xc0])),
|
14350
|
-
// avgBitrate
|
14351
|
-
MP4.box(MP4.types.pasp, new Uint8Array([hSpacing >> 24,
|
14352
|
-
// hSpacing
|
14353
|
-
hSpacing >> 16 & 0xff, hSpacing >> 8 & 0xff, hSpacing & 0xff, vSpacing >> 24,
|
14354
|
-
// vSpacing
|
14355
|
-
vSpacing >> 16 & 0xff, vSpacing >> 8 & 0xff, vSpacing & 0xff])));
|
14356
|
-
}
|
14357
13481
|
}
|
14358
13482
|
MP4.types = void 0;
|
14359
13483
|
MP4.HDLR_TYPES = void 0;
|
@@ -14735,9 +13859,9 @@ class MP4Remuxer {
|
|
14735
13859
|
const foundOverlap = delta < -1;
|
14736
13860
|
if (foundHole || foundOverlap) {
|
14737
13861
|
if (foundHole) {
|
14738
|
-
logger.warn(
|
13862
|
+
logger.warn(`AVC: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
|
14739
13863
|
} else {
|
14740
|
-
logger.warn(
|
13864
|
+
logger.warn(`AVC: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
|
14741
13865
|
}
|
14742
13866
|
if (!foundOverlap || nextAvcDts >= inputSamples[0].pts || chromeVersion) {
|
14743
13867
|
firstDTS = nextAvcDts;
|
@@ -14746,24 +13870,12 @@ class MP4Remuxer {
|
|
14746
13870
|
inputSamples[0].dts = firstDTS;
|
14747
13871
|
inputSamples[0].pts = firstPTS;
|
14748
13872
|
} else {
|
14749
|
-
let isPTSOrderRetained = true;
|
14750
13873
|
for (let i = 0; i < inputSamples.length; i++) {
|
14751
|
-
if (inputSamples[i].dts > firstPTS
|
13874
|
+
if (inputSamples[i].dts > firstPTS) {
|
14752
13875
|
break;
|
14753
13876
|
}
|
14754
|
-
const prevPTS = inputSamples[i].pts;
|
14755
13877
|
inputSamples[i].dts -= delta;
|
14756
13878
|
inputSamples[i].pts -= delta;
|
14757
|
-
|
14758
|
-
// check to see if this sample's PTS order has changed
|
14759
|
-
// relative to the next one
|
14760
|
-
if (i < inputSamples.length - 1) {
|
14761
|
-
const nextSamplePTS = inputSamples[i + 1].pts;
|
14762
|
-
const currentSamplePTS = inputSamples[i].pts;
|
14763
|
-
const currentOrder = nextSamplePTS <= currentSamplePTS;
|
14764
|
-
const prevOrder = nextSamplePTS <= prevPTS;
|
14765
|
-
isPTSOrderRetained = currentOrder == prevOrder;
|
14766
|
-
}
|
14767
13879
|
}
|
14768
13880
|
}
|
14769
13881
|
logger.log(`Video: Initial PTS/DTS adjusted: ${toMsFromMpegTsClock(firstPTS, true)}/${toMsFromMpegTsClock(firstDTS, true)}, delta: ${toMsFromMpegTsClock(delta, true)} ms`);
|
@@ -14911,7 +14023,7 @@ class MP4Remuxer {
|
|
14911
14023
|
}
|
14912
14024
|
}
|
14913
14025
|
}
|
14914
|
-
// next AVC
|
14026
|
+
// next AVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
|
14915
14027
|
mp4SampleDuration = stretchedLastFrame || !mp4SampleDuration ? averageSampleDuration : mp4SampleDuration;
|
14916
14028
|
this.nextAvcDts = nextAvcDts = lastDTS + mp4SampleDuration;
|
14917
14029
|
this.videoSampleDuration = mp4SampleDuration;
|
@@ -15044,7 +14156,7 @@ class MP4Remuxer {
|
|
15044
14156
|
logger.warn(`[mp4-remuxer]: Injecting ${missing} audio frame @ ${(nextPts / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`);
|
15045
14157
|
for (let j = 0; j < missing; j++) {
|
15046
14158
|
const newStamp = Math.max(nextPts, 0);
|
15047
|
-
let fillFrame = AAC.getSilentFrame(track.
|
14159
|
+
let fillFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
|
15048
14160
|
if (!fillFrame) {
|
15049
14161
|
logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.');
|
15050
14162
|
fillFrame = sample.unit.subarray();
|
@@ -15172,7 +14284,7 @@ class MP4Remuxer {
|
|
15172
14284
|
// samples count of this segment's duration
|
15173
14285
|
const nbSamples = Math.ceil((endDTS - startDTS) / frameDuration);
|
15174
14286
|
// silent frame
|
15175
|
-
const silentFrame = AAC.getSilentFrame(track.
|
14287
|
+
const silentFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
|
15176
14288
|
logger.warn('[mp4-remuxer]: remux empty Audio');
|
15177
14289
|
// Can't remux if we can't generate a silent frame...
|
15178
14290
|
if (!silentFrame) {
|
@@ -15566,15 +14678,13 @@ class Transmuxer {
|
|
15566
14678
|
initSegmentData
|
15567
14679
|
} = transmuxConfig;
|
15568
14680
|
const keyData = getEncryptionType(uintData, decryptdata);
|
15569
|
-
if (keyData &&
|
14681
|
+
if (keyData && keyData.method === 'AES-128') {
|
15570
14682
|
const decrypter = this.getDecrypter();
|
15571
|
-
const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
|
15572
|
-
|
15573
14683
|
// Software decryption is synchronous; webCrypto is not
|
15574
14684
|
if (decrypter.isSync()) {
|
15575
14685
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
15576
14686
|
// data is handled in the flush() call
|
15577
|
-
let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer
|
14687
|
+
let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer);
|
15578
14688
|
// For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
|
15579
14689
|
const loadingParts = chunkMeta.part > -1;
|
15580
14690
|
if (loadingParts) {
|
@@ -15586,7 +14696,7 @@ class Transmuxer {
|
|
15586
14696
|
}
|
15587
14697
|
uintData = new Uint8Array(decryptedData);
|
15588
14698
|
} else {
|
15589
|
-
this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer
|
14699
|
+
this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer).then(decryptedData => {
|
15590
14700
|
// Calling push here is important; if flush() is called while this is still resolving, this ensures that
|
15591
14701
|
// the decrypted data has been transmuxed
|
15592
14702
|
const result = this.push(decryptedData, null, chunkMeta);
|
@@ -16240,7 +15350,14 @@ class TransmuxerInterface {
|
|
16240
15350
|
this.observer = new EventEmitter();
|
16241
15351
|
this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
|
16242
15352
|
this.observer.on(Events.ERROR, forwardMessage);
|
16243
|
-
const
|
15353
|
+
const MediaSource = getMediaSource(config.preferManagedMediaSource) || {
|
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
|
+
};
|
16244
15361
|
|
16245
15362
|
// navigator.vendor is not always available in Web Worker
|
16246
15363
|
// refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
|
@@ -16528,7 +15645,7 @@ const TICK_INTERVAL$2 = 100; // how often to tick in ms
|
|
16528
15645
|
|
16529
15646
|
class AudioStreamController extends BaseStreamController {
|
16530
15647
|
constructor(hls, fragmentTracker, keyLoader) {
|
16531
|
-
super(hls, fragmentTracker, keyLoader, 'audio-stream-controller', PlaylistLevelType.AUDIO);
|
15648
|
+
super(hls, fragmentTracker, keyLoader, '[audio-stream-controller]', PlaylistLevelType.AUDIO);
|
16532
15649
|
this.videoBuffer = null;
|
16533
15650
|
this.videoTrackCC = -1;
|
16534
15651
|
this.waitingVideoCC = -1;
|
@@ -16540,24 +15657,27 @@ class AudioStreamController extends BaseStreamController {
|
|
16540
15657
|
this.flushing = false;
|
16541
15658
|
this.bufferFlushed = false;
|
16542
15659
|
this.cachedTrackLoadedData = null;
|
16543
|
-
this.
|
15660
|
+
this._registerListeners();
|
16544
15661
|
}
|
16545
15662
|
onHandlerDestroying() {
|
16546
|
-
this.
|
15663
|
+
this._unregisterListeners();
|
16547
15664
|
super.onHandlerDestroying();
|
16548
15665
|
this.mainDetails = null;
|
16549
15666
|
this.bufferedTrack = null;
|
16550
15667
|
this.switchingTrack = null;
|
16551
15668
|
}
|
16552
|
-
|
16553
|
-
super.registerListeners();
|
15669
|
+
_registerListeners() {
|
16554
15670
|
const {
|
16555
15671
|
hls
|
16556
15672
|
} = 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);
|
16557
15676
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
16558
15677
|
hls.on(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
|
16559
15678
|
hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
16560
15679
|
hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
|
15680
|
+
hls.on(Events.ERROR, this.onError, this);
|
16561
15681
|
hls.on(Events.BUFFER_RESET, this.onBufferReset, this);
|
16562
15682
|
hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
16563
15683
|
hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
@@ -16565,18 +15685,18 @@ class AudioStreamController extends BaseStreamController {
|
|
16565
15685
|
hls.on(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);
|
16566
15686
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
16567
15687
|
}
|
16568
|
-
|
15688
|
+
_unregisterListeners() {
|
16569
15689
|
const {
|
16570
15690
|
hls
|
16571
15691
|
} = this;
|
16572
|
-
|
16573
|
-
|
16574
|
-
|
16575
|
-
super.unregisterListeners();
|
15692
|
+
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
15693
|
+
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
15694
|
+
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
16576
15695
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
16577
15696
|
hls.off(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
|
16578
15697
|
hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
16579
15698
|
hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
|
15699
|
+
hls.off(Events.ERROR, this.onError, this);
|
16580
15700
|
hls.off(Events.BUFFER_RESET, this.onBufferReset, this);
|
16581
15701
|
hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
16582
15702
|
hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
@@ -16745,13 +15865,12 @@ class AudioStreamController extends BaseStreamController {
|
|
16745
15865
|
} = this;
|
16746
15866
|
const config = hls.config;
|
16747
15867
|
|
16748
|
-
// 1. if
|
16749
|
-
// 2. if video not attached AND
|
15868
|
+
// 1. if video not attached AND
|
16750
15869
|
// start fragment already requested OR start frag prefetch not enabled
|
16751
|
-
//
|
15870
|
+
// 2. if tracks or track not loaded and selected
|
16752
15871
|
// then exit loop
|
16753
15872
|
// => if media not attached but start frag prefetch is enabled and start frag not requested yet, we will not exit loop
|
16754
|
-
if (!
|
15873
|
+
if (!media && (this.startFragRequested || !config.startFragPrefetch) || !(levels != null && levels[trackId])) {
|
16755
15874
|
return;
|
16756
15875
|
}
|
16757
15876
|
const levelInfo = levels[trackId];
|
@@ -17309,7 +16428,7 @@ class AudioStreamController extends BaseStreamController {
|
|
17309
16428
|
|
17310
16429
|
class AudioTrackController extends BasePlaylistController {
|
17311
16430
|
constructor(hls) {
|
17312
|
-
super(hls, 'audio-track-controller');
|
16431
|
+
super(hls, '[audio-track-controller]');
|
17313
16432
|
this.tracks = [];
|
17314
16433
|
this.groupIds = null;
|
17315
16434
|
this.tracksInGroup = [];
|
@@ -17628,23 +16747,26 @@ const TICK_INTERVAL$1 = 500; // how often to tick in ms
|
|
17628
16747
|
|
17629
16748
|
class SubtitleStreamController extends BaseStreamController {
|
17630
16749
|
constructor(hls, fragmentTracker, keyLoader) {
|
17631
|
-
super(hls, fragmentTracker, keyLoader, 'subtitle-stream-controller', PlaylistLevelType.SUBTITLE);
|
16750
|
+
super(hls, fragmentTracker, keyLoader, '[subtitle-stream-controller]', PlaylistLevelType.SUBTITLE);
|
17632
16751
|
this.currentTrackId = -1;
|
17633
16752
|
this.tracksBuffered = [];
|
17634
16753
|
this.mainDetails = null;
|
17635
|
-
this.
|
16754
|
+
this._registerListeners();
|
17636
16755
|
}
|
17637
16756
|
onHandlerDestroying() {
|
17638
|
-
this.
|
16757
|
+
this._unregisterListeners();
|
17639
16758
|
super.onHandlerDestroying();
|
17640
16759
|
this.mainDetails = null;
|
17641
16760
|
}
|
17642
|
-
|
17643
|
-
super.registerListeners();
|
16761
|
+
_registerListeners() {
|
17644
16762
|
const {
|
17645
16763
|
hls
|
17646
16764
|
} = 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);
|
17647
16768
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
16769
|
+
hls.on(Events.ERROR, this.onError, this);
|
17648
16770
|
hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
|
17649
16771
|
hls.on(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
|
17650
16772
|
hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
|
@@ -17652,12 +16774,15 @@ class SubtitleStreamController extends BaseStreamController {
|
|
17652
16774
|
hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
17653
16775
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
17654
16776
|
}
|
17655
|
-
|
17656
|
-
super.unregisterListeners();
|
16777
|
+
_unregisterListeners() {
|
17657
16778
|
const {
|
17658
16779
|
hls
|
17659
16780
|
} = 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);
|
17660
16784
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
16785
|
+
hls.off(Events.ERROR, this.onError, this);
|
17661
16786
|
hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
|
17662
16787
|
hls.off(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
|
17663
16788
|
hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
|
@@ -17884,10 +17009,10 @@ class SubtitleStreamController extends BaseStreamController {
|
|
17884
17009
|
return;
|
17885
17010
|
}
|
17886
17011
|
// check to see if the payload needs to be decrypted
|
17887
|
-
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv &&
|
17012
|
+
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
|
17888
17013
|
const startTime = performance.now();
|
17889
17014
|
// decrypt the subtitles
|
17890
|
-
this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer
|
17015
|
+
this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
|
17891
17016
|
hls.trigger(Events.ERROR, {
|
17892
17017
|
type: ErrorTypes.MEDIA_ERROR,
|
17893
17018
|
details: ErrorDetails.FRAG_DECRYPT_ERROR,
|
@@ -18021,7 +17146,7 @@ class BufferableInstance {
|
|
18021
17146
|
|
18022
17147
|
class SubtitleTrackController extends BasePlaylistController {
|
18023
17148
|
constructor(hls) {
|
18024
|
-
super(hls, 'subtitle-track-controller');
|
17149
|
+
super(hls, '[subtitle-track-controller]');
|
18025
17150
|
this.media = null;
|
18026
17151
|
this.tracks = [];
|
18027
17152
|
this.groupIds = null;
|
@@ -18030,10 +17155,10 @@ class SubtitleTrackController extends BasePlaylistController {
|
|
18030
17155
|
this.currentTrack = null;
|
18031
17156
|
this.selectDefaultTrack = true;
|
18032
17157
|
this.queuedDefaultTrack = -1;
|
17158
|
+
this.asyncPollTrackChange = () => this.pollTrackChange(0);
|
18033
17159
|
this.useTextTrackPolling = false;
|
18034
17160
|
this.subtitlePollingInterval = -1;
|
18035
17161
|
this._subtitleDisplay = true;
|
18036
|
-
this.asyncPollTrackChange = () => this.pollTrackChange(0);
|
18037
17162
|
this.onTextTracksChanged = () => {
|
18038
17163
|
if (!this.useTextTrackPolling) {
|
18039
17164
|
self.clearInterval(this.subtitlePollingInterval);
|
@@ -18067,7 +17192,6 @@ class SubtitleTrackController extends BasePlaylistController {
|
|
18067
17192
|
this.tracks.length = 0;
|
18068
17193
|
this.tracksInGroup.length = 0;
|
18069
17194
|
this.currentTrack = null;
|
18070
|
-
// @ts-ignore
|
18071
17195
|
this.onTextTracksChanged = this.asyncPollTrackChange = null;
|
18072
17196
|
super.destroy();
|
18073
17197
|
}
|
@@ -18528,9 +17652,8 @@ class BufferOperationQueue {
|
|
18528
17652
|
}
|
18529
17653
|
|
18530
17654
|
const VIDEO_CODEC_PROFILE_REPLACE = /(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\.[^.,]+)+/;
|
18531
|
-
class BufferController
|
17655
|
+
class BufferController {
|
18532
17656
|
constructor(hls) {
|
18533
|
-
super('buffer-controller', hls.logger);
|
18534
17657
|
// The level details used to determine duration, target-duration and live
|
18535
17658
|
this.details = null;
|
18536
17659
|
// cache the self generated object url to detect hijack of video tag
|
@@ -18560,6 +17683,9 @@ class BufferController extends Logger {
|
|
18560
17683
|
this.tracks = {};
|
18561
17684
|
this.pendingTracks = {};
|
18562
17685
|
this.sourceBuffer = void 0;
|
17686
|
+
this.log = void 0;
|
17687
|
+
this.warn = void 0;
|
17688
|
+
this.error = void 0;
|
18563
17689
|
this._onEndStreaming = event => {
|
18564
17690
|
if (!this.hls) {
|
18565
17691
|
return;
|
@@ -18605,11 +17731,15 @@ class BufferController extends Logger {
|
|
18605
17731
|
_objectUrl
|
18606
17732
|
} = this;
|
18607
17733
|
if (mediaSrc !== _objectUrl) {
|
18608
|
-
|
17734
|
+
logger.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
|
18609
17735
|
}
|
18610
17736
|
};
|
18611
17737
|
this.hls = hls;
|
17738
|
+
const logPrefix = '[buffer-controller]';
|
18612
17739
|
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);
|
18613
17743
|
this._initSourceBuffer();
|
18614
17744
|
this.registerListeners();
|
18615
17745
|
}
|
@@ -18622,12 +17752,6 @@ class BufferController extends Logger {
|
|
18622
17752
|
this.lastMpegAudioChunk = null;
|
18623
17753
|
// @ts-ignore
|
18624
17754
|
this.hls = null;
|
18625
|
-
// @ts-ignore
|
18626
|
-
this._onMediaSourceOpen = this._onMediaSourceClose = null;
|
18627
|
-
// @ts-ignore
|
18628
|
-
this._onMediaSourceEnded = null;
|
18629
|
-
// @ts-ignore
|
18630
|
-
this._onStartStreaming = this._onEndStreaming = null;
|
18631
17755
|
}
|
18632
17756
|
registerListeners() {
|
18633
17757
|
const {
|
@@ -18790,7 +17914,6 @@ class BufferController extends Logger {
|
|
18790
17914
|
this.resetBuffer(type);
|
18791
17915
|
});
|
18792
17916
|
this._initSourceBuffer();
|
18793
|
-
this.hls.resumeBuffering();
|
18794
17917
|
}
|
18795
17918
|
resetBuffer(type) {
|
18796
17919
|
const sb = this.sourceBuffer[type];
|
@@ -21893,12 +21016,14 @@ class TimelineController {
|
|
21893
21016
|
this.cea608Parser1 = this.cea608Parser2 = undefined;
|
21894
21017
|
}
|
21895
21018
|
initCea608Parsers() {
|
21896
|
-
|
21897
|
-
|
21898
|
-
|
21899
|
-
|
21900
|
-
|
21901
|
-
|
21019
|
+
if (this.config.enableCEA708Captions && (!this.cea608Parser1 || !this.cea608Parser2)) {
|
21020
|
+
const channel1 = new OutputFilter(this, 'textTrack1');
|
21021
|
+
const channel2 = new OutputFilter(this, 'textTrack2');
|
21022
|
+
const channel3 = new OutputFilter(this, 'textTrack3');
|
21023
|
+
const channel4 = new OutputFilter(this, 'textTrack4');
|
21024
|
+
this.cea608Parser1 = new Cea608Parser(1, channel1, channel2);
|
21025
|
+
this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
|
21026
|
+
}
|
21902
21027
|
}
|
21903
21028
|
addCues(trackName, startTime, endTime, screen, cueRanges) {
|
21904
21029
|
// skip cues which overlap more than 50% with previously parsed time ranges
|
@@ -22136,7 +21261,7 @@ class TimelineController {
|
|
22136
21261
|
if (inUseTracks != null && inUseTracks.length) {
|
22137
21262
|
const unusedTextTracks = inUseTracks.filter(t => t !== null).map(t => t.label);
|
22138
21263
|
if (unusedTextTracks.length) {
|
22139
|
-
|
21264
|
+
logger.warn(`Media element contains unused subtitle tracks: ${unusedTextTracks.join(', ')}. Replace media element for each source to clear TextTracks and captions menu.`);
|
22140
21265
|
}
|
22141
21266
|
}
|
22142
21267
|
} else if (this.tracks.length) {
|
@@ -22181,23 +21306,26 @@ class TimelineController {
|
|
22181
21306
|
return level == null ? void 0 : level.attrs['CLOSED-CAPTIONS'];
|
22182
21307
|
}
|
22183
21308
|
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
|
+
}
|
22184
21320
|
// if this frag isn't contiguous, clear the parser so cues with bad start/end times aren't added to the textTrack
|
22185
|
-
if (
|
21321
|
+
if (data.frag.type === PlaylistLevelType.MAIN) {
|
22186
21322
|
var _data$part$index, _data$part;
|
22187
|
-
const {
|
22188
|
-
cea608Parser1,
|
22189
|
-
cea608Parser2,
|
22190
|
-
lastSn
|
22191
|
-
} = this;
|
22192
|
-
if (!cea608Parser1 || !cea608Parser2) {
|
22193
|
-
return;
|
22194
|
-
}
|
22195
21323
|
const {
|
22196
21324
|
cc,
|
22197
21325
|
sn
|
22198
21326
|
} = data.frag;
|
22199
|
-
const partIndex = (_data$part$index = (_data$part = data.part) == null ? void 0 : _data$part.index) != null ? _data$part$index : -1;
|
22200
|
-
if (!(sn === lastSn + 1 || sn === lastSn && partIndex ===
|
21327
|
+
const partIndex = (_data$part$index = data == null ? void 0 : (_data$part = data.part) == null ? void 0 : _data$part.index) != null ? _data$part$index : -1;
|
21328
|
+
if (!(sn === lastSn + 1 || sn === lastSn && partIndex === lastPartIndex + 1 || cc === lastCc)) {
|
22201
21329
|
cea608Parser1.reset();
|
22202
21330
|
cea608Parser2.reset();
|
22203
21331
|
}
|
@@ -22254,7 +21382,7 @@ class TimelineController {
|
|
22254
21382
|
frag: frag
|
22255
21383
|
});
|
22256
21384
|
}, error => {
|
22257
|
-
|
21385
|
+
logger.log(`Failed to parse IMSC1: ${error}`);
|
22258
21386
|
hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
|
22259
21387
|
success: false,
|
22260
21388
|
frag: frag,
|
@@ -22295,7 +21423,7 @@ class TimelineController {
|
|
22295
21423
|
this._fallbackToIMSC1(frag, payload);
|
22296
21424
|
}
|
22297
21425
|
// Something went wrong while parsing. Trigger event with success false.
|
22298
|
-
|
21426
|
+
logger.log(`Failed to parse VTT cue: ${error}`);
|
22299
21427
|
if (missingInitPTS && maxAvCC > frag.cc) {
|
22300
21428
|
return;
|
22301
21429
|
}
|
@@ -22356,7 +21484,12 @@ class TimelineController {
|
|
22356
21484
|
this.captionsTracks = {};
|
22357
21485
|
}
|
22358
21486
|
onFragParsingUserdata(event, data) {
|
22359
|
-
|
21487
|
+
this.initCea608Parsers();
|
21488
|
+
const {
|
21489
|
+
cea608Parser1,
|
21490
|
+
cea608Parser2
|
21491
|
+
} = this;
|
21492
|
+
if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
|
22360
21493
|
return;
|
22361
21494
|
}
|
22362
21495
|
const {
|
@@ -22371,12 +21504,9 @@ class TimelineController {
|
|
22371
21504
|
for (let i = 0; i < samples.length; i++) {
|
22372
21505
|
const ccBytes = samples[i].bytes;
|
22373
21506
|
if (ccBytes) {
|
22374
|
-
if (!this.cea608Parser1) {
|
22375
|
-
this.initCea608Parsers();
|
22376
|
-
}
|
22377
21507
|
const ccdatas = this.extractCea608Data(ccBytes);
|
22378
|
-
|
22379
|
-
|
21508
|
+
cea608Parser1.addData(samples[i].pts, ccdatas[0]);
|
21509
|
+
cea608Parser2.addData(samples[i].pts, ccdatas[1]);
|
22380
21510
|
}
|
22381
21511
|
}
|
22382
21512
|
}
|
@@ -22572,7 +21702,7 @@ class CapLevelController {
|
|
22572
21702
|
const hls = this.hls;
|
22573
21703
|
const maxLevel = this.getMaxLevel(levels.length - 1);
|
22574
21704
|
if (maxLevel !== this.autoLevelCapping) {
|
22575
|
-
|
21705
|
+
logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
|
22576
21706
|
}
|
22577
21707
|
hls.autoLevelCapping = maxLevel;
|
22578
21708
|
if (hls.autoLevelCapping > this.autoLevelCapping && this.streamController) {
|
@@ -22750,10 +21880,10 @@ class FPSController {
|
|
22750
21880
|
totalDroppedFrames: droppedFrames
|
22751
21881
|
});
|
22752
21882
|
if (droppedFPS > 0) {
|
22753
|
-
//
|
21883
|
+
// logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
|
22754
21884
|
if (currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
|
22755
21885
|
let currentLevel = hls.currentLevel;
|
22756
|
-
|
21886
|
+
logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
|
22757
21887
|
if (currentLevel > 0 && (hls.autoLevelCapping === -1 || hls.autoLevelCapping >= currentLevel)) {
|
22758
21888
|
currentLevel = currentLevel - 1;
|
22759
21889
|
hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, {
|
@@ -22785,119 +21915,31 @@ class FPSController {
|
|
22785
21915
|
}
|
22786
21916
|
}
|
22787
21917
|
|
21918
|
+
const LOGGER_PREFIX = '[eme]';
|
22788
21919
|
/**
|
22789
21920
|
* Controller to deal with encrypted media extensions (EME)
|
22790
21921
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/Encrypted_Media_Extensions_API
|
22791
21922
|
*
|
22792
21923
|
* @class
|
22793
|
-
* @constructor
|
22794
|
-
*/
|
22795
|
-
class EMEController
|
22796
|
-
constructor(hls) {
|
22797
|
-
|
22798
|
-
this.
|
22799
|
-
this.
|
22800
|
-
this.
|
22801
|
-
this.
|
22802
|
-
this.
|
22803
|
-
this.
|
22804
|
-
this.
|
22805
|
-
this.
|
22806
|
-
this.
|
22807
|
-
this.
|
22808
|
-
|
22809
|
-
|
22810
|
-
|
22811
|
-
|
22812
|
-
this.debug(`"${event.type}" event: init data type: "${initDataType}"`);
|
22813
|
-
|
22814
|
-
// Ignore event when initData is null
|
22815
|
-
if (initData === null) {
|
22816
|
-
return;
|
22817
|
-
}
|
22818
|
-
let keyId;
|
22819
|
-
let keySystemDomain;
|
22820
|
-
if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) {
|
22821
|
-
// Match sinf keyId to playlist skd://keyId=
|
22822
|
-
const json = bin2str(new Uint8Array(initData));
|
22823
|
-
try {
|
22824
|
-
const sinf = base64Decode(JSON.parse(json).sinf);
|
22825
|
-
const tenc = parseSinf(new Uint8Array(sinf));
|
22826
|
-
if (!tenc) {
|
22827
|
-
return;
|
22828
|
-
}
|
22829
|
-
keyId = tenc.subarray(8, 24);
|
22830
|
-
keySystemDomain = KeySystems.FAIRPLAY;
|
22831
|
-
} catch (error) {
|
22832
|
-
this.warn('Failed to parse sinf "encrypted" event message initData');
|
22833
|
-
return;
|
22834
|
-
}
|
22835
|
-
} else {
|
22836
|
-
// Support clear-lead key-session creation (otherwise depend on playlist keys)
|
22837
|
-
const psshInfo = parsePssh(initData);
|
22838
|
-
if (psshInfo === null) {
|
22839
|
-
return;
|
22840
|
-
}
|
22841
|
-
if (psshInfo.version === 0 && psshInfo.systemId === KeySystemIds.WIDEVINE && psshInfo.data) {
|
22842
|
-
keyId = psshInfo.data.subarray(8, 24);
|
22843
|
-
}
|
22844
|
-
keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
|
22845
|
-
}
|
22846
|
-
if (!keySystemDomain || !keyId) {
|
22847
|
-
return;
|
22848
|
-
}
|
22849
|
-
const keyIdHex = Hex.hexDump(keyId);
|
22850
|
-
const {
|
22851
|
-
keyIdToKeySessionPromise,
|
22852
|
-
mediaKeySessions
|
22853
|
-
} = this;
|
22854
|
-
let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
|
22855
|
-
for (let i = 0; i < mediaKeySessions.length; i++) {
|
22856
|
-
// Match playlist key
|
22857
|
-
const keyContext = mediaKeySessions[i];
|
22858
|
-
const decryptdata = keyContext.decryptdata;
|
22859
|
-
if (decryptdata.pssh || !decryptdata.keyId) {
|
22860
|
-
continue;
|
22861
|
-
}
|
22862
|
-
const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
|
22863
|
-
if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
|
22864
|
-
keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
|
22865
|
-
delete keyIdToKeySessionPromise[oldKeyIdHex];
|
22866
|
-
decryptdata.pssh = new Uint8Array(initData);
|
22867
|
-
decryptdata.keyId = keyId;
|
22868
|
-
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
|
22869
|
-
return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
|
22870
|
-
});
|
22871
|
-
break;
|
22872
|
-
}
|
22873
|
-
}
|
22874
|
-
if (!keySessionContextPromise) {
|
22875
|
-
// Clear-lead key (not encountered in playlist)
|
22876
|
-
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
|
22877
|
-
keySystem,
|
22878
|
-
mediaKeys
|
22879
|
-
}) => {
|
22880
|
-
var _keySystemToKeySystem;
|
22881
|
-
this.throwIfDestroyed();
|
22882
|
-
const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
|
22883
|
-
decryptdata.pssh = new Uint8Array(initData);
|
22884
|
-
decryptdata.keyId = keyId;
|
22885
|
-
return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
|
22886
|
-
this.throwIfDestroyed();
|
22887
|
-
const keySessionContext = this.createMediaKeySessionContext({
|
22888
|
-
decryptdata,
|
22889
|
-
keySystem,
|
22890
|
-
mediaKeys
|
22891
|
-
});
|
22892
|
-
return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
|
22893
|
-
});
|
22894
|
-
});
|
22895
|
-
}
|
22896
|
-
keySessionContextPromise.catch(error => this.handleError(error));
|
22897
|
-
};
|
22898
|
-
this.onWaitingForKey = event => {
|
22899
|
-
this.log(`"${event.type}" event`);
|
22900
|
-
};
|
21924
|
+
* @constructor
|
21925
|
+
*/
|
21926
|
+
class EMEController {
|
21927
|
+
constructor(hls) {
|
21928
|
+
this.hls = void 0;
|
21929
|
+
this.config = void 0;
|
21930
|
+
this.media = null;
|
21931
|
+
this.keyFormatPromise = null;
|
21932
|
+
this.keySystemAccessPromises = {};
|
21933
|
+
this._requestLicenseFailureCount = 0;
|
21934
|
+
this.mediaKeySessions = [];
|
21935
|
+
this.keyIdToKeySessionPromise = {};
|
21936
|
+
this.setMediaKeysQueue = EMEController.CDMCleanupPromise ? [EMEController.CDMCleanupPromise] : [];
|
21937
|
+
this.onMediaEncrypted = this._onMediaEncrypted.bind(this);
|
21938
|
+
this.onWaitingForKey = this._onWaitingForKey.bind(this);
|
21939
|
+
this.debug = logger.debug.bind(logger, LOGGER_PREFIX);
|
21940
|
+
this.log = logger.log.bind(logger, LOGGER_PREFIX);
|
21941
|
+
this.warn = logger.warn.bind(logger, LOGGER_PREFIX);
|
21942
|
+
this.error = logger.error.bind(logger, LOGGER_PREFIX);
|
22901
21943
|
this.hls = hls;
|
22902
21944
|
this.config = hls.config;
|
22903
21945
|
this.registerListeners();
|
@@ -22911,9 +21953,9 @@ class EMEController extends Logger {
|
|
22911
21953
|
config.licenseXhrSetup = config.licenseResponseCallback = undefined;
|
22912
21954
|
config.drmSystems = config.drmSystemOptions = {};
|
22913
21955
|
// @ts-ignore
|
22914
|
-
this.hls = this.
|
21956
|
+
this.hls = this.onMediaEncrypted = this.onWaitingForKey = this.keyIdToKeySessionPromise = null;
|
22915
21957
|
// @ts-ignore
|
22916
|
-
this.
|
21958
|
+
this.config = null;
|
22917
21959
|
}
|
22918
21960
|
registerListeners() {
|
22919
21961
|
this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
@@ -23177,6 +22219,100 @@ class EMEController extends Logger {
|
|
23177
22219
|
}
|
23178
22220
|
return this.attemptKeySystemAccess(keySystemsToAttempt);
|
23179
22221
|
}
|
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
|
+
}
|
23180
22316
|
attemptSetMediaKeys(keySystem, mediaKeys) {
|
23181
22317
|
const queue = this.setMediaKeysQueue.slice();
|
23182
22318
|
this.log(`Setting media-keys for "${keySystem}"`);
|
@@ -23769,6 +22905,20 @@ class SfItem {
|
|
23769
22905
|
}
|
23770
22906
|
}
|
23771
22907
|
|
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
|
+
|
23772
22922
|
const DICT = 'Dict';
|
23773
22923
|
|
23774
22924
|
function format(value) {
|
@@ -23792,27 +22942,29 @@ function throwError(action, src, type, cause) {
|
|
23792
22942
|
});
|
23793
22943
|
}
|
23794
22944
|
|
23795
|
-
|
23796
|
-
return throwError('serialize', src, type, cause);
|
23797
|
-
}
|
22945
|
+
const BARE_ITEM = 'Bare Item';
|
23798
22946
|
|
23799
|
-
|
23800
|
-
|
23801
|
-
|
23802
|
-
|
23803
|
-
|
23804
|
-
|
23805
|
-
|
23806
|
-
|
23807
|
-
|
23808
|
-
|
23809
|
-
this.description = description;
|
23810
|
-
}
|
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;
|
23811
22957
|
}
|
23812
22958
|
|
23813
|
-
const
|
22959
|
+
const STRING_REGEX = /[\x00-\x1f\x7f]+/; // eslint-disable-line no-control-regex
|
23814
22960
|
|
23815
|
-
const
|
22961
|
+
const TOKEN = 'Token';
|
22962
|
+
|
22963
|
+
const KEY = 'Key';
|
22964
|
+
|
22965
|
+
function serializeError(src, type, cause) {
|
22966
|
+
return throwError('serialize', src, type, cause);
|
22967
|
+
}
|
23816
22968
|
|
23817
22969
|
// 4.1.9. Serializing a Boolean
|
23818
22970
|
//
|
@@ -23851,8 +23003,6 @@ function base64encode(binary) {
|
|
23851
23003
|
return btoa(String.fromCharCode(...binary));
|
23852
23004
|
}
|
23853
23005
|
|
23854
|
-
const BYTES = 'Byte Sequence';
|
23855
|
-
|
23856
23006
|
// 4.1.8. Serializing a Byte Sequence
|
23857
23007
|
//
|
23858
23008
|
// Given a Byte Sequence as input_bytes, return an ASCII string suitable
|
@@ -23884,12 +23034,6 @@ function serializeByteSequence(value) {
|
|
23884
23034
|
return `:${base64encode(value)}:`;
|
23885
23035
|
}
|
23886
23036
|
|
23887
|
-
const INTEGER = 'Integer';
|
23888
|
-
|
23889
|
-
function isInvalidInt(value) {
|
23890
|
-
return value < -999999999999999 || 999999999999999 < value;
|
23891
|
-
}
|
23892
|
-
|
23893
23037
|
// 4.1.4. Serializing an Integer
|
23894
23038
|
//
|
23895
23039
|
// Given an Integer as input_integer, return an ASCII string suitable
|
@@ -23955,8 +23099,6 @@ function roundToEven(value, precision) {
|
|
23955
23099
|
}
|
23956
23100
|
}
|
23957
23101
|
|
23958
|
-
const DECIMAL = 'Decimal';
|
23959
|
-
|
23960
23102
|
// 4.1.5. Serializing a Decimal
|
23961
23103
|
//
|
23962
23104
|
// Given a decimal number as input_decimal, return an ASCII string
|
@@ -24002,8 +23144,6 @@ function serializeDecimal(value) {
|
|
24002
23144
|
|
24003
23145
|
const STRING = 'String';
|
24004
23146
|
|
24005
|
-
const STRING_REGEX = /[\x00-\x1f\x7f]+/; // eslint-disable-line no-control-regex
|
24006
|
-
|
24007
23147
|
// 4.1.6. Serializing a String
|
24008
23148
|
//
|
24009
23149
|
// Given a String as input_string, return an ASCII string suitable for
|
@@ -24039,8 +23179,6 @@ function symbolToStr(symbol) {
|
|
24039
23179
|
return symbol.description || symbol.toString().slice(7, -1);
|
24040
23180
|
}
|
24041
23181
|
|
24042
|
-
const TOKEN = 'Token';
|
24043
|
-
|
24044
23182
|
function serializeToken(token) {
|
24045
23183
|
const value = symbolToStr(token);
|
24046
23184
|
if (/^([a-zA-Z*])([!#$%&'*+\-.^_`|~\w:/]*)$/.test(value) === false) {
|
@@ -24108,8 +23246,6 @@ function serializeBareItem(value) {
|
|
24108
23246
|
}
|
24109
23247
|
}
|
24110
23248
|
|
24111
|
-
const KEY = 'Key';
|
24112
|
-
|
24113
23249
|
// 4.1.1.3. Serializing a Key
|
24114
23250
|
//
|
24115
23251
|
// Given a key as input_key, return an ASCII string suitable for use in
|
@@ -24351,6 +23487,36 @@ function urlToRelativePath(url, base) {
|
|
24351
23487
|
return toPath.join('/');
|
24352
23488
|
}
|
24353
23489
|
|
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
|
+
|
24354
23520
|
const toRounded = value => Math.round(value);
|
24355
23521
|
const toUrlSafe = (value, options) => {
|
24356
23522
|
if (options != null && options.baseUrl) {
|
@@ -24576,36 +23742,6 @@ function appendCmcdQuery(url, cmcd, options) {
|
|
24576
23742
|
return `${url}${separator}${query}`;
|
24577
23743
|
}
|
24578
23744
|
|
24579
|
-
/**
|
24580
|
-
* Generate a random v4 UUID
|
24581
|
-
*
|
24582
|
-
* @returns A random v4 UUID
|
24583
|
-
*
|
24584
|
-
* @group Utils
|
24585
|
-
*
|
24586
|
-
* @beta
|
24587
|
-
*/
|
24588
|
-
function uuid() {
|
24589
|
-
try {
|
24590
|
-
return crypto.randomUUID();
|
24591
|
-
} catch (error) {
|
24592
|
-
try {
|
24593
|
-
const url = URL.createObjectURL(new Blob());
|
24594
|
-
const uuid = url.toString();
|
24595
|
-
URL.revokeObjectURL(url);
|
24596
|
-
return uuid.slice(uuid.lastIndexOf('/') + 1);
|
24597
|
-
} catch (error) {
|
24598
|
-
let dt = new Date().getTime();
|
24599
|
-
const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
24600
|
-
const r = (dt + Math.random() * 16) % 16 | 0;
|
24601
|
-
dt = Math.floor(dt / 16);
|
24602
|
-
return (c == 'x' ? r : r & 0x3 | 0x8).toString(16);
|
24603
|
-
});
|
24604
|
-
return uuid;
|
24605
|
-
}
|
24606
|
-
}
|
24607
|
-
}
|
24608
|
-
|
24609
23745
|
/**
|
24610
23746
|
* Controller to deal with Common Media Client Data (CMCD)
|
24611
23747
|
* @see https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf
|
@@ -24649,7 +23785,7 @@ class CMCDController {
|
|
24649
23785
|
su: !this.initialized
|
24650
23786
|
});
|
24651
23787
|
} catch (error) {
|
24652
|
-
|
23788
|
+
logger.warn('Could not generate manifest CMCD data.', error);
|
24653
23789
|
}
|
24654
23790
|
};
|
24655
23791
|
/**
|
@@ -24669,15 +23805,9 @@ class CMCDController {
|
|
24669
23805
|
data.tb = this.getTopBandwidth(ot) / 1000;
|
24670
23806
|
data.bl = this.getBufferLength(ot);
|
24671
23807
|
}
|
24672
|
-
const next = this.getNextFrag(fragment);
|
24673
|
-
if (next) {
|
24674
|
-
if (next.url && next.url !== fragment.url) {
|
24675
|
-
data.nor = next.url;
|
24676
|
-
}
|
24677
|
-
}
|
24678
23808
|
this.apply(context, data);
|
24679
23809
|
} catch (error) {
|
24680
|
-
|
23810
|
+
logger.warn('Could not generate segment CMCD data.', error);
|
24681
23811
|
}
|
24682
23812
|
};
|
24683
23813
|
this.hls = hls;
|
@@ -24767,7 +23897,7 @@ class CMCDController {
|
|
24767
23897
|
data.su = this.buffering;
|
24768
23898
|
}
|
24769
23899
|
|
24770
|
-
// TODO: Implement rtp, nrr, dl
|
23900
|
+
// TODO: Implement rtp, nrr, nor, dl
|
24771
23901
|
|
24772
23902
|
const {
|
24773
23903
|
includeKeys
|
@@ -24778,28 +23908,15 @@ class CMCDController {
|
|
24778
23908
|
return acc;
|
24779
23909
|
}, {});
|
24780
23910
|
}
|
24781
|
-
const options = {
|
24782
|
-
baseUrl: context.url
|
24783
|
-
};
|
24784
23911
|
if (this.useHeaders) {
|
24785
23912
|
if (!context.headers) {
|
24786
23913
|
context.headers = {};
|
24787
23914
|
}
|
24788
|
-
appendCmcdHeaders(context.headers, data
|
23915
|
+
appendCmcdHeaders(context.headers, data);
|
24789
23916
|
} else {
|
24790
|
-
context.url = appendCmcdQuery(context.url, data
|
24791
|
-
}
|
24792
|
-
}
|
24793
|
-
getNextFrag(fragment) {
|
24794
|
-
var _this$hls$levels$frag;
|
24795
|
-
const levelDetails = (_this$hls$levels$frag = this.hls.levels[fragment.level]) == null ? void 0 : _this$hls$levels$frag.details;
|
24796
|
-
if (levelDetails) {
|
24797
|
-
const index = fragment.sn - levelDetails.startSN;
|
24798
|
-
return levelDetails.fragments[index + 1];
|
23917
|
+
context.url = appendCmcdQuery(context.url, data);
|
24799
23918
|
}
|
24800
|
-
return undefined;
|
24801
23919
|
}
|
24802
|
-
|
24803
23920
|
/**
|
24804
23921
|
* The CMCD object type.
|
24805
23922
|
*/
|
@@ -24928,10 +24045,10 @@ class CMCDController {
|
|
24928
24045
|
}
|
24929
24046
|
|
24930
24047
|
const PATHWAY_PENALTY_DURATION_MS = 300000;
|
24931
|
-
class ContentSteeringController
|
24048
|
+
class ContentSteeringController {
|
24932
24049
|
constructor(hls) {
|
24933
|
-
super('content-steering', hls.logger);
|
24934
24050
|
this.hls = void 0;
|
24051
|
+
this.log = void 0;
|
24935
24052
|
this.loader = null;
|
24936
24053
|
this.uri = null;
|
24937
24054
|
this.pathwayId = '.';
|
@@ -24946,6 +24063,7 @@ class ContentSteeringController extends Logger {
|
|
24946
24063
|
this.subtitleTracks = null;
|
24947
24064
|
this.penalizedPathways = {};
|
24948
24065
|
this.hls = hls;
|
24066
|
+
this.log = logger.log.bind(logger, `[content-steering]:`);
|
24949
24067
|
this.registerListeners();
|
24950
24068
|
}
|
24951
24069
|
registerListeners() {
|
@@ -25069,7 +24187,7 @@ class ContentSteeringController extends Logger {
|
|
25069
24187
|
errorAction.resolved = this.pathwayId !== errorPathway;
|
25070
24188
|
}
|
25071
24189
|
if (!errorAction.resolved) {
|
25072
|
-
|
24190
|
+
logger.warn(`Could not resolve ${data.details} ("${data.error.message}") with content-steering for Pathway: ${errorPathway} levels: ${levels ? levels.length : levels} priorities: ${JSON.stringify(pathwayPriority)} penalized: ${JSON.stringify(this.penalizedPathways)}`);
|
25073
24191
|
}
|
25074
24192
|
}
|
25075
24193
|
}
|
@@ -25240,7 +24358,7 @@ class ContentSteeringController extends Logger {
|
|
25240
24358
|
onSuccess: (response, stats, context, networkDetails) => {
|
25241
24359
|
this.log(`Loaded steering manifest: "${url}"`);
|
25242
24360
|
const steeringData = response.data;
|
25243
|
-
if (
|
24361
|
+
if (steeringData.VERSION !== 1) {
|
25244
24362
|
this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
|
25245
24363
|
return;
|
25246
24364
|
}
|
@@ -26210,7 +25328,7 @@ function timelineConfig() {
|
|
26210
25328
|
/**
|
26211
25329
|
* @ignore
|
26212
25330
|
*/
|
26213
|
-
function mergeConfig(defaultConfig, userConfig
|
25331
|
+
function mergeConfig(defaultConfig, userConfig) {
|
26214
25332
|
if ((userConfig.liveSyncDurationCount || userConfig.liveMaxLatencyDurationCount) && (userConfig.liveSyncDuration || userConfig.liveMaxLatencyDuration)) {
|
26215
25333
|
throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration");
|
26216
25334
|
}
|
@@ -26280,7 +25398,7 @@ function deepCpy(obj) {
|
|
26280
25398
|
/**
|
26281
25399
|
* @ignore
|
26282
25400
|
*/
|
26283
|
-
function enableStreamingMode(config
|
25401
|
+
function enableStreamingMode(config) {
|
26284
25402
|
const currentLoader = config.loader;
|
26285
25403
|
if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
|
26286
25404
|
// If a developer has configured their own loader, respect that choice
|
@@ -26297,9 +25415,10 @@ function enableStreamingMode(config, logger) {
|
|
26297
25415
|
}
|
26298
25416
|
}
|
26299
25417
|
|
25418
|
+
let chromeOrFirefox;
|
26300
25419
|
class LevelController extends BasePlaylistController {
|
26301
25420
|
constructor(hls, contentSteeringController) {
|
26302
|
-
super(hls, 'level-controller');
|
25421
|
+
super(hls, '[level-controller]');
|
26303
25422
|
this._levels = [];
|
26304
25423
|
this._firstLevel = -1;
|
26305
25424
|
this._maxAutoLevel = -1;
|
@@ -26370,15 +25489,23 @@ class LevelController extends BasePlaylistController {
|
|
26370
25489
|
let videoCodecFound = false;
|
26371
25490
|
let audioCodecFound = false;
|
26372
25491
|
data.levels.forEach(levelParsed => {
|
26373
|
-
var _videoCodec;
|
25492
|
+
var _audioCodec, _videoCodec;
|
26374
25493
|
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
|
26375
25497
|
let {
|
26376
25498
|
audioCodec,
|
26377
25499
|
videoCodec
|
26378
25500
|
} = 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
|
+
}
|
26379
25507
|
if (audioCodec) {
|
26380
|
-
|
26381
|
-
levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource) || undefined;
|
25508
|
+
levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource);
|
26382
25509
|
}
|
26383
25510
|
if (((_videoCodec = videoCodec) == null ? void 0 : _videoCodec.indexOf('avc1')) === 0) {
|
26384
25511
|
videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
|
@@ -26720,12 +25847,7 @@ class LevelController extends BasePlaylistController {
|
|
26720
25847
|
if (curLevel.fragmentError === 0) {
|
26721
25848
|
curLevel.loadError = 0;
|
26722
25849
|
}
|
26723
|
-
|
26724
|
-
let previousDetails = curLevel.details;
|
26725
|
-
if (previousDetails === data.details && previousDetails.advanced) {
|
26726
|
-
previousDetails = undefined;
|
26727
|
-
}
|
26728
|
-
this.playlistLoaded(level, data, previousDetails);
|
25850
|
+
this.playlistLoaded(level, data, curLevel.details);
|
26729
25851
|
} else if ((_data$deliveryDirecti2 = data.deliveryDirectives) != null && _data$deliveryDirecti2.skip) {
|
26730
25852
|
// received a delta playlist update that cannot be merged
|
26731
25853
|
details.deltaUpdateFailed = true;
|
@@ -26969,8 +26091,6 @@ class KeyLoader {
|
|
26969
26091
|
}
|
26970
26092
|
return this.loadKeyEME(keyInfo, frag);
|
26971
26093
|
case 'AES-128':
|
26972
|
-
case 'AES-256':
|
26973
|
-
case 'AES-256-CTR':
|
26974
26094
|
return this.loadKeyHTTP(keyInfo, frag);
|
26975
26095
|
default:
|
26976
26096
|
return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`)));
|
@@ -27108,9 +26228,8 @@ const STALL_MINIMUM_DURATION_MS = 250;
|
|
27108
26228
|
const MAX_START_GAP_JUMP = 2.0;
|
27109
26229
|
const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
|
27110
26230
|
const SKIP_BUFFER_RANGE_START = 0.05;
|
27111
|
-
class GapController
|
26231
|
+
class GapController {
|
27112
26232
|
constructor(config, media, fragmentTracker, hls) {
|
27113
|
-
super('gap-controller', hls.logger);
|
27114
26233
|
this.config = void 0;
|
27115
26234
|
this.media = null;
|
27116
26235
|
this.fragmentTracker = void 0;
|
@@ -27120,7 +26239,6 @@ class GapController extends Logger {
|
|
27120
26239
|
this.stalled = null;
|
27121
26240
|
this.moved = false;
|
27122
26241
|
this.seeking = false;
|
27123
|
-
this.ended = 0;
|
27124
26242
|
this.config = config;
|
27125
26243
|
this.media = media;
|
27126
26244
|
this.fragmentTracker = fragmentTracker;
|
@@ -27138,7 +26256,7 @@ class GapController extends Logger {
|
|
27138
26256
|
*
|
27139
26257
|
* @param lastCurrentTime - Previously read playhead position
|
27140
26258
|
*/
|
27141
|
-
poll(lastCurrentTime, activeFrag
|
26259
|
+
poll(lastCurrentTime, activeFrag) {
|
27142
26260
|
const {
|
27143
26261
|
config,
|
27144
26262
|
media,
|
@@ -27157,7 +26275,6 @@ class GapController extends Logger {
|
|
27157
26275
|
|
27158
26276
|
// The playhead is moving, no-op
|
27159
26277
|
if (currentTime !== lastCurrentTime) {
|
27160
|
-
this.ended = 0;
|
27161
26278
|
this.moved = true;
|
27162
26279
|
if (!seeking) {
|
27163
26280
|
this.nudgeRetry = 0;
|
@@ -27166,7 +26283,7 @@ class GapController extends Logger {
|
|
27166
26283
|
// The playhead is now moving, but was previously stalled
|
27167
26284
|
if (this.stallReported) {
|
27168
26285
|
const _stalledDuration = self.performance.now() - stalled;
|
27169
|
-
|
26286
|
+
logger.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
|
27170
26287
|
this.stallReported = false;
|
27171
26288
|
}
|
27172
26289
|
this.stalled = null;
|
@@ -27202,6 +26319,7 @@ class GapController extends Logger {
|
|
27202
26319
|
// Skip start gaps if we haven't played, but the last poll detected the start of a stall
|
27203
26320
|
// The addition poll gives the browser a chance to jump the gap for us
|
27204
26321
|
if (!this.moved && this.stalled !== null) {
|
26322
|
+
var _level$details;
|
27205
26323
|
// There is no playable buffer (seeked, waiting for buffer)
|
27206
26324
|
const isBuffered = bufferInfo.len > 0;
|
27207
26325
|
if (!isBuffered && !nextStart) {
|
@@ -27213,8 +26331,9 @@ class GapController extends Logger {
|
|
27213
26331
|
// When joining a live stream with audio tracks, account for live playlist window sliding by allowing
|
27214
26332
|
// a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
|
27215
26333
|
// that begins over 1 target duration after the video start position.
|
27216
|
-
const
|
27217
|
-
const
|
26334
|
+
const level = this.hls.levels ? this.hls.levels[this.hls.currentLevel] : null;
|
26335
|
+
const isLive = level == null ? void 0 : (_level$details = level.details) == null ? void 0 : _level$details.live;
|
26336
|
+
const maxStartGapJump = isLive ? level.details.targetduration * 2 : MAX_START_GAP_JUMP;
|
27218
26337
|
const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
|
27219
26338
|
if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
|
27220
26339
|
if (!media.paused) {
|
@@ -27232,17 +26351,6 @@ class GapController extends Logger {
|
|
27232
26351
|
}
|
27233
26352
|
const stalledDuration = tnow - stalled;
|
27234
26353
|
if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) {
|
27235
|
-
// Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
|
27236
|
-
if (state === State.ENDED && !(levelDetails && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? void 0 : levelDetails.edge) || 0)) < 1) {
|
27237
|
-
if (stalledDuration < 1000 || this.ended) {
|
27238
|
-
return;
|
27239
|
-
}
|
27240
|
-
this.ended = currentTime;
|
27241
|
-
this.hls.trigger(Events.MEDIA_ENDED, {
|
27242
|
-
stalled: true
|
27243
|
-
});
|
27244
|
-
return;
|
27245
|
-
}
|
27246
26354
|
// Report stalling after trying to fix
|
27247
26355
|
this._reportStall(bufferInfo);
|
27248
26356
|
if (!this.media) {
|
@@ -27286,7 +26394,7 @@ class GapController extends Logger {
|
|
27286
26394
|
// needs to cross some sort of threshold covering all source-buffers content
|
27287
26395
|
// to start playing properly.
|
27288
26396
|
if ((bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
|
27289
|
-
|
26397
|
+
logger.warn('Trying to nudge playhead over buffer-hole');
|
27290
26398
|
// Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
|
27291
26399
|
// We only try to jump the hole if it's under the configured size
|
27292
26400
|
// Reset stalled so to rearm watchdog timer
|
@@ -27310,7 +26418,7 @@ class GapController extends Logger {
|
|
27310
26418
|
// Report stalled error once
|
27311
26419
|
this.stallReported = true;
|
27312
26420
|
const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${JSON.stringify(bufferInfo)})`);
|
27313
|
-
|
26421
|
+
logger.warn(error.message);
|
27314
26422
|
hls.trigger(Events.ERROR, {
|
27315
26423
|
type: ErrorTypes.MEDIA_ERROR,
|
27316
26424
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
@@ -27378,7 +26486,7 @@ class GapController extends Logger {
|
|
27378
26486
|
}
|
27379
26487
|
}
|
27380
26488
|
const targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
|
27381
|
-
|
26489
|
+
logger.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
|
27382
26490
|
this.moved = true;
|
27383
26491
|
this.stalled = null;
|
27384
26492
|
media.currentTime = targetTime;
|
@@ -27419,7 +26527,7 @@ class GapController extends Logger {
|
|
27419
26527
|
const targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset;
|
27420
26528
|
// playback stalled in buffered area ... let's nudge currentTime to try to overcome this
|
27421
26529
|
const error = new Error(`Nudging 'currentTime' from ${currentTime} to ${targetTime}`);
|
27422
|
-
|
26530
|
+
logger.warn(error.message);
|
27423
26531
|
media.currentTime = targetTime;
|
27424
26532
|
hls.trigger(Events.ERROR, {
|
27425
26533
|
type: ErrorTypes.MEDIA_ERROR,
|
@@ -27429,7 +26537,7 @@ class GapController extends Logger {
|
|
27429
26537
|
});
|
27430
26538
|
} else {
|
27431
26539
|
const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`);
|
27432
|
-
|
26540
|
+
logger.error(error.message);
|
27433
26541
|
hls.trigger(Events.ERROR, {
|
27434
26542
|
type: ErrorTypes.MEDIA_ERROR,
|
27435
26543
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
@@ -27444,7 +26552,7 @@ const TICK_INTERVAL = 100; // how often to tick in ms
|
|
27444
26552
|
|
27445
26553
|
class StreamController extends BaseStreamController {
|
27446
26554
|
constructor(hls, fragmentTracker, keyLoader) {
|
27447
|
-
super(hls, fragmentTracker, keyLoader, 'stream-controller', PlaylistLevelType.MAIN);
|
26555
|
+
super(hls, fragmentTracker, keyLoader, '[stream-controller]', PlaylistLevelType.MAIN);
|
27448
26556
|
this.audioCodecSwap = false;
|
27449
26557
|
this.gapController = null;
|
27450
26558
|
this.level = -1;
|
@@ -27452,43 +26560,27 @@ class StreamController extends BaseStreamController {
|
|
27452
26560
|
this.altAudio = false;
|
27453
26561
|
this.audioOnly = false;
|
27454
26562
|
this.fragPlaying = null;
|
26563
|
+
this.onvplaying = null;
|
26564
|
+
this.onvseeked = null;
|
27455
26565
|
this.fragLastKbps = 0;
|
27456
26566
|
this.couldBacktrack = false;
|
27457
26567
|
this.backtrackFragment = null;
|
27458
26568
|
this.audioCodecSwitch = false;
|
27459
26569
|
this.videoBuffer = null;
|
27460
|
-
this.
|
27461
|
-
// tick to speed up FRAG_CHANGED triggering
|
27462
|
-
this.tick();
|
27463
|
-
};
|
27464
|
-
this.onMediaSeeked = () => {
|
27465
|
-
const media = this.media;
|
27466
|
-
const currentTime = media ? media.currentTime : null;
|
27467
|
-
if (isFiniteNumber(currentTime)) {
|
27468
|
-
this.log(`Media seeked to ${currentTime.toFixed(3)}`);
|
27469
|
-
}
|
27470
|
-
|
27471
|
-
// If seeked was issued before buffer was appended do not tick immediately
|
27472
|
-
const bufferInfo = this.getMainFwdBufferInfo();
|
27473
|
-
if (bufferInfo === null || bufferInfo.len === 0) {
|
27474
|
-
this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
|
27475
|
-
return;
|
27476
|
-
}
|
27477
|
-
|
27478
|
-
// tick to speed up FRAG_CHANGED triggering
|
27479
|
-
this.tick();
|
27480
|
-
};
|
27481
|
-
this.registerListeners();
|
26570
|
+
this._registerListeners();
|
27482
26571
|
}
|
27483
|
-
|
27484
|
-
super.registerListeners();
|
26572
|
+
_registerListeners() {
|
27485
26573
|
const {
|
27486
26574
|
hls
|
27487
26575
|
} = 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);
|
27488
26579
|
hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
27489
26580
|
hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
|
27490
26581
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
27491
26582
|
hls.on(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
|
26583
|
+
hls.on(Events.ERROR, this.onError, this);
|
27492
26584
|
hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
27493
26585
|
hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
27494
26586
|
hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -27496,14 +26588,17 @@ class StreamController extends BaseStreamController {
|
|
27496
26588
|
hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
|
27497
26589
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
27498
26590
|
}
|
27499
|
-
|
27500
|
-
super.unregisterListeners();
|
26591
|
+
_unregisterListeners() {
|
27501
26592
|
const {
|
27502
26593
|
hls
|
27503
26594
|
} = 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);
|
27504
26598
|
hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
27505
26599
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
27506
26600
|
hls.off(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
|
26601
|
+
hls.off(Events.ERROR, this.onError, this);
|
27507
26602
|
hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
27508
26603
|
hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
27509
26604
|
hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -27512,9 +26607,7 @@ class StreamController extends BaseStreamController {
|
|
27512
26607
|
hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
27513
26608
|
}
|
27514
26609
|
onHandlerDestroying() {
|
27515
|
-
|
27516
|
-
this.onMediaPlaying = this.onMediaSeeked = null;
|
27517
|
-
this.unregisterListeners();
|
26610
|
+
this._unregisterListeners();
|
27518
26611
|
super.onHandlerDestroying();
|
27519
26612
|
}
|
27520
26613
|
startLoad(startPosition) {
|
@@ -27618,15 +26711,11 @@ class StreamController extends BaseStreamController {
|
|
27618
26711
|
levels,
|
27619
26712
|
media
|
27620
26713
|
} = this;
|
27621
|
-
const {
|
27622
|
-
config,
|
27623
|
-
nextLoadLevel: level
|
27624
|
-
} = hls;
|
27625
26714
|
|
27626
26715
|
// if start level not parsed yet OR
|
27627
26716
|
// if video not attached AND start fragment already requested OR start frag prefetch not enabled
|
27628
26717
|
// exit loop, as we either need more info (level not parsed) or we need media to be attached to load new fragment
|
27629
|
-
if (levelLastLoaded === null || !media && (this.startFragRequested || !config.startFragPrefetch)) {
|
26718
|
+
if (levelLastLoaded === null || !media && (this.startFragRequested || !hls.config.startFragPrefetch)) {
|
27630
26719
|
return;
|
27631
26720
|
}
|
27632
26721
|
|
@@ -27634,7 +26723,8 @@ class StreamController extends BaseStreamController {
|
|
27634
26723
|
if (this.altAudio && this.audioOnly) {
|
27635
26724
|
return;
|
27636
26725
|
}
|
27637
|
-
|
26726
|
+
const level = hls.nextLoadLevel;
|
26727
|
+
if (!(levels != null && levels[level])) {
|
27638
26728
|
return;
|
27639
26729
|
}
|
27640
26730
|
const levelInfo = levels[level];
|
@@ -27842,17 +26932,20 @@ class StreamController extends BaseStreamController {
|
|
27842
26932
|
onMediaAttached(event, data) {
|
27843
26933
|
super.onMediaAttached(event, data);
|
27844
26934
|
const media = data.media;
|
27845
|
-
|
27846
|
-
|
26935
|
+
this.onvplaying = this.onMediaPlaying.bind(this);
|
26936
|
+
this.onvseeked = this.onMediaSeeked.bind(this);
|
26937
|
+
media.addEventListener('playing', this.onvplaying);
|
26938
|
+
media.addEventListener('seeked', this.onvseeked);
|
27847
26939
|
this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
|
27848
26940
|
}
|
27849
26941
|
onMediaDetaching() {
|
27850
26942
|
const {
|
27851
26943
|
media
|
27852
26944
|
} = this;
|
27853
|
-
if (media) {
|
27854
|
-
media.removeEventListener('playing', this.
|
27855
|
-
media.removeEventListener('seeked', this.
|
26945
|
+
if (media && this.onvplaying && this.onvseeked) {
|
26946
|
+
media.removeEventListener('playing', this.onvplaying);
|
26947
|
+
media.removeEventListener('seeked', this.onvseeked);
|
26948
|
+
this.onvplaying = this.onvseeked = null;
|
27856
26949
|
this.videoBuffer = null;
|
27857
26950
|
}
|
27858
26951
|
this.fragPlaying = null;
|
@@ -27862,6 +26955,27 @@ class StreamController extends BaseStreamController {
|
|
27862
26955
|
}
|
27863
26956
|
super.onMediaDetaching();
|
27864
26957
|
}
|
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
|
+
}
|
27865
26979
|
onManifestLoading() {
|
27866
26980
|
// reset buffer on manifest loading
|
27867
26981
|
this.log('Trigger BUFFER_RESET');
|
@@ -28153,10 +27267,8 @@ class StreamController extends BaseStreamController {
|
|
28153
27267
|
}
|
28154
27268
|
if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) {
|
28155
27269
|
// Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
|
28156
|
-
const
|
28157
|
-
|
28158
|
-
const levelDetails = this.getLevelDetails();
|
28159
|
-
gapController.poll(this.lastCurrentTime, activeFrag, levelDetails, state);
|
27270
|
+
const activeFrag = this.state !== State.IDLE ? this.fragCurrent : null;
|
27271
|
+
gapController.poll(this.lastCurrentTime, activeFrag);
|
28160
27272
|
}
|
28161
27273
|
this.lastCurrentTime = media.currentTime;
|
28162
27274
|
}
|
@@ -28594,7 +27706,7 @@ class Hls {
|
|
28594
27706
|
* Get the video-dev/hls.js package version.
|
28595
27707
|
*/
|
28596
27708
|
static get version() {
|
28597
|
-
return "1.5.6
|
27709
|
+
return "1.5.6";
|
28598
27710
|
}
|
28599
27711
|
|
28600
27712
|
/**
|
@@ -28657,12 +27769,9 @@ class Hls {
|
|
28657
27769
|
* The configuration object provided on player instantiation.
|
28658
27770
|
*/
|
28659
27771
|
this.userConfig = void 0;
|
28660
|
-
/**
|
28661
|
-
* The logger functions used by this player instance, configured on player instantiation.
|
28662
|
-
*/
|
28663
|
-
this.logger = void 0;
|
28664
27772
|
this.coreComponents = void 0;
|
28665
27773
|
this.networkControllers = void 0;
|
27774
|
+
this.started = false;
|
28666
27775
|
this._emitter = new EventEmitter();
|
28667
27776
|
this._autoLevelCapping = -1;
|
28668
27777
|
this._maxHdcpLevel = null;
|
@@ -28679,11 +27788,11 @@ class Hls {
|
|
28679
27788
|
this._media = null;
|
28680
27789
|
this.url = null;
|
28681
27790
|
this.triggeringException = void 0;
|
28682
|
-
|
28683
|
-
const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig
|
27791
|
+
enableLogs(userConfig.debug || false, 'Hls instance');
|
27792
|
+
const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig);
|
28684
27793
|
this.userConfig = userConfig;
|
28685
27794
|
if (config.progressive) {
|
28686
|
-
enableStreamingMode(config
|
27795
|
+
enableStreamingMode(config);
|
28687
27796
|
}
|
28688
27797
|
|
28689
27798
|
// core controllers and network loaders
|
@@ -28782,7 +27891,7 @@ class Hls {
|
|
28782
27891
|
try {
|
28783
27892
|
return this.emit(event, event, eventObject);
|
28784
27893
|
} catch (error) {
|
28785
|
-
|
27894
|
+
logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
|
28786
27895
|
// Prevent recursion in error event handlers that throw #5497
|
28787
27896
|
if (!this.triggeringException) {
|
28788
27897
|
this.triggeringException = true;
|
@@ -28808,7 +27917,7 @@ class Hls {
|
|
28808
27917
|
* Dispose of the instance
|
28809
27918
|
*/
|
28810
27919
|
destroy() {
|
28811
|
-
|
27920
|
+
logger.log('destroy');
|
28812
27921
|
this.trigger(Events.DESTROYING, undefined);
|
28813
27922
|
this.detachMedia();
|
28814
27923
|
this.removeAllListeners();
|
@@ -28829,7 +27938,7 @@ class Hls {
|
|
28829
27938
|
* Attaches Hls.js to a media element
|
28830
27939
|
*/
|
28831
27940
|
attachMedia(media) {
|
28832
|
-
|
27941
|
+
logger.log('attachMedia');
|
28833
27942
|
this._media = media;
|
28834
27943
|
this.trigger(Events.MEDIA_ATTACHING, {
|
28835
27944
|
media: media
|
@@ -28840,7 +27949,7 @@ class Hls {
|
|
28840
27949
|
* Detach Hls.js from the media
|
28841
27950
|
*/
|
28842
27951
|
detachMedia() {
|
28843
|
-
|
27952
|
+
logger.log('detachMedia');
|
28844
27953
|
this.trigger(Events.MEDIA_DETACHING, undefined);
|
28845
27954
|
this._media = null;
|
28846
27955
|
}
|
@@ -28857,7 +27966,7 @@ class Hls {
|
|
28857
27966
|
});
|
28858
27967
|
this._autoLevelCapping = -1;
|
28859
27968
|
this._maxHdcpLevel = null;
|
28860
|
-
|
27969
|
+
logger.log(`loadSource:${loadingSource}`);
|
28861
27970
|
if (media && loadedSource && (loadedSource !== loadingSource || this.bufferController.hasSourceTypes())) {
|
28862
27971
|
this.detachMedia();
|
28863
27972
|
this.attachMedia(media);
|
@@ -28876,7 +27985,8 @@ class Hls {
|
|
28876
27985
|
* Defaults to -1 (None: starts from earliest point)
|
28877
27986
|
*/
|
28878
27987
|
startLoad(startPosition = -1) {
|
28879
|
-
|
27988
|
+
logger.log(`startLoad(${startPosition})`);
|
27989
|
+
this.started = true;
|
28880
27990
|
this.networkControllers.forEach(controller => {
|
28881
27991
|
controller.startLoad(startPosition);
|
28882
27992
|
});
|
@@ -28886,31 +27996,34 @@ class Hls {
|
|
28886
27996
|
* Stop loading of any stream data.
|
28887
27997
|
*/
|
28888
27998
|
stopLoad() {
|
28889
|
-
|
27999
|
+
logger.log('stopLoad');
|
28000
|
+
this.started = false;
|
28890
28001
|
this.networkControllers.forEach(controller => {
|
28891
28002
|
controller.stopLoad();
|
28892
28003
|
});
|
28893
28004
|
}
|
28894
28005
|
|
28895
28006
|
/**
|
28896
|
-
* Resumes stream controller segment loading
|
28007
|
+
* Resumes stream controller segment loading if previously started.
|
28897
28008
|
*/
|
28898
28009
|
resumeBuffering() {
|
28899
|
-
this.
|
28900
|
-
|
28901
|
-
controller
|
28902
|
-
|
28903
|
-
|
28010
|
+
if (this.started) {
|
28011
|
+
this.networkControllers.forEach(controller => {
|
28012
|
+
if ('fragmentLoader' in controller) {
|
28013
|
+
controller.startLoad(-1);
|
28014
|
+
}
|
28015
|
+
});
|
28016
|
+
}
|
28904
28017
|
}
|
28905
28018
|
|
28906
28019
|
/**
|
28907
|
-
*
|
28020
|
+
* Stops stream controller segment loading without changing 'started' state like stopLoad().
|
28908
28021
|
* This allows for media buffering to be paused without interupting playlist loading.
|
28909
28022
|
*/
|
28910
28023
|
pauseBuffering() {
|
28911
28024
|
this.networkControllers.forEach(controller => {
|
28912
|
-
if (controller
|
28913
|
-
controller.
|
28025
|
+
if ('fragmentLoader' in controller) {
|
28026
|
+
controller.stopLoad();
|
28914
28027
|
}
|
28915
28028
|
});
|
28916
28029
|
}
|
@@ -28919,7 +28032,7 @@ class Hls {
|
|
28919
28032
|
* Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
|
28920
28033
|
*/
|
28921
28034
|
swapAudioCodec() {
|
28922
|
-
|
28035
|
+
logger.log('swapAudioCodec');
|
28923
28036
|
this.streamController.swapAudioCodec();
|
28924
28037
|
}
|
28925
28038
|
|
@@ -28930,7 +28043,7 @@ class Hls {
|
|
28930
28043
|
* Automatic recovery of media-errors by this process is configurable.
|
28931
28044
|
*/
|
28932
28045
|
recoverMediaError() {
|
28933
|
-
|
28046
|
+
logger.log('recoverMediaError');
|
28934
28047
|
const media = this._media;
|
28935
28048
|
this.detachMedia();
|
28936
28049
|
if (media) {
|
@@ -28960,7 +28073,7 @@ class Hls {
|
|
28960
28073
|
* 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.
|
28961
28074
|
*/
|
28962
28075
|
set currentLevel(newLevel) {
|
28963
|
-
|
28076
|
+
logger.log(`set currentLevel:${newLevel}`);
|
28964
28077
|
this.levelController.manualLevel = newLevel;
|
28965
28078
|
this.streamController.immediateLevelSwitch();
|
28966
28079
|
}
|
@@ -28979,7 +28092,7 @@ class Hls {
|
|
28979
28092
|
* @param newLevel - Pass -1 for automatic level selection
|
28980
28093
|
*/
|
28981
28094
|
set nextLevel(newLevel) {
|
28982
|
-
|
28095
|
+
logger.log(`set nextLevel:${newLevel}`);
|
28983
28096
|
this.levelController.manualLevel = newLevel;
|
28984
28097
|
this.streamController.nextLevelSwitch();
|
28985
28098
|
}
|
@@ -28998,7 +28111,7 @@ class Hls {
|
|
28998
28111
|
* @param newLevel - Pass -1 for automatic level selection
|
28999
28112
|
*/
|
29000
28113
|
set loadLevel(newLevel) {
|
29001
|
-
|
28114
|
+
logger.log(`set loadLevel:${newLevel}`);
|
29002
28115
|
this.levelController.manualLevel = newLevel;
|
29003
28116
|
}
|
29004
28117
|
|
@@ -29029,7 +28142,7 @@ class Hls {
|
|
29029
28142
|
* Sets "first-level", see getter.
|
29030
28143
|
*/
|
29031
28144
|
set firstLevel(newLevel) {
|
29032
|
-
|
28145
|
+
logger.log(`set firstLevel:${newLevel}`);
|
29033
28146
|
this.levelController.firstLevel = newLevel;
|
29034
28147
|
}
|
29035
28148
|
|
@@ -29054,7 +28167,7 @@ class Hls {
|
|
29054
28167
|
* (determined from download of first segment)
|
29055
28168
|
*/
|
29056
28169
|
set startLevel(newLevel) {
|
29057
|
-
|
28170
|
+
logger.log(`set startLevel:${newLevel}`);
|
29058
28171
|
// if not in automatic start level detection, ensure startLevel is greater than minAutoLevel
|
29059
28172
|
if (newLevel !== -1) {
|
29060
28173
|
newLevel = Math.max(newLevel, this.minAutoLevel);
|
@@ -29129,7 +28242,7 @@ class Hls {
|
|
29129
28242
|
*/
|
29130
28243
|
set autoLevelCapping(newLevel) {
|
29131
28244
|
if (this._autoLevelCapping !== newLevel) {
|
29132
|
-
|
28245
|
+
logger.log(`set autoLevelCapping:${newLevel}`);
|
29133
28246
|
this._autoLevelCapping = newLevel;
|
29134
28247
|
this.levelController.checkMaxAutoUpdated();
|
29135
28248
|
}
|