hls.js 1.5.7 → 1.5.8-0.canary.10046
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 +2 -1
- package/dist/hls-demo.js +10 -0
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +2314 -1298
- package/dist/hls.js.d.ts +97 -84
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +1486 -1075
- 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 +1195 -789
- 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 +1979 -982
- 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 +22 -22
- package/src/config.ts +3 -2
- package/src/controller/abr-controller.ts +24 -20
- package/src/controller/audio-stream-controller.ts +68 -74
- package/src/controller/audio-track-controller.ts +1 -1
- package/src/controller/base-playlist-controller.ts +20 -8
- package/src/controller/base-stream-controller.ts +157 -36
- package/src/controller/buffer-controller.ts +203 -67
- package/src/controller/buffer-operation-queue.ts +16 -19
- package/src/controller/cap-level-controller.ts +2 -2
- package/src/controller/cmcd-controller.ts +27 -6
- package/src/controller/content-steering-controller.ts +8 -6
- package/src/controller/eme-controller.ts +9 -22
- package/src/controller/error-controller.ts +6 -8
- package/src/controller/fps-controller.ts +2 -3
- package/src/controller/fragment-tracker.ts +15 -11
- package/src/controller/gap-controller.ts +43 -16
- package/src/controller/latency-controller.ts +9 -11
- package/src/controller/level-controller.ts +12 -18
- package/src/controller/stream-controller.ts +36 -31
- package/src/controller/subtitle-stream-controller.ts +28 -40
- package/src/controller/subtitle-track-controller.ts +5 -3
- package/src/controller/timeline-controller.ts +23 -30
- package/src/crypt/aes-crypto.ts +21 -2
- package/src/crypt/decrypter-aes-mode.ts +4 -0
- package/src/crypt/decrypter.ts +32 -18
- package/src/crypt/fast-aes-key.ts +24 -5
- package/src/demux/audio/adts.ts +9 -4
- package/src/demux/sample-aes.ts +2 -0
- package/src/demux/transmuxer-interface.ts +4 -12
- package/src/demux/transmuxer-worker.ts +4 -4
- package/src/demux/transmuxer.ts +16 -3
- package/src/demux/tsdemuxer.ts +71 -37
- package/src/demux/video/avc-video-parser.ts +208 -119
- package/src/demux/video/base-video-parser.ts +134 -2
- package/src/demux/video/exp-golomb.ts +0 -208
- package/src/demux/video/hevc-video-parser.ts +746 -0
- package/src/events.ts +7 -0
- package/src/hls.ts +49 -37
- package/src/loader/fragment-loader.ts +9 -2
- package/src/loader/key-loader.ts +2 -0
- package/src/loader/level-key.ts +10 -9
- package/src/loader/playlist-loader.ts +4 -5
- package/src/remux/mp4-generator.ts +196 -1
- package/src/remux/mp4-remuxer.ts +23 -7
- package/src/task-loop.ts +5 -2
- package/src/types/component-api.ts +2 -0
- package/src/types/demuxer.ts +3 -0
- package/src/types/events.ts +4 -0
- package/src/utils/buffer-helper.ts +12 -31
- package/src/utils/codecs.ts +34 -5
- package/src/utils/encryption-methods-util.ts +21 -0
- package/src/utils/logger.ts +54 -24
- package/src/utils/mp4-tools.ts +4 -2
package/dist/hls.mjs
CHANGED
@@ -256,6 +256,7 @@ let Events = /*#__PURE__*/function (Events) {
|
|
256
256
|
Events["MEDIA_ATTACHED"] = "hlsMediaAttached";
|
257
257
|
Events["MEDIA_DETACHING"] = "hlsMediaDetaching";
|
258
258
|
Events["MEDIA_DETACHED"] = "hlsMediaDetached";
|
259
|
+
Events["MEDIA_ENDED"] = "hlsMediaEnded";
|
259
260
|
Events["BUFFER_RESET"] = "hlsBufferReset";
|
260
261
|
Events["BUFFER_CODECS"] = "hlsBufferCodecs";
|
261
262
|
Events["BUFFER_CREATED"] = "hlsBufferCreated";
|
@@ -369,58 +370,6 @@ let ErrorDetails = /*#__PURE__*/function (ErrorDetails) {
|
|
369
370
|
return ErrorDetails;
|
370
371
|
}({});
|
371
372
|
|
372
|
-
const noop = function noop() {};
|
373
|
-
const fakeLogger = {
|
374
|
-
trace: noop,
|
375
|
-
debug: noop,
|
376
|
-
log: noop,
|
377
|
-
warn: noop,
|
378
|
-
info: noop,
|
379
|
-
error: noop
|
380
|
-
};
|
381
|
-
let exportedLogger = fakeLogger;
|
382
|
-
|
383
|
-
// let lastCallTime;
|
384
|
-
// function formatMsgWithTimeInfo(type, msg) {
|
385
|
-
// const now = Date.now();
|
386
|
-
// const diff = lastCallTime ? '+' + (now - lastCallTime) : '0';
|
387
|
-
// lastCallTime = now;
|
388
|
-
// msg = (new Date(now)).toISOString() + ' | [' + type + '] > ' + msg + ' ( ' + diff + ' ms )';
|
389
|
-
// return msg;
|
390
|
-
// }
|
391
|
-
|
392
|
-
function consolePrintFn(type) {
|
393
|
-
const func = self.console[type];
|
394
|
-
if (func) {
|
395
|
-
return func.bind(self.console, `[${type}] >`);
|
396
|
-
}
|
397
|
-
return noop;
|
398
|
-
}
|
399
|
-
function exportLoggerFunctions(debugConfig, ...functions) {
|
400
|
-
functions.forEach(function (type) {
|
401
|
-
exportedLogger[type] = debugConfig[type] ? debugConfig[type].bind(debugConfig) : consolePrintFn(type);
|
402
|
-
});
|
403
|
-
}
|
404
|
-
function enableLogs(debugConfig, id) {
|
405
|
-
// check that console is available
|
406
|
-
if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
|
407
|
-
exportLoggerFunctions(debugConfig,
|
408
|
-
// Remove out from list here to hard-disable a log-level
|
409
|
-
// 'trace',
|
410
|
-
'debug', 'log', 'info', 'warn', 'error');
|
411
|
-
// Some browsers don't allow to use bind on console object anyway
|
412
|
-
// fallback to default if needed
|
413
|
-
try {
|
414
|
-
exportedLogger.log(`Debug logs enabled for "${id}" in hls.js version ${"1.5.7"}`);
|
415
|
-
} catch (e) {
|
416
|
-
exportedLogger = fakeLogger;
|
417
|
-
}
|
418
|
-
} else {
|
419
|
-
exportedLogger = fakeLogger;
|
420
|
-
}
|
421
|
-
}
|
422
|
-
const logger = exportedLogger;
|
423
|
-
|
424
373
|
const DECIMAL_RESOLUTION_REGEX = /^(\d+)x(\d+)$/;
|
425
374
|
const ATTR_LIST_REGEX = /(.+?)=(".*?"|.*?)(?:,|$)/g;
|
426
375
|
|
@@ -502,6 +451,79 @@ class AttrList {
|
|
502
451
|
}
|
503
452
|
}
|
504
453
|
|
454
|
+
class Logger {
|
455
|
+
constructor(label, logger) {
|
456
|
+
this.trace = void 0;
|
457
|
+
this.debug = void 0;
|
458
|
+
this.log = void 0;
|
459
|
+
this.warn = void 0;
|
460
|
+
this.info = void 0;
|
461
|
+
this.error = void 0;
|
462
|
+
const lb = `[${label}]:`;
|
463
|
+
this.trace = noop;
|
464
|
+
this.debug = logger.debug.bind(null, lb);
|
465
|
+
this.log = logger.log.bind(null, lb);
|
466
|
+
this.warn = logger.warn.bind(null, lb);
|
467
|
+
this.info = logger.info.bind(null, lb);
|
468
|
+
this.error = logger.error.bind(null, lb);
|
469
|
+
}
|
470
|
+
}
|
471
|
+
const noop = function noop() {};
|
472
|
+
const fakeLogger = {
|
473
|
+
trace: noop,
|
474
|
+
debug: noop,
|
475
|
+
log: noop,
|
476
|
+
warn: noop,
|
477
|
+
info: noop,
|
478
|
+
error: noop
|
479
|
+
};
|
480
|
+
function createLogger() {
|
481
|
+
return _extends({}, fakeLogger);
|
482
|
+
}
|
483
|
+
|
484
|
+
// let lastCallTime;
|
485
|
+
// function formatMsgWithTimeInfo(type, msg) {
|
486
|
+
// const now = Date.now();
|
487
|
+
// const diff = lastCallTime ? '+' + (now - lastCallTime) : '0';
|
488
|
+
// lastCallTime = now;
|
489
|
+
// msg = (new Date(now)).toISOString() + ' | [' + type + '] > ' + msg + ' ( ' + diff + ' ms )';
|
490
|
+
// return msg;
|
491
|
+
// }
|
492
|
+
|
493
|
+
function consolePrintFn(type, id) {
|
494
|
+
const func = self.console[type];
|
495
|
+
return func ? func.bind(self.console, `${id ? '[' + id + '] ' : ''}[${type}] >`) : noop;
|
496
|
+
}
|
497
|
+
function getLoggerFn(key, debugConfig, id) {
|
498
|
+
return debugConfig[key] ? debugConfig[key].bind(debugConfig) : consolePrintFn(key, id);
|
499
|
+
}
|
500
|
+
const exportedLogger = createLogger();
|
501
|
+
function enableLogs(debugConfig, context, id) {
|
502
|
+
// check that console is available
|
503
|
+
const newLogger = createLogger();
|
504
|
+
if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
|
505
|
+
const keys = [
|
506
|
+
// Remove out from list here to hard-disable a log-level
|
507
|
+
// 'trace',
|
508
|
+
'debug', 'log', 'info', 'warn', 'error'];
|
509
|
+
keys.forEach(key => {
|
510
|
+
newLogger[key] = getLoggerFn(key, debugConfig, id);
|
511
|
+
});
|
512
|
+
// Some browsers don't allow to use bind on console object anyway
|
513
|
+
// fallback to default if needed
|
514
|
+
try {
|
515
|
+
newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.5.8-0.canary.10046"}`);
|
516
|
+
} catch (e) {
|
517
|
+
/* log fn threw an exception. All logger methods are no-ops. */
|
518
|
+
return createLogger();
|
519
|
+
}
|
520
|
+
}
|
521
|
+
// global exported logger uses the log methods from last call to `enableLogs`
|
522
|
+
_extends(exportedLogger, newLogger);
|
523
|
+
return newLogger;
|
524
|
+
}
|
525
|
+
const logger = exportedLogger;
|
526
|
+
|
505
527
|
// Avoid exporting const enum so that these values can be inlined
|
506
528
|
|
507
529
|
function isDateRangeCueAttribute(attrName) {
|
@@ -1036,6 +1058,26 @@ function strToUtf8array(str) {
|
|
1036
1058
|
return Uint8Array.from(unescape(encodeURIComponent(str)), c => c.charCodeAt(0));
|
1037
1059
|
}
|
1038
1060
|
|
1061
|
+
var DecrypterAesMode = {
|
1062
|
+
cbc: 0,
|
1063
|
+
ctr: 1
|
1064
|
+
};
|
1065
|
+
|
1066
|
+
function isFullSegmentEncryption(method) {
|
1067
|
+
return method === 'AES-128' || method === 'AES-256' || method === 'AES-256-CTR';
|
1068
|
+
}
|
1069
|
+
function getAesModeFromFullSegmentMethod(method) {
|
1070
|
+
switch (method) {
|
1071
|
+
case 'AES-128':
|
1072
|
+
case 'AES-256':
|
1073
|
+
return DecrypterAesMode.cbc;
|
1074
|
+
case 'AES-256-CTR':
|
1075
|
+
return DecrypterAesMode.ctr;
|
1076
|
+
default:
|
1077
|
+
throw new Error(`invalid full segment method ${method}`);
|
1078
|
+
}
|
1079
|
+
}
|
1080
|
+
|
1039
1081
|
/** returns `undefined` is `self` is missing, e.g. in node */
|
1040
1082
|
const optionalSelf = typeof self !== 'undefined' ? self : undefined;
|
1041
1083
|
|
@@ -1784,7 +1826,7 @@ function parseStsd(stsd) {
|
|
1784
1826
|
{
|
1785
1827
|
const codecBox = findBox(sampleEntries, [fourCC])[0];
|
1786
1828
|
const esdsBox = findBox(codecBox.subarray(28), ['esds'])[0];
|
1787
|
-
if (esdsBox && esdsBox.length >
|
1829
|
+
if (esdsBox && esdsBox.length > 7) {
|
1788
1830
|
let i = 4;
|
1789
1831
|
// ES Descriptor tag
|
1790
1832
|
if (esdsBox[i++] !== 0x03) {
|
@@ -1899,7 +1941,9 @@ function parseStsd(stsd) {
|
|
1899
1941
|
}
|
1900
1942
|
function skipBERInteger(bytes, i) {
|
1901
1943
|
const limit = i + 5;
|
1902
|
-
while (bytes[i++] & 0x80 && i < limit) {
|
1944
|
+
while (bytes[i++] & 0x80 && i < limit) {
|
1945
|
+
/* do nothing */
|
1946
|
+
}
|
1903
1947
|
return i;
|
1904
1948
|
}
|
1905
1949
|
function toHex(x) {
|
@@ -2688,12 +2732,12 @@ class LevelKey {
|
|
2688
2732
|
this.keyFormatVersions = formatversions;
|
2689
2733
|
this.iv = iv;
|
2690
2734
|
this.encrypted = method ? method !== 'NONE' : false;
|
2691
|
-
this.isCommonEncryption = this.encrypted && method
|
2735
|
+
this.isCommonEncryption = this.encrypted && !isFullSegmentEncryption(method);
|
2692
2736
|
}
|
2693
2737
|
isSupported() {
|
2694
2738
|
// If it's Segment encryption or No encryption, just select that key system
|
2695
2739
|
if (this.method) {
|
2696
|
-
if (this.method
|
2740
|
+
if (isFullSegmentEncryption(this.method) || this.method === 'NONE') {
|
2697
2741
|
return true;
|
2698
2742
|
}
|
2699
2743
|
if (this.keyFormat === 'identity') {
|
@@ -2715,14 +2759,13 @@ class LevelKey {
|
|
2715
2759
|
if (!this.encrypted || !this.uri) {
|
2716
2760
|
return null;
|
2717
2761
|
}
|
2718
|
-
if (this.method
|
2762
|
+
if (isFullSegmentEncryption(this.method) && this.uri && !this.iv) {
|
2719
2763
|
if (typeof sn !== 'number') {
|
2720
2764
|
// We are fetching decryption data for a initialization segment
|
2721
|
-
// If the segment was encrypted with AES-128
|
2765
|
+
// If the segment was encrypted with AES-128/256
|
2722
2766
|
// It must have an IV defined. We cannot substitute the Segment Number in.
|
2723
|
-
|
2724
|
-
|
2725
|
-
}
|
2767
|
+
logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
|
2768
|
+
|
2726
2769
|
// Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
|
2727
2770
|
sn = 0;
|
2728
2771
|
}
|
@@ -3001,23 +3044,28 @@ function getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource =
|
|
3001
3044
|
if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) {
|
3002
3045
|
return CODEC_COMPATIBLE_NAMES[lowerCaseCodec];
|
3003
3046
|
}
|
3004
|
-
|
3005
|
-
// Idealy fLaC and Opus would be first (spec-compliant) but
|
3006
|
-
// some browsers will report that fLaC is supported then fail.
|
3007
|
-
// see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
|
3008
3047
|
const codecsToCheck = {
|
3048
|
+
// Idealy fLaC and Opus would be first (spec-compliant) but
|
3049
|
+
// some browsers will report that fLaC is supported then fail.
|
3050
|
+
// see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
|
3009
3051
|
flac: ['flac', 'fLaC', 'FLAC'],
|
3010
|
-
opus: ['opus', 'Opus']
|
3052
|
+
opus: ['opus', 'Opus'],
|
3053
|
+
// Replace audio codec info if browser does not support mp4a.40.34,
|
3054
|
+
// and demuxer can fallback to 'audio/mpeg' or 'audio/mp4;codecs="mp3"'
|
3055
|
+
'mp4a.40.34': ['mp3']
|
3011
3056
|
}[lowerCaseCodec];
|
3012
3057
|
for (let i = 0; i < codecsToCheck.length; i++) {
|
3058
|
+
var _getMediaSource;
|
3013
3059
|
if (isCodecMediaSourceSupported(codecsToCheck[i], 'audio', preferManagedMediaSource)) {
|
3014
3060
|
CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
|
3015
3061
|
return codecsToCheck[i];
|
3062
|
+
} else if (codecsToCheck[i] === 'mp3' && (_getMediaSource = getMediaSource(preferManagedMediaSource)) != null && _getMediaSource.isTypeSupported('audio/mpeg')) {
|
3063
|
+
return '';
|
3016
3064
|
}
|
3017
3065
|
}
|
3018
3066
|
return lowerCaseCodec;
|
3019
3067
|
}
|
3020
|
-
const AUDIO_CODEC_REGEXP = /flac|opus/i;
|
3068
|
+
const AUDIO_CODEC_REGEXP = /flac|opus|mp4a\.40\.34/i;
|
3021
3069
|
function getCodecCompatibleName(codec, preferManagedMediaSource = true) {
|
3022
3070
|
return codec.replace(AUDIO_CODEC_REGEXP, m => getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource));
|
3023
3071
|
}
|
@@ -3040,6 +3088,16 @@ function convertAVC1ToAVCOTI(codec) {
|
|
3040
3088
|
}
|
3041
3089
|
return codec;
|
3042
3090
|
}
|
3091
|
+
function getM2TSSupportedAudioTypes(preferManagedMediaSource) {
|
3092
|
+
const MediaSource = getMediaSource(preferManagedMediaSource) || {
|
3093
|
+
isTypeSupported: () => false
|
3094
|
+
};
|
3095
|
+
return {
|
3096
|
+
mpeg: MediaSource.isTypeSupported('audio/mpeg'),
|
3097
|
+
mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
|
3098
|
+
ac3: MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
|
3099
|
+
};
|
3100
|
+
}
|
3043
3101
|
|
3044
3102
|
const MASTER_PLAYLIST_REGEX = /#EXT-X-STREAM-INF:([^\r\n]*)(?:[\r\n](?:#[^\r\n]*)?)*([^\r\n]+)|#EXT-X-(SESSION-DATA|SESSION-KEY|DEFINE|CONTENT-STEERING|START):([^\r\n]*)[\r\n]+/g;
|
3045
3103
|
const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g;
|
@@ -3889,10 +3947,10 @@ class PlaylistLoader {
|
|
3889
3947
|
const loaderContext = loader.context;
|
3890
3948
|
if (loaderContext && loaderContext.url === context.url && loaderContext.level === context.level) {
|
3891
3949
|
// same URL can't overlap
|
3892
|
-
logger.trace('[playlist-loader]: playlist request ongoing');
|
3950
|
+
this.hls.logger.trace('[playlist-loader]: playlist request ongoing');
|
3893
3951
|
return;
|
3894
3952
|
}
|
3895
|
-
logger.log(`[playlist-loader]: aborting previous loader for type: ${context.type}`);
|
3953
|
+
this.hls.logger.log(`[playlist-loader]: aborting previous loader for type: ${context.type}`);
|
3896
3954
|
loader.abort();
|
3897
3955
|
}
|
3898
3956
|
|
@@ -4002,7 +4060,7 @@ class PlaylistLoader {
|
|
4002
4060
|
// alt audio rendition in which quality levels (main)
|
4003
4061
|
// contains both audio+video. but with mixed audio track not signaled
|
4004
4062
|
if (!embeddedAudioFound && levels[0].audioCodec && !levels[0].attrs.AUDIO) {
|
4005
|
-
logger.log('[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one');
|
4063
|
+
this.hls.logger.log('[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one');
|
4006
4064
|
audioTracks.unshift({
|
4007
4065
|
type: 'main',
|
4008
4066
|
name: 'main',
|
@@ -4101,7 +4159,7 @@ class PlaylistLoader {
|
|
4101
4159
|
message += ` id: ${context.id} group-id: "${context.groupId}"`;
|
4102
4160
|
}
|
4103
4161
|
const error = new Error(message);
|
4104
|
-
logger.warn(`[playlist-loader]: ${message}`);
|
4162
|
+
this.hls.logger.warn(`[playlist-loader]: ${message}`);
|
4105
4163
|
let details = ErrorDetails.UNKNOWN;
|
4106
4164
|
let fatal = false;
|
4107
4165
|
const loader = this.getInternalLoader(context);
|
@@ -4706,7 +4764,47 @@ class LatencyController {
|
|
4706
4764
|
this.currentTime = 0;
|
4707
4765
|
this.stallCount = 0;
|
4708
4766
|
this._latency = null;
|
4709
|
-
this.
|
4767
|
+
this.onTimeupdate = () => {
|
4768
|
+
const {
|
4769
|
+
media,
|
4770
|
+
levelDetails
|
4771
|
+
} = this;
|
4772
|
+
if (!media || !levelDetails) {
|
4773
|
+
return;
|
4774
|
+
}
|
4775
|
+
this.currentTime = media.currentTime;
|
4776
|
+
const latency = this.computeLatency();
|
4777
|
+
if (latency === null) {
|
4778
|
+
return;
|
4779
|
+
}
|
4780
|
+
this._latency = latency;
|
4781
|
+
|
4782
|
+
// Adapt playbackRate to meet target latency in low-latency mode
|
4783
|
+
const {
|
4784
|
+
lowLatencyMode,
|
4785
|
+
maxLiveSyncPlaybackRate
|
4786
|
+
} = this.config;
|
4787
|
+
if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
|
4788
|
+
return;
|
4789
|
+
}
|
4790
|
+
const targetLatency = this.targetLatency;
|
4791
|
+
if (targetLatency === null) {
|
4792
|
+
return;
|
4793
|
+
}
|
4794
|
+
const distanceFromTarget = latency - targetLatency;
|
4795
|
+
// Only adjust playbackRate when within one target duration of targetLatency
|
4796
|
+
// and more than one second from under-buffering.
|
4797
|
+
// Playback further than one target duration from target can be considered DVR playback.
|
4798
|
+
const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
|
4799
|
+
const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
|
4800
|
+
if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
|
4801
|
+
const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
|
4802
|
+
const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
|
4803
|
+
media.playbackRate = Math.min(max, Math.max(1, rate));
|
4804
|
+
} else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
|
4805
|
+
media.playbackRate = 1;
|
4806
|
+
}
|
4807
|
+
};
|
4710
4808
|
this.hls = hls;
|
4711
4809
|
this.config = hls.config;
|
4712
4810
|
this.registerListeners();
|
@@ -4798,7 +4896,7 @@ class LatencyController {
|
|
4798
4896
|
this.onMediaDetaching();
|
4799
4897
|
this.levelDetails = null;
|
4800
4898
|
// @ts-ignore
|
4801
|
-
this.hls =
|
4899
|
+
this.hls = null;
|
4802
4900
|
}
|
4803
4901
|
registerListeners() {
|
4804
4902
|
this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
@@ -4816,11 +4914,11 @@ class LatencyController {
|
|
4816
4914
|
}
|
4817
4915
|
onMediaAttached(event, data) {
|
4818
4916
|
this.media = data.media;
|
4819
|
-
this.media.addEventListener('timeupdate', this.
|
4917
|
+
this.media.addEventListener('timeupdate', this.onTimeupdate);
|
4820
4918
|
}
|
4821
4919
|
onMediaDetaching() {
|
4822
4920
|
if (this.media) {
|
4823
|
-
this.media.removeEventListener('timeupdate', this.
|
4921
|
+
this.media.removeEventListener('timeupdate', this.onTimeupdate);
|
4824
4922
|
this.media = null;
|
4825
4923
|
}
|
4826
4924
|
}
|
@@ -4834,10 +4932,10 @@ class LatencyController {
|
|
4834
4932
|
}) {
|
4835
4933
|
this.levelDetails = details;
|
4836
4934
|
if (details.advanced) {
|
4837
|
-
this.
|
4935
|
+
this.onTimeupdate();
|
4838
4936
|
}
|
4839
4937
|
if (!details.live && this.media) {
|
4840
|
-
this.media.removeEventListener('timeupdate', this.
|
4938
|
+
this.media.removeEventListener('timeupdate', this.onTimeupdate);
|
4841
4939
|
}
|
4842
4940
|
}
|
4843
4941
|
onError(event, data) {
|
@@ -4847,48 +4945,7 @@ class LatencyController {
|
|
4847
4945
|
}
|
4848
4946
|
this.stallCount++;
|
4849
4947
|
if ((_this$levelDetails = this.levelDetails) != null && _this$levelDetails.live) {
|
4850
|
-
logger.warn('[
|
4851
|
-
}
|
4852
|
-
}
|
4853
|
-
timeupdate() {
|
4854
|
-
const {
|
4855
|
-
media,
|
4856
|
-
levelDetails
|
4857
|
-
} = this;
|
4858
|
-
if (!media || !levelDetails) {
|
4859
|
-
return;
|
4860
|
-
}
|
4861
|
-
this.currentTime = media.currentTime;
|
4862
|
-
const latency = this.computeLatency();
|
4863
|
-
if (latency === null) {
|
4864
|
-
return;
|
4865
|
-
}
|
4866
|
-
this._latency = latency;
|
4867
|
-
|
4868
|
-
// Adapt playbackRate to meet target latency in low-latency mode
|
4869
|
-
const {
|
4870
|
-
lowLatencyMode,
|
4871
|
-
maxLiveSyncPlaybackRate
|
4872
|
-
} = this.config;
|
4873
|
-
if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
|
4874
|
-
return;
|
4875
|
-
}
|
4876
|
-
const targetLatency = this.targetLatency;
|
4877
|
-
if (targetLatency === null) {
|
4878
|
-
return;
|
4879
|
-
}
|
4880
|
-
const distanceFromTarget = latency - targetLatency;
|
4881
|
-
// Only adjust playbackRate when within one target duration of targetLatency
|
4882
|
-
// and more than one second from under-buffering.
|
4883
|
-
// Playback further than one target duration from target can be considered DVR playback.
|
4884
|
-
const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
|
4885
|
-
const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
|
4886
|
-
if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
|
4887
|
-
const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
|
4888
|
-
const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
|
4889
|
-
media.playbackRate = Math.min(max, Math.max(1, rate));
|
4890
|
-
} else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
|
4891
|
-
media.playbackRate = 1;
|
4948
|
+
this.hls.logger.warn('[latency-controller]: Stall detected, adjusting target latency');
|
4892
4949
|
}
|
4893
4950
|
}
|
4894
4951
|
estimateLiveEdge() {
|
@@ -5660,18 +5717,13 @@ var ErrorActionFlags = {
|
|
5660
5717
|
MoveAllAlternatesMatchingHDCP: 2,
|
5661
5718
|
SwitchToSDR: 4
|
5662
5719
|
}; // Reserved for future use
|
5663
|
-
class ErrorController {
|
5720
|
+
class ErrorController extends Logger {
|
5664
5721
|
constructor(hls) {
|
5722
|
+
super('error-controller', hls.logger);
|
5665
5723
|
this.hls = void 0;
|
5666
5724
|
this.playlistError = 0;
|
5667
5725
|
this.penalizedRenditions = {};
|
5668
|
-
this.log = void 0;
|
5669
|
-
this.warn = void 0;
|
5670
|
-
this.error = void 0;
|
5671
5726
|
this.hls = hls;
|
5672
|
-
this.log = logger.log.bind(logger, `[info]:`);
|
5673
|
-
this.warn = logger.warn.bind(logger, `[warning]:`);
|
5674
|
-
this.error = logger.error.bind(logger, `[error]:`);
|
5675
5727
|
this.registerListeners();
|
5676
5728
|
}
|
5677
5729
|
registerListeners() {
|
@@ -6023,16 +6075,13 @@ class ErrorController {
|
|
6023
6075
|
}
|
6024
6076
|
}
|
6025
6077
|
|
6026
|
-
class BasePlaylistController {
|
6078
|
+
class BasePlaylistController extends Logger {
|
6027
6079
|
constructor(hls, logPrefix) {
|
6080
|
+
super(logPrefix, hls.logger);
|
6028
6081
|
this.hls = void 0;
|
6029
6082
|
this.timer = -1;
|
6030
6083
|
this.requestScheduled = -1;
|
6031
6084
|
this.canLoad = false;
|
6032
|
-
this.log = void 0;
|
6033
|
-
this.warn = void 0;
|
6034
|
-
this.log = logger.log.bind(logger, `${logPrefix}:`);
|
6035
|
-
this.warn = logger.warn.bind(logger, `${logPrefix}:`);
|
6036
6085
|
this.hls = hls;
|
6037
6086
|
}
|
6038
6087
|
destroy() {
|
@@ -6065,7 +6114,7 @@ class BasePlaylistController {
|
|
6065
6114
|
try {
|
6066
6115
|
uri = new self.URL(attr.URI, previous.url).href;
|
6067
6116
|
} catch (error) {
|
6068
|
-
|
6117
|
+
this.warn(`Could not construct new URL for Rendition Report: ${error}`);
|
6069
6118
|
uri = attr.URI || '';
|
6070
6119
|
}
|
6071
6120
|
// Use exact match. Otherwise, the last partial match, if any, will be used
|
@@ -6152,7 +6201,12 @@ class BasePlaylistController {
|
|
6152
6201
|
const cdnAge = lastAdvanced + details.ageHeader;
|
6153
6202
|
let currentGoal = Math.min(cdnAge - details.partTarget, details.targetduration * 1.5);
|
6154
6203
|
if (currentGoal > 0) {
|
6155
|
-
if (
|
6204
|
+
if (cdnAge > details.targetduration * 3) {
|
6205
|
+
// Omit segment and part directives when the last response was more than 3 target durations ago,
|
6206
|
+
this.log(`Playlist last advanced ${lastAdvanced.toFixed(2)}s ago. Omitting segment and part directives.`);
|
6207
|
+
msn = undefined;
|
6208
|
+
part = undefined;
|
6209
|
+
} else if (previousDetails != null && previousDetails.tuneInGoal && cdnAge - details.partTarget > previousDetails.tuneInGoal) {
|
6156
6210
|
// If we attempted to get the next or latest playlist update, but currentGoal increased,
|
6157
6211
|
// then we either can't catchup, or the "age" header cannot be trusted.
|
6158
6212
|
this.warn(`CDN Tune-in goal increased from: ${previousDetails.tuneInGoal} to: ${currentGoal} with playlist age: ${details.age}`);
|
@@ -6824,8 +6878,9 @@ function searchDownAndUpList(arr, searchIndex, predicate) {
|
|
6824
6878
|
return -1;
|
6825
6879
|
}
|
6826
6880
|
|
6827
|
-
class AbrController {
|
6881
|
+
class AbrController extends Logger {
|
6828
6882
|
constructor(_hls) {
|
6883
|
+
super('abr', _hls.logger);
|
6829
6884
|
this.hls = void 0;
|
6830
6885
|
this.lastLevelLoadSec = 0;
|
6831
6886
|
this.lastLoadedFragLevel = -1;
|
@@ -6939,7 +6994,7 @@ class AbrController {
|
|
6939
6994
|
this.resetEstimator(nextLoadLevelBitrate);
|
6940
6995
|
}
|
6941
6996
|
this.clearTimer();
|
6942
|
-
|
6997
|
+
this.warn(`Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
|
6943
6998
|
Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s
|
6944
6999
|
Estimated load time for current fragment: ${fragLoadedDelay.toFixed(3)} s
|
6945
7000
|
Estimated load time for down switch fragment: ${fragLevelNextLoadedDelay.toFixed(3)} s
|
@@ -6959,7 +7014,7 @@ class AbrController {
|
|
6959
7014
|
}
|
6960
7015
|
resetEstimator(abrEwmaDefaultEstimate) {
|
6961
7016
|
if (abrEwmaDefaultEstimate) {
|
6962
|
-
|
7017
|
+
this.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
|
6963
7018
|
this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate;
|
6964
7019
|
}
|
6965
7020
|
this.firstSelection = -1;
|
@@ -7191,7 +7246,7 @@ class AbrController {
|
|
7191
7246
|
}
|
7192
7247
|
const firstLevel = this.hls.firstLevel;
|
7193
7248
|
const clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel);
|
7194
|
-
|
7249
|
+
this.warn(`Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
|
7195
7250
|
return clamped;
|
7196
7251
|
}
|
7197
7252
|
get forcedAutoLevel() {
|
@@ -7237,6 +7292,9 @@ class AbrController {
|
|
7237
7292
|
partCurrent,
|
7238
7293
|
hls
|
7239
7294
|
} = this;
|
7295
|
+
if (hls.levels.length <= 1) {
|
7296
|
+
return hls.loadLevel;
|
7297
|
+
}
|
7240
7298
|
const {
|
7241
7299
|
maxAutoLevel,
|
7242
7300
|
config,
|
@@ -7269,13 +7327,13 @@ class AbrController {
|
|
7269
7327
|
// cap maxLoadingDelay and ensure it is not bigger 'than bitrate test' frag duration
|
7270
7328
|
const maxLoadingDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxLoadingDelay) : config.maxLoadingDelay;
|
7271
7329
|
maxStarvationDelay = maxLoadingDelay - bitrateTestDelay;
|
7272
|
-
|
7330
|
+
this.info(`bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
|
7273
7331
|
// don't use conservative factor on bitrate test
|
7274
7332
|
bwFactor = bwUpFactor = 1;
|
7275
7333
|
}
|
7276
7334
|
}
|
7277
7335
|
const bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor);
|
7278
|
-
|
7336
|
+
this.info(`${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
|
7279
7337
|
if (bestLevel > -1) {
|
7280
7338
|
return bestLevel;
|
7281
7339
|
}
|
@@ -7349,7 +7407,7 @@ class AbrController {
|
|
7349
7407
|
currentVideoRange = preferHDR ? videoRanges[videoRanges.length - 1] : videoRanges[0];
|
7350
7408
|
currentFrameRate = minFramerate;
|
7351
7409
|
currentBw = Math.max(currentBw, minBitrate);
|
7352
|
-
|
7410
|
+
this.log(`picked start tier ${JSON.stringify(startTier)}`);
|
7353
7411
|
} else {
|
7354
7412
|
currentCodecSet = level == null ? void 0 : level.codecSet;
|
7355
7413
|
currentVideoRange = level == null ? void 0 : level.videoRange;
|
@@ -7376,11 +7434,11 @@ class AbrController {
|
|
7376
7434
|
const levels = this.hls.levels;
|
7377
7435
|
const index = levels.indexOf(levelInfo);
|
7378
7436
|
if (decodingInfo.error) {
|
7379
|
-
|
7437
|
+
this.warn(`MediaCapabilities decodingInfo error: "${decodingInfo.error}" for level ${index} ${JSON.stringify(decodingInfo)}`);
|
7380
7438
|
} else if (!decodingInfo.supported) {
|
7381
|
-
|
7439
|
+
this.warn(`Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify(decodingInfo)}`);
|
7382
7440
|
if (index > -1 && levels.length > 1) {
|
7383
|
-
|
7441
|
+
this.log(`Removing unsupported level ${index}`);
|
7384
7442
|
this.hls.removeLevel(index);
|
7385
7443
|
}
|
7386
7444
|
}
|
@@ -7427,9 +7485,9 @@ class AbrController {
|
|
7427
7485
|
const forcedAutoLevel = this.forcedAutoLevel;
|
7428
7486
|
if (i !== loadLevel && (forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)) {
|
7429
7487
|
if (levelsSkipped.length) {
|
7430
|
-
|
7488
|
+
this.trace(`Skipped level(s) ${levelsSkipped.join(',')} of ${maxAutoLevel} max with CODECS and VIDEO-RANGE:"${levels[levelsSkipped[0]].codecs}" ${levels[levelsSkipped[0]].videoRange}; not compatible with "${level.codecs}" ${currentVideoRange}`);
|
7431
7489
|
}
|
7432
|
-
|
7490
|
+
this.info(`switch candidate:${selectionBaseLevel}->${i} adjustedbw(${Math.round(adjustedbw)})-bitrate=${Math.round(adjustedbw - bitrate)} ttfb:${ttfbEstimateSec.toFixed(1)} avgDuration:${avgDuration.toFixed(1)} maxFetchDuration:${maxFetchDuration.toFixed(1)} fetchDuration:${fetchDuration.toFixed(1)} firstSelection:${firstSelection} codecSet:${currentCodecSet} videoRange:${currentVideoRange} hls.loadLevel:${loadLevel}`);
|
7433
7491
|
}
|
7434
7492
|
if (firstSelection) {
|
7435
7493
|
this.firstSelection = i;
|
@@ -7483,8 +7541,9 @@ class AbrController {
|
|
7483
7541
|
* we are limiting the task execution per call stack to exactly one, but scheduling/post-poning further
|
7484
7542
|
* task processing on the next main loop iteration (also known as "next tick" in the Node/JS runtime lingo).
|
7485
7543
|
*/
|
7486
|
-
class TaskLoop {
|
7487
|
-
constructor() {
|
7544
|
+
class TaskLoop extends Logger {
|
7545
|
+
constructor(label, logger) {
|
7546
|
+
super(label, logger);
|
7488
7547
|
this._boundTick = void 0;
|
7489
7548
|
this._tickTimer = null;
|
7490
7549
|
this._tickInterval = null;
|
@@ -7646,13 +7705,16 @@ class FragmentTracker {
|
|
7646
7705
|
* If not found any Fragment, return null
|
7647
7706
|
*/
|
7648
7707
|
getBufferedFrag(position, levelType) {
|
7708
|
+
return this.getFragAtPos(position, levelType, true);
|
7709
|
+
}
|
7710
|
+
getFragAtPos(position, levelType, buffered) {
|
7649
7711
|
const {
|
7650
7712
|
fragments
|
7651
7713
|
} = this;
|
7652
7714
|
const keys = Object.keys(fragments);
|
7653
7715
|
for (let i = keys.length; i--;) {
|
7654
7716
|
const fragmentEntity = fragments[keys[i]];
|
7655
|
-
if ((fragmentEntity == null ? void 0 : fragmentEntity.body.type) === levelType && fragmentEntity.buffered) {
|
7717
|
+
if ((fragmentEntity == null ? void 0 : fragmentEntity.body.type) === levelType && (!buffered || fragmentEntity.buffered)) {
|
7656
7718
|
const frag = fragmentEntity.body;
|
7657
7719
|
if (frag.start <= position && position <= frag.end) {
|
7658
7720
|
return frag;
|
@@ -7907,7 +7969,8 @@ class FragmentTracker {
|
|
7907
7969
|
const {
|
7908
7970
|
frag,
|
7909
7971
|
part,
|
7910
|
-
timeRanges
|
7972
|
+
timeRanges,
|
7973
|
+
type
|
7911
7974
|
} = data;
|
7912
7975
|
if (frag.sn === 'initSegment') {
|
7913
7976
|
return;
|
@@ -7922,10 +7985,8 @@ class FragmentTracker {
|
|
7922
7985
|
}
|
7923
7986
|
// Store the latest timeRanges loaded in the buffer
|
7924
7987
|
this.timeRanges = timeRanges;
|
7925
|
-
|
7926
|
-
|
7927
|
-
this.detectEvictedFragments(elementaryStream, timeRange, playlistType, part);
|
7928
|
-
});
|
7988
|
+
const timeRange = timeRanges[type];
|
7989
|
+
this.detectEvictedFragments(type, timeRange, playlistType, part);
|
7929
7990
|
}
|
7930
7991
|
onFragBuffered(event, data) {
|
7931
7992
|
this.detectPartialFragments(data);
|
@@ -8003,40 +8064,29 @@ class BufferHelper {
|
|
8003
8064
|
* Return true if `media`'s buffered include `position`
|
8004
8065
|
*/
|
8005
8066
|
static isBuffered(media, position) {
|
8006
|
-
|
8007
|
-
|
8008
|
-
|
8009
|
-
|
8010
|
-
|
8011
|
-
return true;
|
8012
|
-
}
|
8067
|
+
if (media) {
|
8068
|
+
const buffered = BufferHelper.getBuffered(media);
|
8069
|
+
for (let i = buffered.length; i--;) {
|
8070
|
+
if (position >= buffered.start(i) && position <= buffered.end(i)) {
|
8071
|
+
return true;
|
8013
8072
|
}
|
8014
8073
|
}
|
8015
|
-
} catch (error) {
|
8016
|
-
// this is to catch
|
8017
|
-
// InvalidStateError: Failed to read the 'buffered' property from 'SourceBuffer':
|
8018
|
-
// This SourceBuffer has been removed from the parent media source
|
8019
8074
|
}
|
8020
8075
|
return false;
|
8021
8076
|
}
|
8022
8077
|
static bufferInfo(media, pos, maxHoleDuration) {
|
8023
|
-
|
8024
|
-
|
8025
|
-
|
8078
|
+
if (media) {
|
8079
|
+
const vbuffered = BufferHelper.getBuffered(media);
|
8080
|
+
if (vbuffered.length) {
|
8026
8081
|
const buffered = [];
|
8027
|
-
let i;
|
8028
|
-
for (i = 0; i < vbuffered.length; i++) {
|
8082
|
+
for (let i = 0; i < vbuffered.length; i++) {
|
8029
8083
|
buffered.push({
|
8030
8084
|
start: vbuffered.start(i),
|
8031
8085
|
end: vbuffered.end(i)
|
8032
8086
|
});
|
8033
8087
|
}
|
8034
|
-
return
|
8088
|
+
return BufferHelper.bufferedInfo(buffered, pos, maxHoleDuration);
|
8035
8089
|
}
|
8036
|
-
} catch (error) {
|
8037
|
-
// this is to catch
|
8038
|
-
// InvalidStateError: Failed to read the 'buffered' property from 'SourceBuffer':
|
8039
|
-
// This SourceBuffer has been removed from the parent media source
|
8040
8090
|
}
|
8041
8091
|
return {
|
8042
8092
|
len: 0,
|
@@ -8048,14 +8098,7 @@ class BufferHelper {
|
|
8048
8098
|
static bufferedInfo(buffered, pos, maxHoleDuration) {
|
8049
8099
|
pos = Math.max(0, pos);
|
8050
8100
|
// sort on buffer.start/smaller end (IE does not always return sorted buffered range)
|
8051
|
-
buffered.sort(
|
8052
|
-
const diff = a.start - b.start;
|
8053
|
-
if (diff) {
|
8054
|
-
return diff;
|
8055
|
-
} else {
|
8056
|
-
return b.end - a.end;
|
8057
|
-
}
|
8058
|
-
});
|
8101
|
+
buffered.sort((a, b) => a.start - b.start || b.end - a.end);
|
8059
8102
|
let buffered2 = [];
|
8060
8103
|
if (maxHoleDuration) {
|
8061
8104
|
// there might be some small holes between buffer time range
|
@@ -8122,7 +8165,7 @@ class BufferHelper {
|
|
8122
8165
|
*/
|
8123
8166
|
static getBuffered(media) {
|
8124
8167
|
try {
|
8125
|
-
return media.buffered;
|
8168
|
+
return media.buffered || noopBuffered;
|
8126
8169
|
} catch (e) {
|
8127
8170
|
logger.log('failed to get media.buffered', e);
|
8128
8171
|
return noopBuffered;
|
@@ -8575,8 +8618,8 @@ function createLoaderContext(frag, part = null) {
|
|
8575
8618
|
var _frag$decryptdata;
|
8576
8619
|
let byteRangeStart = start;
|
8577
8620
|
let byteRangeEnd = end;
|
8578
|
-
if (frag.sn === 'initSegment' && ((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method)
|
8579
|
-
// MAP segment encrypted with method 'AES-128', when served with HTTP Range,
|
8621
|
+
if (frag.sn === 'initSegment' && isMethodFullSegmentAesCbc((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method)) {
|
8622
|
+
// MAP segment encrypted with method 'AES-128' or 'AES-256' (cbc), when served with HTTP Range,
|
8580
8623
|
// has the unencrypted size specified in the range.
|
8581
8624
|
// Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
|
8582
8625
|
const fragmentLen = end - start;
|
@@ -8609,6 +8652,9 @@ function createGapLoadError(frag, part) {
|
|
8609
8652
|
(part ? part : frag).stats.aborted = true;
|
8610
8653
|
return new LoadError(errorData);
|
8611
8654
|
}
|
8655
|
+
function isMethodFullSegmentAesCbc(method) {
|
8656
|
+
return method === 'AES-128' || method === 'AES-256';
|
8657
|
+
}
|
8612
8658
|
class LoadError extends Error {
|
8613
8659
|
constructor(data) {
|
8614
8660
|
super(data.error.message);
|
@@ -8618,33 +8664,61 @@ class LoadError extends Error {
|
|
8618
8664
|
}
|
8619
8665
|
|
8620
8666
|
class AESCrypto {
|
8621
|
-
constructor(subtle, iv) {
|
8667
|
+
constructor(subtle, iv, aesMode) {
|
8622
8668
|
this.subtle = void 0;
|
8623
8669
|
this.aesIV = void 0;
|
8670
|
+
this.aesMode = void 0;
|
8624
8671
|
this.subtle = subtle;
|
8625
8672
|
this.aesIV = iv;
|
8673
|
+
this.aesMode = aesMode;
|
8626
8674
|
}
|
8627
8675
|
decrypt(data, key) {
|
8628
|
-
|
8629
|
-
|
8630
|
-
|
8631
|
-
|
8676
|
+
switch (this.aesMode) {
|
8677
|
+
case DecrypterAesMode.cbc:
|
8678
|
+
return this.subtle.decrypt({
|
8679
|
+
name: 'AES-CBC',
|
8680
|
+
iv: this.aesIV
|
8681
|
+
}, key, data);
|
8682
|
+
case DecrypterAesMode.ctr:
|
8683
|
+
return this.subtle.decrypt({
|
8684
|
+
name: 'AES-CTR',
|
8685
|
+
counter: this.aesIV,
|
8686
|
+
length: 64
|
8687
|
+
},
|
8688
|
+
//64 : NIST SP800-38A standard suggests that the counter should occupy half of the counter block
|
8689
|
+
key, data);
|
8690
|
+
default:
|
8691
|
+
throw new Error(`[AESCrypto] invalid aes mode ${this.aesMode}`);
|
8692
|
+
}
|
8632
8693
|
}
|
8633
8694
|
}
|
8634
8695
|
|
8635
8696
|
class FastAESKey {
|
8636
|
-
constructor(subtle, key) {
|
8697
|
+
constructor(subtle, key, aesMode) {
|
8637
8698
|
this.subtle = void 0;
|
8638
8699
|
this.key = void 0;
|
8700
|
+
this.aesMode = void 0;
|
8639
8701
|
this.subtle = subtle;
|
8640
8702
|
this.key = key;
|
8703
|
+
this.aesMode = aesMode;
|
8641
8704
|
}
|
8642
8705
|
expandKey() {
|
8706
|
+
const subtleAlgoName = getSubtleAlgoName(this.aesMode);
|
8643
8707
|
return this.subtle.importKey('raw', this.key, {
|
8644
|
-
name:
|
8708
|
+
name: subtleAlgoName
|
8645
8709
|
}, false, ['encrypt', 'decrypt']);
|
8646
8710
|
}
|
8647
8711
|
}
|
8712
|
+
function getSubtleAlgoName(aesMode) {
|
8713
|
+
switch (aesMode) {
|
8714
|
+
case DecrypterAesMode.cbc:
|
8715
|
+
return 'AES-CBC';
|
8716
|
+
case DecrypterAesMode.ctr:
|
8717
|
+
return 'AES-CTR';
|
8718
|
+
default:
|
8719
|
+
throw new Error(`[FastAESKey] invalid aes mode ${aesMode}`);
|
8720
|
+
}
|
8721
|
+
}
|
8648
8722
|
|
8649
8723
|
// PKCS7
|
8650
8724
|
function removePadding(array) {
|
@@ -8894,7 +8968,8 @@ class Decrypter {
|
|
8894
8968
|
this.currentIV = null;
|
8895
8969
|
this.currentResult = null;
|
8896
8970
|
this.useSoftware = void 0;
|
8897
|
-
this.
|
8971
|
+
this.enableSoftwareAES = void 0;
|
8972
|
+
this.enableSoftwareAES = config.enableSoftwareAES;
|
8898
8973
|
this.removePKCS7Padding = removePKCS7Padding;
|
8899
8974
|
// built in decryptor expects PKCS7 padding
|
8900
8975
|
if (removePKCS7Padding) {
|
@@ -8907,9 +8982,7 @@ class Decrypter {
|
|
8907
8982
|
/* no-op */
|
8908
8983
|
}
|
8909
8984
|
}
|
8910
|
-
|
8911
|
-
this.useSoftware = true;
|
8912
|
-
}
|
8985
|
+
this.useSoftware = this.subtle === null;
|
8913
8986
|
}
|
8914
8987
|
destroy() {
|
8915
8988
|
this.subtle = null;
|
@@ -8947,10 +9020,10 @@ class Decrypter {
|
|
8947
9020
|
this.softwareDecrypter = null;
|
8948
9021
|
}
|
8949
9022
|
}
|
8950
|
-
decrypt(data, key, iv) {
|
9023
|
+
decrypt(data, key, iv, aesMode) {
|
8951
9024
|
if (this.useSoftware) {
|
8952
9025
|
return new Promise((resolve, reject) => {
|
8953
|
-
this.softwareDecrypt(new Uint8Array(data), key, iv);
|
9026
|
+
this.softwareDecrypt(new Uint8Array(data), key, iv, aesMode);
|
8954
9027
|
const decryptResult = this.flush();
|
8955
9028
|
if (decryptResult) {
|
8956
9029
|
resolve(decryptResult.buffer);
|
@@ -8959,17 +9032,21 @@ class Decrypter {
|
|
8959
9032
|
}
|
8960
9033
|
});
|
8961
9034
|
}
|
8962
|
-
return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
|
9035
|
+
return this.webCryptoDecrypt(new Uint8Array(data), key, iv, aesMode);
|
8963
9036
|
}
|
8964
9037
|
|
8965
9038
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
8966
9039
|
// data is handled in the flush() call
|
8967
|
-
softwareDecrypt(data, key, iv) {
|
9040
|
+
softwareDecrypt(data, key, iv, aesMode) {
|
8968
9041
|
const {
|
8969
9042
|
currentIV,
|
8970
9043
|
currentResult,
|
8971
9044
|
remainderData
|
8972
9045
|
} = this;
|
9046
|
+
if (aesMode !== DecrypterAesMode.cbc || key.byteLength !== 16) {
|
9047
|
+
logger.warn('SoftwareDecrypt: can only handle AES-128-CBC');
|
9048
|
+
return null;
|
9049
|
+
}
|
8973
9050
|
this.logOnce('JS AES decrypt');
|
8974
9051
|
// The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
|
8975
9052
|
// This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached
|
@@ -9002,11 +9079,11 @@ class Decrypter {
|
|
9002
9079
|
}
|
9003
9080
|
return result;
|
9004
9081
|
}
|
9005
|
-
webCryptoDecrypt(data, key, iv) {
|
9082
|
+
webCryptoDecrypt(data, key, iv, aesMode) {
|
9006
9083
|
const subtle = this.subtle;
|
9007
9084
|
if (this.key !== key || !this.fastAesKey) {
|
9008
9085
|
this.key = key;
|
9009
|
-
this.fastAesKey = new FastAESKey(subtle, key);
|
9086
|
+
this.fastAesKey = new FastAESKey(subtle, key, aesMode);
|
9010
9087
|
}
|
9011
9088
|
return this.fastAesKey.expandKey().then(aesKey => {
|
9012
9089
|
// decrypt using web crypto
|
@@ -9014,22 +9091,25 @@ class Decrypter {
|
|
9014
9091
|
return Promise.reject(new Error('web crypto not initialized'));
|
9015
9092
|
}
|
9016
9093
|
this.logOnce('WebCrypto AES decrypt');
|
9017
|
-
const crypto = new AESCrypto(subtle, new Uint8Array(iv));
|
9094
|
+
const crypto = new AESCrypto(subtle, new Uint8Array(iv), aesMode);
|
9018
9095
|
return crypto.decrypt(data.buffer, aesKey);
|
9019
9096
|
}).catch(err => {
|
9020
9097
|
logger.warn(`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`);
|
9021
|
-
return this.onWebCryptoError(data, key, iv);
|
9098
|
+
return this.onWebCryptoError(data, key, iv, aesMode);
|
9022
9099
|
});
|
9023
9100
|
}
|
9024
|
-
onWebCryptoError(data, key, iv) {
|
9025
|
-
|
9026
|
-
|
9027
|
-
|
9028
|
-
|
9029
|
-
|
9030
|
-
|
9101
|
+
onWebCryptoError(data, key, iv, aesMode) {
|
9102
|
+
const enableSoftwareAES = this.enableSoftwareAES;
|
9103
|
+
if (enableSoftwareAES) {
|
9104
|
+
this.useSoftware = true;
|
9105
|
+
this.logEnabled = true;
|
9106
|
+
this.softwareDecrypt(data, key, iv, aesMode);
|
9107
|
+
const decryptResult = this.flush();
|
9108
|
+
if (decryptResult) {
|
9109
|
+
return decryptResult.buffer;
|
9110
|
+
}
|
9031
9111
|
}
|
9032
|
-
throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data');
|
9112
|
+
throw new Error('WebCrypto' + (enableSoftwareAES ? ' and softwareDecrypt' : '') + ': failed to decrypt data');
|
9033
9113
|
}
|
9034
9114
|
getValidChunk(data) {
|
9035
9115
|
let currentChunk = data;
|
@@ -9080,7 +9160,7 @@ const State = {
|
|
9080
9160
|
};
|
9081
9161
|
class BaseStreamController extends TaskLoop {
|
9082
9162
|
constructor(hls, fragmentTracker, keyLoader, logPrefix, playlistType) {
|
9083
|
-
super();
|
9163
|
+
super(logPrefix, hls.logger);
|
9084
9164
|
this.hls = void 0;
|
9085
9165
|
this.fragPrevious = null;
|
9086
9166
|
this.fragCurrent = null;
|
@@ -9105,22 +9185,98 @@ class BaseStreamController extends TaskLoop {
|
|
9105
9185
|
this.startFragRequested = false;
|
9106
9186
|
this.decrypter = void 0;
|
9107
9187
|
this.initPTS = [];
|
9108
|
-
this.
|
9109
|
-
this.
|
9110
|
-
this.
|
9111
|
-
|
9112
|
-
|
9188
|
+
this.buffering = true;
|
9189
|
+
this.loadingParts = false;
|
9190
|
+
this.onMediaSeeking = () => {
|
9191
|
+
const {
|
9192
|
+
config,
|
9193
|
+
fragCurrent,
|
9194
|
+
media,
|
9195
|
+
mediaBuffer,
|
9196
|
+
state
|
9197
|
+
} = this;
|
9198
|
+
const currentTime = media ? media.currentTime : 0;
|
9199
|
+
const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
|
9200
|
+
this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
|
9201
|
+
if (this.state === State.ENDED) {
|
9202
|
+
this.resetLoadingState();
|
9203
|
+
} else if (fragCurrent) {
|
9204
|
+
// Seeking while frag load is in progress
|
9205
|
+
const tolerance = config.maxFragLookUpTolerance;
|
9206
|
+
const fragStartOffset = fragCurrent.start - tolerance;
|
9207
|
+
const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
|
9208
|
+
// if seeking out of buffered range or into new one
|
9209
|
+
if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
|
9210
|
+
const pastFragment = currentTime > fragEndOffset;
|
9211
|
+
// if the seek position is outside the current fragment range
|
9212
|
+
if (currentTime < fragStartOffset || pastFragment) {
|
9213
|
+
if (pastFragment && fragCurrent.loader) {
|
9214
|
+
this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
|
9215
|
+
fragCurrent.abortRequests();
|
9216
|
+
this.resetLoadingState();
|
9217
|
+
}
|
9218
|
+
this.fragPrevious = null;
|
9219
|
+
}
|
9220
|
+
}
|
9221
|
+
}
|
9222
|
+
if (media) {
|
9223
|
+
// Remove gap fragments
|
9224
|
+
this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
|
9225
|
+
this.lastCurrentTime = currentTime;
|
9226
|
+
if (!this.loadingParts) {
|
9227
|
+
const bufferEnd = Math.max(bufferInfo.end, currentTime);
|
9228
|
+
const shouldLoadParts = this.shouldLoadParts(this.getLevelDetails(), bufferEnd);
|
9229
|
+
if (shouldLoadParts) {
|
9230
|
+
this.log(`LL-Part loading ON after seeking to ${currentTime.toFixed(2)} with buffer @${bufferEnd.toFixed(2)}`);
|
9231
|
+
this.loadingParts = shouldLoadParts;
|
9232
|
+
}
|
9233
|
+
}
|
9234
|
+
}
|
9235
|
+
|
9236
|
+
// in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
|
9237
|
+
if (!this.loadedmetadata && !bufferInfo.len) {
|
9238
|
+
this.nextLoadPosition = this.startPosition = currentTime;
|
9239
|
+
}
|
9240
|
+
|
9241
|
+
// Async tick to speed up processing
|
9242
|
+
this.tickImmediate();
|
9243
|
+
};
|
9244
|
+
this.onMediaEnded = () => {
|
9245
|
+
// reset startPosition and lastCurrentTime to restart playback @ stream beginning
|
9246
|
+
this.startPosition = this.lastCurrentTime = 0;
|
9247
|
+
if (this.playlistType === PlaylistLevelType.MAIN) {
|
9248
|
+
this.hls.trigger(Events.MEDIA_ENDED, {
|
9249
|
+
stalled: false
|
9250
|
+
});
|
9251
|
+
}
|
9252
|
+
};
|
9113
9253
|
this.playlistType = playlistType;
|
9114
|
-
this.logPrefix = logPrefix;
|
9115
|
-
this.log = logger.log.bind(logger, `${logPrefix}:`);
|
9116
|
-
this.warn = logger.warn.bind(logger, `${logPrefix}:`);
|
9117
9254
|
this.hls = hls;
|
9118
9255
|
this.fragmentLoader = new FragmentLoader(hls.config);
|
9119
9256
|
this.keyLoader = keyLoader;
|
9120
9257
|
this.fragmentTracker = fragmentTracker;
|
9121
9258
|
this.config = hls.config;
|
9122
9259
|
this.decrypter = new Decrypter(hls.config);
|
9260
|
+
}
|
9261
|
+
registerListeners() {
|
9262
|
+
const {
|
9263
|
+
hls
|
9264
|
+
} = this;
|
9265
|
+
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
9266
|
+
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
9267
|
+
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
9123
9268
|
hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
9269
|
+
hls.on(Events.ERROR, this.onError, this);
|
9270
|
+
}
|
9271
|
+
unregisterListeners() {
|
9272
|
+
const {
|
9273
|
+
hls
|
9274
|
+
} = this;
|
9275
|
+
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
9276
|
+
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
9277
|
+
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
9278
|
+
hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
9279
|
+
hls.off(Events.ERROR, this.onError, this);
|
9124
9280
|
}
|
9125
9281
|
doTick() {
|
9126
9282
|
this.onTickEnd();
|
@@ -9144,6 +9300,12 @@ class BaseStreamController extends TaskLoop {
|
|
9144
9300
|
this.clearNextTick();
|
9145
9301
|
this.state = State.STOPPED;
|
9146
9302
|
}
|
9303
|
+
pauseBuffering() {
|
9304
|
+
this.buffering = false;
|
9305
|
+
}
|
9306
|
+
resumeBuffering() {
|
9307
|
+
this.buffering = true;
|
9308
|
+
}
|
9147
9309
|
_streamEnded(bufferInfo, levelDetails) {
|
9148
9310
|
// If playlist is live, there is another buffered range after the current range, nothing buffered, media is detached,
|
9149
9311
|
// of nothing loading/loaded return false
|
@@ -9174,10 +9336,8 @@ class BaseStreamController extends TaskLoop {
|
|
9174
9336
|
}
|
9175
9337
|
onMediaAttached(event, data) {
|
9176
9338
|
const media = this.media = this.mediaBuffer = data.media;
|
9177
|
-
|
9178
|
-
|
9179
|
-
media.addEventListener('seeking', this.onvseeking);
|
9180
|
-
media.addEventListener('ended', this.onvended);
|
9339
|
+
media.addEventListener('seeking', this.onMediaSeeking);
|
9340
|
+
media.addEventListener('ended', this.onMediaEnded);
|
9181
9341
|
const config = this.config;
|
9182
9342
|
if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
|
9183
9343
|
this.startLoad(config.startPosition);
|
@@ -9191,10 +9351,9 @@ class BaseStreamController extends TaskLoop {
|
|
9191
9351
|
}
|
9192
9352
|
|
9193
9353
|
// remove video listeners
|
9194
|
-
if (media
|
9195
|
-
media.removeEventListener('seeking', this.
|
9196
|
-
media.removeEventListener('ended', this.
|
9197
|
-
this.onvseeking = this.onvended = null;
|
9354
|
+
if (media) {
|
9355
|
+
media.removeEventListener('seeking', this.onMediaSeeking);
|
9356
|
+
media.removeEventListener('ended', this.onMediaEnded);
|
9198
9357
|
}
|
9199
9358
|
if (this.keyLoader) {
|
9200
9359
|
this.keyLoader.detach();
|
@@ -9204,56 +9363,8 @@ class BaseStreamController extends TaskLoop {
|
|
9204
9363
|
this.fragmentTracker.removeAllFragments();
|
9205
9364
|
this.stopLoad();
|
9206
9365
|
}
|
9207
|
-
|
9208
|
-
|
9209
|
-
config,
|
9210
|
-
fragCurrent,
|
9211
|
-
media,
|
9212
|
-
mediaBuffer,
|
9213
|
-
state
|
9214
|
-
} = this;
|
9215
|
-
const currentTime = media ? media.currentTime : 0;
|
9216
|
-
const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
|
9217
|
-
this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
|
9218
|
-
if (this.state === State.ENDED) {
|
9219
|
-
this.resetLoadingState();
|
9220
|
-
} else if (fragCurrent) {
|
9221
|
-
// Seeking while frag load is in progress
|
9222
|
-
const tolerance = config.maxFragLookUpTolerance;
|
9223
|
-
const fragStartOffset = fragCurrent.start - tolerance;
|
9224
|
-
const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
|
9225
|
-
// if seeking out of buffered range or into new one
|
9226
|
-
if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
|
9227
|
-
const pastFragment = currentTime > fragEndOffset;
|
9228
|
-
// if the seek position is outside the current fragment range
|
9229
|
-
if (currentTime < fragStartOffset || pastFragment) {
|
9230
|
-
if (pastFragment && fragCurrent.loader) {
|
9231
|
-
this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
|
9232
|
-
fragCurrent.abortRequests();
|
9233
|
-
this.resetLoadingState();
|
9234
|
-
}
|
9235
|
-
this.fragPrevious = null;
|
9236
|
-
}
|
9237
|
-
}
|
9238
|
-
}
|
9239
|
-
if (media) {
|
9240
|
-
// Remove gap fragments
|
9241
|
-
this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
|
9242
|
-
this.lastCurrentTime = currentTime;
|
9243
|
-
}
|
9244
|
-
|
9245
|
-
// in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
|
9246
|
-
if (!this.loadedmetadata && !bufferInfo.len) {
|
9247
|
-
this.nextLoadPosition = this.startPosition = currentTime;
|
9248
|
-
}
|
9249
|
-
|
9250
|
-
// Async tick to speed up processing
|
9251
|
-
this.tickImmediate();
|
9252
|
-
}
|
9253
|
-
onMediaEnded() {
|
9254
|
-
// reset startPosition and lastCurrentTime to restart playback @ stream beginning
|
9255
|
-
this.startPosition = this.lastCurrentTime = 0;
|
9256
|
-
}
|
9366
|
+
onManifestLoading() {}
|
9367
|
+
onError(event, data) {}
|
9257
9368
|
onManifestLoaded(event, data) {
|
9258
9369
|
this.startTimeOffset = data.startTimeOffset;
|
9259
9370
|
this.initPTS = [];
|
@@ -9263,7 +9374,7 @@ class BaseStreamController extends TaskLoop {
|
|
9263
9374
|
this.stopLoad();
|
9264
9375
|
super.onHandlerDestroying();
|
9265
9376
|
// @ts-ignore
|
9266
|
-
this.hls = null;
|
9377
|
+
this.hls = this.onMediaSeeking = this.onMediaEnded = null;
|
9267
9378
|
}
|
9268
9379
|
onHandlerDestroyed() {
|
9269
9380
|
this.state = State.STOPPED;
|
@@ -9394,10 +9505,10 @@ class BaseStreamController extends TaskLoop {
|
|
9394
9505
|
const decryptData = frag.decryptdata;
|
9395
9506
|
|
9396
9507
|
// check to see if the payload needs to be decrypted
|
9397
|
-
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method
|
9508
|
+
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
|
9398
9509
|
const startTime = self.performance.now();
|
9399
9510
|
// decrypt init segment data
|
9400
|
-
return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
|
9511
|
+
return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch(err => {
|
9401
9512
|
hls.trigger(Events.ERROR, {
|
9402
9513
|
type: ErrorTypes.MEDIA_ERROR,
|
9403
9514
|
details: ErrorDetails.FRAG_DECRYPT_ERROR,
|
@@ -9509,7 +9620,7 @@ class BaseStreamController extends TaskLoop {
|
|
9509
9620
|
}
|
9510
9621
|
let keyLoadingPromise = null;
|
9511
9622
|
if (frag.encrypted && !((_frag$decryptdata = frag.decryptdata) != null && _frag$decryptdata.key)) {
|
9512
|
-
this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.
|
9623
|
+
this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'} ${frag.level}`);
|
9513
9624
|
this.state = State.KEY_LOADING;
|
9514
9625
|
this.fragCurrent = frag;
|
9515
9626
|
keyLoadingPromise = this.keyLoader.load(frag).then(keyLoadedData => {
|
@@ -9530,8 +9641,16 @@ class BaseStreamController extends TaskLoop {
|
|
9530
9641
|
} else if (!frag.encrypted && details.encryptedFragments.length) {
|
9531
9642
|
this.keyLoader.loadClear(frag, details.encryptedFragments);
|
9532
9643
|
}
|
9644
|
+
const fragPrevious = this.fragPrevious;
|
9645
|
+
if (frag.sn !== 'initSegment' && (!fragPrevious || frag.sn !== fragPrevious.sn)) {
|
9646
|
+
const shouldLoadParts = this.shouldLoadParts(level.details, frag.end);
|
9647
|
+
if (shouldLoadParts !== this.loadingParts) {
|
9648
|
+
this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} loading sn ${fragPrevious == null ? void 0 : fragPrevious.sn}->${frag.sn}`);
|
9649
|
+
this.loadingParts = shouldLoadParts;
|
9650
|
+
}
|
9651
|
+
}
|
9533
9652
|
targetBufferTime = Math.max(frag.start, targetBufferTime || 0);
|
9534
|
-
if (this.
|
9653
|
+
if (this.loadingParts && frag.sn !== 'initSegment') {
|
9535
9654
|
const partList = details.partList;
|
9536
9655
|
if (partList && progressCallback) {
|
9537
9656
|
if (targetBufferTime > frag.end && details.fragmentHint) {
|
@@ -9540,7 +9659,7 @@ class BaseStreamController extends TaskLoop {
|
|
9540
9659
|
const partIndex = this.getNextPart(partList, frag, targetBufferTime);
|
9541
9660
|
if (partIndex > -1) {
|
9542
9661
|
const part = partList[partIndex];
|
9543
|
-
this.log(`Loading part sn: ${frag.sn} p: ${part.index} cc: ${frag.cc} of playlist [${details.startSN}-${details.endSN}] parts [0-${partIndex}-${partList.length - 1}] ${this.
|
9662
|
+
this.log(`Loading part sn: ${frag.sn} p: ${part.index} cc: ${frag.cc} of playlist [${details.startSN}-${details.endSN}] parts [0-${partIndex}-${partList.length - 1}] ${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
|
9544
9663
|
this.nextLoadPosition = part.start + part.duration;
|
9545
9664
|
this.state = State.FRAG_LOADING;
|
9546
9665
|
let _result;
|
@@ -9569,7 +9688,14 @@ class BaseStreamController extends TaskLoop {
|
|
9569
9688
|
}
|
9570
9689
|
}
|
9571
9690
|
}
|
9572
|
-
|
9691
|
+
if (frag.sn !== 'initSegment' && this.loadingParts) {
|
9692
|
+
this.log(`LL-Part loading OFF after next part miss @${targetBufferTime.toFixed(2)}`);
|
9693
|
+
this.loadingParts = false;
|
9694
|
+
} else if (!frag.url) {
|
9695
|
+
// Selected fragment hint for part but not loading parts
|
9696
|
+
return Promise.resolve(null);
|
9697
|
+
}
|
9698
|
+
this.log(`Loading fragment ${frag.sn} cc: ${frag.cc} ${details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''}${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
|
9573
9699
|
// Don't update nextLoadPosition for fragments which are not buffered
|
9574
9700
|
if (isFiniteNumber(frag.sn) && !this.bitrateTest) {
|
9575
9701
|
this.nextLoadPosition = frag.start + frag.duration;
|
@@ -9667,12 +9793,40 @@ class BaseStreamController extends TaskLoop {
|
|
9667
9793
|
if (part) {
|
9668
9794
|
part.stats.parsing.end = now;
|
9669
9795
|
}
|
9796
|
+
// See if part loading should be disabled/enabled based on buffer and playback position.
|
9797
|
+
if (frag.sn !== 'initSegment') {
|
9798
|
+
const levelDetails = this.getLevelDetails();
|
9799
|
+
const loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
|
9800
|
+
const shouldLoadParts = loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
|
9801
|
+
if (shouldLoadParts !== this.loadingParts) {
|
9802
|
+
this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} after parsing segment ending @${frag.end.toFixed(2)}`);
|
9803
|
+
this.loadingParts = shouldLoadParts;
|
9804
|
+
}
|
9805
|
+
}
|
9670
9806
|
this.updateLevelTiming(frag, part, level, chunkMeta.partial);
|
9671
9807
|
}
|
9672
|
-
|
9673
|
-
|
9674
|
-
|
9675
|
-
|
9808
|
+
shouldLoadParts(details, bufferEnd) {
|
9809
|
+
if (this.config.lowLatencyMode) {
|
9810
|
+
if (!details) {
|
9811
|
+
return this.loadingParts;
|
9812
|
+
}
|
9813
|
+
if (details != null && details.partList) {
|
9814
|
+
var _details$fragmentHint;
|
9815
|
+
// Buffer must be ahead of first part + duration of parts after last segment
|
9816
|
+
// and playback must be at or past segment adjacent to part list
|
9817
|
+
const firstPart = details.partList[0];
|
9818
|
+
const safePartStart = firstPart.end + (((_details$fragmentHint = details.fragmentHint) == null ? void 0 : _details$fragmentHint.duration) || 0);
|
9819
|
+
if (bufferEnd >= safePartStart && this.lastCurrentTime > firstPart.start - firstPart.fragment.duration) {
|
9820
|
+
return true;
|
9821
|
+
}
|
9822
|
+
}
|
9823
|
+
}
|
9824
|
+
return false;
|
9825
|
+
}
|
9826
|
+
getCurrentContext(chunkMeta) {
|
9827
|
+
const {
|
9828
|
+
levels,
|
9829
|
+
fragCurrent
|
9676
9830
|
} = this;
|
9677
9831
|
const {
|
9678
9832
|
level: levelIndex,
|
@@ -9769,7 +9923,7 @@ class BaseStreamController extends TaskLoop {
|
|
9769
9923
|
// Workaround flaw in getting forward buffer when maxBufferHole is smaller than gap at current pos
|
9770
9924
|
if (bufferInfo.len === 0 && bufferInfo.nextStart !== undefined) {
|
9771
9925
|
const bufferedFragAtPos = this.fragmentTracker.getBufferedFrag(pos, type);
|
9772
|
-
if (bufferedFragAtPos && bufferInfo.nextStart
|
9926
|
+
if (bufferedFragAtPos && (bufferInfo.nextStart <= bufferedFragAtPos.end || bufferedFragAtPos.gap)) {
|
9773
9927
|
return BufferHelper.bufferInfo(bufferable, pos, Math.max(bufferInfo.nextStart, maxBufferHole));
|
9774
9928
|
}
|
9775
9929
|
}
|
@@ -9817,7 +9971,8 @@ class BaseStreamController extends TaskLoop {
|
|
9817
9971
|
config
|
9818
9972
|
} = this;
|
9819
9973
|
const start = fragments[0].start;
|
9820
|
-
|
9974
|
+
const canLoadParts = config.lowLatencyMode && !!levelDetails.partList;
|
9975
|
+
let frag = null;
|
9821
9976
|
if (levelDetails.live) {
|
9822
9977
|
const initialLiveManifestSize = config.initialLiveManifestSize;
|
9823
9978
|
if (fragLen < initialLiveManifestSize) {
|
@@ -9829,6 +9984,10 @@ class BaseStreamController extends TaskLoop {
|
|
9829
9984
|
// Do not load using live logic if the starting frag is requested - we want to use getFragmentAtPosition() so that
|
9830
9985
|
// we get the fragment matching that start time
|
9831
9986
|
if (!levelDetails.PTSKnown && !this.startFragRequested && this.startPosition === -1 || pos < start) {
|
9987
|
+
if (canLoadParts && !this.loadingParts) {
|
9988
|
+
this.log(`LL-Part loading ON for initial live fragment`);
|
9989
|
+
this.loadingParts = true;
|
9990
|
+
}
|
9832
9991
|
frag = this.getInitialLiveFragment(levelDetails, fragments);
|
9833
9992
|
this.startPosition = this.nextLoadPosition = frag ? this.hls.liveSyncPosition || frag.start : pos;
|
9834
9993
|
}
|
@@ -9839,7 +9998,7 @@ class BaseStreamController extends TaskLoop {
|
|
9839
9998
|
|
9840
9999
|
// If we haven't run into any special cases already, just load the fragment most closely matching the requested position
|
9841
10000
|
if (!frag) {
|
9842
|
-
const end =
|
10001
|
+
const end = this.loadingParts ? levelDetails.partEnd : levelDetails.fragmentEnd;
|
9843
10002
|
frag = this.getFragmentAtPosition(pos, end, levelDetails);
|
9844
10003
|
}
|
9845
10004
|
return this.mapToInitFragWhenRequired(frag);
|
@@ -9961,7 +10120,7 @@ class BaseStreamController extends TaskLoop {
|
|
9961
10120
|
} = levelDetails;
|
9962
10121
|
const tolerance = config.maxFragLookUpTolerance;
|
9963
10122
|
const partList = levelDetails.partList;
|
9964
|
-
const loadingParts = !!(
|
10123
|
+
const loadingParts = !!(this.loadingParts && partList != null && partList.length && fragmentHint);
|
9965
10124
|
if (loadingParts && fragmentHint && !this.bitrateTest) {
|
9966
10125
|
// Include incomplete fragment with parts at end
|
9967
10126
|
fragments = fragments.concat(fragmentHint);
|
@@ -10154,7 +10313,7 @@ class BaseStreamController extends TaskLoop {
|
|
10154
10313
|
errorAction.resolved = true;
|
10155
10314
|
}
|
10156
10315
|
} else {
|
10157
|
-
|
10316
|
+
this.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
|
10158
10317
|
return;
|
10159
10318
|
}
|
10160
10319
|
} else if ((errorAction == null ? void 0 : errorAction.action) === NetworkErrorAction.SendAlternateToPenaltyBox) {
|
@@ -10222,7 +10381,9 @@ class BaseStreamController extends TaskLoop {
|
|
10222
10381
|
this.log('Reset loading state');
|
10223
10382
|
this.fragCurrent = null;
|
10224
10383
|
this.fragPrevious = null;
|
10225
|
-
this.state
|
10384
|
+
if (this.state !== State.STOPPED) {
|
10385
|
+
this.state = State.IDLE;
|
10386
|
+
}
|
10226
10387
|
}
|
10227
10388
|
resetStartWhenNotLoaded(level) {
|
10228
10389
|
// if loadedmetadata is not set, it means that first frag request failed
|
@@ -10563,6 +10724,7 @@ const initPTSFn = (timestamp, timeOffset, initPTS) => {
|
|
10563
10724
|
*/
|
10564
10725
|
function getAudioConfig(observer, data, offset, audioCodec) {
|
10565
10726
|
let adtsObjectType;
|
10727
|
+
let originalAdtsObjectType;
|
10566
10728
|
let adtsExtensionSamplingIndex;
|
10567
10729
|
let adtsChannelConfig;
|
10568
10730
|
let config;
|
@@ -10570,7 +10732,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
10570
10732
|
const manifestCodec = audioCodec;
|
10571
10733
|
const adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
|
10572
10734
|
// byte 2
|
10573
|
-
adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
|
10735
|
+
adtsObjectType = originalAdtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
|
10574
10736
|
const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
|
10575
10737
|
if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
|
10576
10738
|
const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
|
@@ -10587,8 +10749,8 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
10587
10749
|
// byte 3
|
10588
10750
|
adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6;
|
10589
10751
|
logger.log(`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`);
|
10590
|
-
//
|
10591
|
-
if (/firefox/i.test(userAgent)) {
|
10752
|
+
// Firefox and Pale Moon: freq less than 24kHz = AAC SBR (HE-AAC)
|
10753
|
+
if (/firefox|palemoon/i.test(userAgent)) {
|
10592
10754
|
if (adtsSamplingIndex >= 6) {
|
10593
10755
|
adtsObjectType = 5;
|
10594
10756
|
config = new Array(4);
|
@@ -10682,6 +10844,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
10682
10844
|
samplerate: adtsSamplingRates[adtsSamplingIndex],
|
10683
10845
|
channelCount: adtsChannelConfig,
|
10684
10846
|
codec: 'mp4a.40.' + adtsObjectType,
|
10847
|
+
parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
|
10685
10848
|
manifestCodec
|
10686
10849
|
};
|
10687
10850
|
}
|
@@ -10736,7 +10899,8 @@ function initTrackConfig(track, observer, data, offset, audioCodec) {
|
|
10736
10899
|
track.channelCount = config.channelCount;
|
10737
10900
|
track.codec = config.codec;
|
10738
10901
|
track.manifestCodec = config.manifestCodec;
|
10739
|
-
|
10902
|
+
track.parsedCodec = config.parsedCodec;
|
10903
|
+
logger.log(`parsed codec:${track.parsedCodec}, codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
|
10740
10904
|
}
|
10741
10905
|
}
|
10742
10906
|
function getFrameDuration(samplerate) {
|
@@ -11327,6 +11491,110 @@ class BaseVideoParser {
|
|
11327
11491
|
logger.log(VideoSample.pts + '/' + VideoSample.dts + ':' + VideoSample.debug);
|
11328
11492
|
}
|
11329
11493
|
}
|
11494
|
+
parseNALu(track, array) {
|
11495
|
+
const len = array.byteLength;
|
11496
|
+
let state = track.naluState || 0;
|
11497
|
+
const lastState = state;
|
11498
|
+
const units = [];
|
11499
|
+
let i = 0;
|
11500
|
+
let value;
|
11501
|
+
let overflow;
|
11502
|
+
let unitType;
|
11503
|
+
let lastUnitStart = -1;
|
11504
|
+
let lastUnitType = 0;
|
11505
|
+
// logger.log('PES:' + Hex.hexDump(array));
|
11506
|
+
|
11507
|
+
if (state === -1) {
|
11508
|
+
// special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
|
11509
|
+
lastUnitStart = 0;
|
11510
|
+
// NALu type is value read from offset 0
|
11511
|
+
lastUnitType = this.getNALuType(array, 0);
|
11512
|
+
state = 0;
|
11513
|
+
i = 1;
|
11514
|
+
}
|
11515
|
+
while (i < len) {
|
11516
|
+
value = array[i++];
|
11517
|
+
// optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
|
11518
|
+
if (!state) {
|
11519
|
+
state = value ? 0 : 1;
|
11520
|
+
continue;
|
11521
|
+
}
|
11522
|
+
if (state === 1) {
|
11523
|
+
state = value ? 0 : 2;
|
11524
|
+
continue;
|
11525
|
+
}
|
11526
|
+
// here we have state either equal to 2 or 3
|
11527
|
+
if (!value) {
|
11528
|
+
state = 3;
|
11529
|
+
} else if (value === 1) {
|
11530
|
+
overflow = i - state - 1;
|
11531
|
+
if (lastUnitStart >= 0) {
|
11532
|
+
const unit = {
|
11533
|
+
data: array.subarray(lastUnitStart, overflow),
|
11534
|
+
type: lastUnitType
|
11535
|
+
};
|
11536
|
+
// logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
|
11537
|
+
units.push(unit);
|
11538
|
+
} else {
|
11539
|
+
// lastUnitStart is undefined => this is the first start code found in this PES packet
|
11540
|
+
// first check if start code delimiter is overlapping between 2 PES packets,
|
11541
|
+
// ie it started in last packet (lastState not zero)
|
11542
|
+
// and ended at the beginning of this PES packet (i <= 4 - lastState)
|
11543
|
+
const lastUnit = this.getLastNalUnit(track.samples);
|
11544
|
+
if (lastUnit) {
|
11545
|
+
if (lastState && i <= 4 - lastState) {
|
11546
|
+
// start delimiter overlapping between PES packets
|
11547
|
+
// strip start delimiter bytes from the end of last NAL unit
|
11548
|
+
// check if lastUnit had a state different from zero
|
11549
|
+
if (lastUnit.state) {
|
11550
|
+
// strip last bytes
|
11551
|
+
lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
|
11552
|
+
}
|
11553
|
+
}
|
11554
|
+
// If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
|
11555
|
+
|
11556
|
+
if (overflow > 0) {
|
11557
|
+
// logger.log('first NALU found with overflow:' + overflow);
|
11558
|
+
lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
|
11559
|
+
lastUnit.state = 0;
|
11560
|
+
}
|
11561
|
+
}
|
11562
|
+
}
|
11563
|
+
// check if we can read unit type
|
11564
|
+
if (i < len) {
|
11565
|
+
unitType = this.getNALuType(array, i);
|
11566
|
+
// logger.log('find NALU @ offset:' + i + ',type:' + unitType);
|
11567
|
+
lastUnitStart = i;
|
11568
|
+
lastUnitType = unitType;
|
11569
|
+
state = 0;
|
11570
|
+
} else {
|
11571
|
+
// not enough byte to read unit type. let's read it on next PES parsing
|
11572
|
+
state = -1;
|
11573
|
+
}
|
11574
|
+
} else {
|
11575
|
+
state = 0;
|
11576
|
+
}
|
11577
|
+
}
|
11578
|
+
if (lastUnitStart >= 0 && state >= 0) {
|
11579
|
+
const unit = {
|
11580
|
+
data: array.subarray(lastUnitStart, len),
|
11581
|
+
type: lastUnitType,
|
11582
|
+
state: state
|
11583
|
+
};
|
11584
|
+
units.push(unit);
|
11585
|
+
// logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
|
11586
|
+
}
|
11587
|
+
// no NALu found
|
11588
|
+
if (units.length === 0) {
|
11589
|
+
// append pes.data to previous NAL unit
|
11590
|
+
const lastUnit = this.getLastNalUnit(track.samples);
|
11591
|
+
if (lastUnit) {
|
11592
|
+
lastUnit.data = appendUint8Array(lastUnit.data, array);
|
11593
|
+
}
|
11594
|
+
}
|
11595
|
+
track.naluState = state;
|
11596
|
+
return units;
|
11597
|
+
}
|
11330
11598
|
}
|
11331
11599
|
|
11332
11600
|
/**
|
@@ -11469,21 +11737,171 @@ class ExpGolomb {
|
|
11469
11737
|
readUInt() {
|
11470
11738
|
return this.readBits(32);
|
11471
11739
|
}
|
11740
|
+
}
|
11741
|
+
|
11742
|
+
class AvcVideoParser extends BaseVideoParser {
|
11743
|
+
parsePES(track, textTrack, pes, last, duration) {
|
11744
|
+
const units = this.parseNALu(track, pes.data);
|
11745
|
+
let VideoSample = this.VideoSample;
|
11746
|
+
let push;
|
11747
|
+
let spsfound = false;
|
11748
|
+
// free pes.data to save up some memory
|
11749
|
+
pes.data = null;
|
11750
|
+
|
11751
|
+
// if new NAL units found and last sample still there, let's push ...
|
11752
|
+
// this helps parsing streams with missing AUD (only do this if AUD never found)
|
11753
|
+
if (VideoSample && units.length && !track.audFound) {
|
11754
|
+
this.pushAccessUnit(VideoSample, track);
|
11755
|
+
VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
|
11756
|
+
}
|
11757
|
+
units.forEach(unit => {
|
11758
|
+
var _VideoSample2;
|
11759
|
+
switch (unit.type) {
|
11760
|
+
// NDR
|
11761
|
+
case 1:
|
11762
|
+
{
|
11763
|
+
let iskey = false;
|
11764
|
+
push = true;
|
11765
|
+
const data = unit.data;
|
11766
|
+
// only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
|
11767
|
+
if (spsfound && data.length > 4) {
|
11768
|
+
// retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
|
11769
|
+
const sliceType = this.readSliceType(data);
|
11770
|
+
// 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
|
11771
|
+
// SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
|
11772
|
+
// An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
|
11773
|
+
// I slice: A slice that is not an SI slice that is decoded using intra prediction only.
|
11774
|
+
// if (sliceType === 2 || sliceType === 7) {
|
11775
|
+
if (sliceType === 2 || sliceType === 4 || sliceType === 7 || sliceType === 9) {
|
11776
|
+
iskey = true;
|
11777
|
+
}
|
11778
|
+
}
|
11779
|
+
if (iskey) {
|
11780
|
+
var _VideoSample;
|
11781
|
+
// if we have non-keyframe data already, that cannot belong to the same frame as a keyframe, so force a push
|
11782
|
+
if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
|
11783
|
+
this.pushAccessUnit(VideoSample, track);
|
11784
|
+
VideoSample = this.VideoSample = null;
|
11785
|
+
}
|
11786
|
+
}
|
11787
|
+
if (!VideoSample) {
|
11788
|
+
VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
|
11789
|
+
}
|
11790
|
+
VideoSample.frame = true;
|
11791
|
+
VideoSample.key = iskey;
|
11792
|
+
break;
|
11793
|
+
// IDR
|
11794
|
+
}
|
11795
|
+
case 5:
|
11796
|
+
push = true;
|
11797
|
+
// handle PES not starting with AUD
|
11798
|
+
// if we have frame data already, that cannot belong to the same frame, so force a push
|
11799
|
+
if ((_VideoSample2 = VideoSample) != null && _VideoSample2.frame && !VideoSample.key) {
|
11800
|
+
this.pushAccessUnit(VideoSample, track);
|
11801
|
+
VideoSample = this.VideoSample = null;
|
11802
|
+
}
|
11803
|
+
if (!VideoSample) {
|
11804
|
+
VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
|
11805
|
+
}
|
11806
|
+
VideoSample.key = true;
|
11807
|
+
VideoSample.frame = true;
|
11808
|
+
break;
|
11809
|
+
// SEI
|
11810
|
+
case 6:
|
11811
|
+
{
|
11812
|
+
push = true;
|
11813
|
+
parseSEIMessageFromNALu(unit.data, 1, pes.pts, textTrack.samples);
|
11814
|
+
break;
|
11815
|
+
// SPS
|
11816
|
+
}
|
11817
|
+
case 7:
|
11818
|
+
{
|
11819
|
+
var _track$pixelRatio, _track$pixelRatio2;
|
11820
|
+
push = true;
|
11821
|
+
spsfound = true;
|
11822
|
+
const sps = unit.data;
|
11823
|
+
const config = this.readSPS(sps);
|
11824
|
+
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]) {
|
11825
|
+
track.width = config.width;
|
11826
|
+
track.height = config.height;
|
11827
|
+
track.pixelRatio = config.pixelRatio;
|
11828
|
+
track.sps = [sps];
|
11829
|
+
track.duration = duration;
|
11830
|
+
const codecarray = sps.subarray(1, 4);
|
11831
|
+
let codecstring = 'avc1.';
|
11832
|
+
for (let i = 0; i < 3; i++) {
|
11833
|
+
let h = codecarray[i].toString(16);
|
11834
|
+
if (h.length < 2) {
|
11835
|
+
h = '0' + h;
|
11836
|
+
}
|
11837
|
+
codecstring += h;
|
11838
|
+
}
|
11839
|
+
track.codec = codecstring;
|
11840
|
+
}
|
11841
|
+
break;
|
11842
|
+
}
|
11843
|
+
// PPS
|
11844
|
+
case 8:
|
11845
|
+
push = true;
|
11846
|
+
track.pps = [unit.data];
|
11847
|
+
break;
|
11848
|
+
// AUD
|
11849
|
+
case 9:
|
11850
|
+
push = true;
|
11851
|
+
track.audFound = true;
|
11852
|
+
if (VideoSample) {
|
11853
|
+
this.pushAccessUnit(VideoSample, track);
|
11854
|
+
}
|
11855
|
+
VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
|
11856
|
+
break;
|
11857
|
+
// Filler Data
|
11858
|
+
case 12:
|
11859
|
+
push = true;
|
11860
|
+
break;
|
11861
|
+
default:
|
11862
|
+
push = false;
|
11863
|
+
if (VideoSample) {
|
11864
|
+
VideoSample.debug += 'unknown NAL ' + unit.type + ' ';
|
11865
|
+
}
|
11866
|
+
break;
|
11867
|
+
}
|
11868
|
+
if (VideoSample && push) {
|
11869
|
+
const units = VideoSample.units;
|
11870
|
+
units.push(unit);
|
11871
|
+
}
|
11872
|
+
});
|
11873
|
+
// if last PES packet, push samples
|
11874
|
+
if (last && VideoSample) {
|
11875
|
+
this.pushAccessUnit(VideoSample, track);
|
11876
|
+
this.VideoSample = null;
|
11877
|
+
}
|
11878
|
+
}
|
11879
|
+
getNALuType(data, offset) {
|
11880
|
+
return data[offset] & 0x1f;
|
11881
|
+
}
|
11882
|
+
readSliceType(data) {
|
11883
|
+
const eg = new ExpGolomb(data);
|
11884
|
+
// skip NALu type
|
11885
|
+
eg.readUByte();
|
11886
|
+
// discard first_mb_in_slice
|
11887
|
+
eg.readUEG();
|
11888
|
+
// return slice_type
|
11889
|
+
return eg.readUEG();
|
11890
|
+
}
|
11472
11891
|
|
11473
11892
|
/**
|
11474
|
-
*
|
11475
|
-
* list is optionally transmitted as part of a sequence parameter
|
11893
|
+
* The scaling list is optionally transmitted as part of a sequence parameter
|
11476
11894
|
* set and is not relevant to transmuxing.
|
11477
11895
|
* @param count the number of entries in this scaling list
|
11478
11896
|
* @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
|
11479
11897
|
*/
|
11480
|
-
skipScalingList(count) {
|
11898
|
+
skipScalingList(count, reader) {
|
11481
11899
|
let lastScale = 8;
|
11482
11900
|
let nextScale = 8;
|
11483
11901
|
let deltaScale;
|
11484
11902
|
for (let j = 0; j < count; j++) {
|
11485
11903
|
if (nextScale !== 0) {
|
11486
|
-
deltaScale =
|
11904
|
+
deltaScale = reader.readEG();
|
11487
11905
|
nextScale = (lastScale + deltaScale + 256) % 256;
|
11488
11906
|
}
|
11489
11907
|
lastScale = nextScale === 0 ? lastScale : nextScale;
|
@@ -11498,7 +11916,8 @@ class ExpGolomb {
|
|
11498
11916
|
* sequence parameter set, including the dimensions of the
|
11499
11917
|
* associated video frames.
|
11500
11918
|
*/
|
11501
|
-
readSPS() {
|
11919
|
+
readSPS(sps) {
|
11920
|
+
const eg = new ExpGolomb(sps);
|
11502
11921
|
let frameCropLeftOffset = 0;
|
11503
11922
|
let frameCropRightOffset = 0;
|
11504
11923
|
let frameCropTopOffset = 0;
|
@@ -11506,13 +11925,13 @@ class ExpGolomb {
|
|
11506
11925
|
let numRefFramesInPicOrderCntCycle;
|
11507
11926
|
let scalingListCount;
|
11508
11927
|
let i;
|
11509
|
-
const readUByte =
|
11510
|
-
const readBits =
|
11511
|
-
const readUEG =
|
11512
|
-
const readBoolean =
|
11513
|
-
const skipBits =
|
11514
|
-
const skipEG =
|
11515
|
-
const skipUEG =
|
11928
|
+
const readUByte = eg.readUByte.bind(eg);
|
11929
|
+
const readBits = eg.readBits.bind(eg);
|
11930
|
+
const readUEG = eg.readUEG.bind(eg);
|
11931
|
+
const readBoolean = eg.readBoolean.bind(eg);
|
11932
|
+
const skipBits = eg.skipBits.bind(eg);
|
11933
|
+
const skipEG = eg.skipEG.bind(eg);
|
11934
|
+
const skipUEG = eg.skipUEG.bind(eg);
|
11516
11935
|
const skipScalingList = this.skipScalingList.bind(this);
|
11517
11936
|
readUByte();
|
11518
11937
|
const profileIdc = readUByte(); // profile_idc
|
@@ -11537,9 +11956,9 @@ class ExpGolomb {
|
|
11537
11956
|
if (readBoolean()) {
|
11538
11957
|
// seq_scaling_list_present_flag[ i ]
|
11539
11958
|
if (i < 6) {
|
11540
|
-
skipScalingList(16);
|
11959
|
+
skipScalingList(16, eg);
|
11541
11960
|
} else {
|
11542
|
-
skipScalingList(64);
|
11961
|
+
skipScalingList(64, eg);
|
11543
11962
|
}
|
11544
11963
|
}
|
11545
11964
|
}
|
@@ -11644,19 +12063,15 @@ class ExpGolomb {
|
|
11644
12063
|
pixelRatio: pixelRatio
|
11645
12064
|
};
|
11646
12065
|
}
|
11647
|
-
readSliceType() {
|
11648
|
-
// skip NALu type
|
11649
|
-
this.readUByte();
|
11650
|
-
// discard first_mb_in_slice
|
11651
|
-
this.readUEG();
|
11652
|
-
// return slice_type
|
11653
|
-
return this.readUEG();
|
11654
|
-
}
|
11655
12066
|
}
|
11656
12067
|
|
11657
|
-
class
|
11658
|
-
|
11659
|
-
|
12068
|
+
class HevcVideoParser extends BaseVideoParser {
|
12069
|
+
constructor(...args) {
|
12070
|
+
super(...args);
|
12071
|
+
this.initVPS = null;
|
12072
|
+
}
|
12073
|
+
parsePES(track, textTrack, pes, last, duration) {
|
12074
|
+
const units = this.parseNALu(track, pes.data);
|
11660
12075
|
let VideoSample = this.VideoSample;
|
11661
12076
|
let push;
|
11662
12077
|
let spsfound = false;
|
@@ -11672,42 +12087,49 @@ class AvcVideoParser extends BaseVideoParser {
|
|
11672
12087
|
units.forEach(unit => {
|
11673
12088
|
var _VideoSample2;
|
11674
12089
|
switch (unit.type) {
|
11675
|
-
//
|
12090
|
+
// NON-IDR, NON RANDOM ACCESS SLICE
|
12091
|
+
case 0:
|
11676
12092
|
case 1:
|
11677
|
-
|
11678
|
-
|
11679
|
-
|
11680
|
-
|
11681
|
-
|
11682
|
-
|
11683
|
-
|
11684
|
-
|
11685
|
-
|
11686
|
-
|
11687
|
-
|
11688
|
-
|
11689
|
-
|
11690
|
-
|
11691
|
-
|
11692
|
-
|
11693
|
-
|
11694
|
-
|
11695
|
-
|
11696
|
-
|
11697
|
-
|
11698
|
-
|
11699
|
-
|
11700
|
-
|
11701
|
-
|
11702
|
-
if (!VideoSample) {
|
11703
|
-
|
12093
|
+
case 2:
|
12094
|
+
case 3:
|
12095
|
+
case 4:
|
12096
|
+
case 5:
|
12097
|
+
case 6:
|
12098
|
+
case 7:
|
12099
|
+
case 8:
|
12100
|
+
case 9:
|
12101
|
+
if (!VideoSample) {
|
12102
|
+
VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
|
12103
|
+
}
|
12104
|
+
VideoSample.frame = true;
|
12105
|
+
push = true;
|
12106
|
+
break;
|
12107
|
+
|
12108
|
+
// CRA, BLA (random access picture)
|
12109
|
+
case 16:
|
12110
|
+
case 17:
|
12111
|
+
case 18:
|
12112
|
+
case 21:
|
12113
|
+
push = true;
|
12114
|
+
if (spsfound) {
|
12115
|
+
var _VideoSample;
|
12116
|
+
// handle PES not starting with AUD
|
12117
|
+
// if we have frame data already, that cannot belong to the same frame, so force a push
|
12118
|
+
if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
|
12119
|
+
this.pushAccessUnit(VideoSample, track);
|
12120
|
+
VideoSample = this.VideoSample = null;
|
11704
12121
|
}
|
11705
|
-
VideoSample.frame = true;
|
11706
|
-
VideoSample.key = iskey;
|
11707
|
-
break;
|
11708
|
-
// IDR
|
11709
12122
|
}
|
11710
|
-
|
12123
|
+
if (!VideoSample) {
|
12124
|
+
VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
|
12125
|
+
}
|
12126
|
+
VideoSample.key = true;
|
12127
|
+
VideoSample.frame = true;
|
12128
|
+
break;
|
12129
|
+
|
12130
|
+
// IDR
|
12131
|
+
case 19:
|
12132
|
+
case 20:
|
11711
12133
|
push = true;
|
11712
12134
|
// handle PES not starting with AUD
|
11713
12135
|
// if we have frame data already, that cannot belong to the same frame, so force a push
|
@@ -11721,48 +12143,76 @@ class AvcVideoParser extends BaseVideoParser {
|
|
11721
12143
|
VideoSample.key = true;
|
11722
12144
|
VideoSample.frame = true;
|
11723
12145
|
break;
|
12146
|
+
|
11724
12147
|
// SEI
|
11725
|
-
case
|
11726
|
-
|
11727
|
-
|
11728
|
-
|
11729
|
-
|
11730
|
-
|
12148
|
+
case 39:
|
12149
|
+
push = true;
|
12150
|
+
parseSEIMessageFromNALu(unit.data, 2,
|
12151
|
+
// NALu header size
|
12152
|
+
pes.pts, textTrack.samples);
|
12153
|
+
break;
|
12154
|
+
|
12155
|
+
// VPS
|
12156
|
+
case 32:
|
12157
|
+
push = true;
|
12158
|
+
if (!track.vps) {
|
12159
|
+
const config = this.readVPS(unit.data);
|
12160
|
+
track.params = _objectSpread2({}, config);
|
12161
|
+
this.initVPS = unit.data;
|
11731
12162
|
}
|
11732
|
-
|
11733
|
-
|
11734
|
-
|
11735
|
-
|
11736
|
-
|
11737
|
-
|
11738
|
-
|
11739
|
-
|
11740
|
-
if (
|
12163
|
+
track.vps = [unit.data];
|
12164
|
+
break;
|
12165
|
+
|
12166
|
+
// SPS
|
12167
|
+
case 33:
|
12168
|
+
push = true;
|
12169
|
+
spsfound = true;
|
12170
|
+
if (typeof track.params === 'object') {
|
12171
|
+
if (track.vps !== undefined && track.vps[0] !== this.initVPS && track.sps !== undefined && !this.matchSPS(track.sps[0], unit.data)) {
|
12172
|
+
this.initVPS = track.vps[0];
|
12173
|
+
track.sps = track.pps = undefined;
|
12174
|
+
}
|
12175
|
+
if (!track.sps) {
|
12176
|
+
const config = this.readSPS(unit.data);
|
11741
12177
|
track.width = config.width;
|
11742
12178
|
track.height = config.height;
|
11743
12179
|
track.pixelRatio = config.pixelRatio;
|
11744
|
-
track.sps = [sps];
|
11745
12180
|
track.duration = duration;
|
11746
|
-
|
11747
|
-
|
11748
|
-
for (
|
11749
|
-
|
11750
|
-
if (h.length < 2) {
|
11751
|
-
h = '0' + h;
|
11752
|
-
}
|
11753
|
-
codecstring += h;
|
12181
|
+
track.codec = config.codecString;
|
12182
|
+
track.sps = [];
|
12183
|
+
for (const prop in config.params) {
|
12184
|
+
track.params[prop] = config.params[prop];
|
11754
12185
|
}
|
11755
|
-
track.codec = codecstring;
|
11756
12186
|
}
|
11757
|
-
|
12187
|
+
if (track.vps !== undefined && track.vps[0] === this.initVPS) {
|
12188
|
+
track.sps.push(unit.data);
|
12189
|
+
}
|
11758
12190
|
}
|
12191
|
+
if (!VideoSample) {
|
12192
|
+
VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
|
12193
|
+
}
|
12194
|
+
VideoSample.key = true;
|
12195
|
+
break;
|
12196
|
+
|
11759
12197
|
// PPS
|
11760
|
-
case
|
12198
|
+
case 34:
|
11761
12199
|
push = true;
|
11762
|
-
track.
|
12200
|
+
if (typeof track.params === 'object') {
|
12201
|
+
if (!track.pps) {
|
12202
|
+
track.pps = [];
|
12203
|
+
const config = this.readPPS(unit.data);
|
12204
|
+
for (const prop in config) {
|
12205
|
+
track.params[prop] = config[prop];
|
12206
|
+
}
|
12207
|
+
}
|
12208
|
+
if (this.initVPS !== null || track.pps.length === 0) {
|
12209
|
+
track.pps.push(unit.data);
|
12210
|
+
}
|
12211
|
+
}
|
11763
12212
|
break;
|
11764
|
-
|
11765
|
-
|
12213
|
+
|
12214
|
+
// ACCESS UNIT DELIMITER
|
12215
|
+
case 35:
|
11766
12216
|
push = true;
|
11767
12217
|
track.audFound = true;
|
11768
12218
|
if (VideoSample) {
|
@@ -11770,14 +12220,10 @@ class AvcVideoParser extends BaseVideoParser {
|
|
11770
12220
|
}
|
11771
12221
|
VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
|
11772
12222
|
break;
|
11773
|
-
// Filler Data
|
11774
|
-
case 12:
|
11775
|
-
push = true;
|
11776
|
-
break;
|
11777
12223
|
default:
|
11778
12224
|
push = false;
|
11779
12225
|
if (VideoSample) {
|
11780
|
-
VideoSample.debug += 'unknown NAL ' + unit.type + ' ';
|
12226
|
+
VideoSample.debug += 'unknown or irrelevant NAL ' + unit.type + ' ';
|
11781
12227
|
}
|
11782
12228
|
break;
|
11783
12229
|
}
|
@@ -11792,109 +12238,423 @@ class AvcVideoParser extends BaseVideoParser {
|
|
11792
12238
|
this.VideoSample = null;
|
11793
12239
|
}
|
11794
12240
|
}
|
11795
|
-
|
11796
|
-
|
11797
|
-
|
11798
|
-
|
11799
|
-
const
|
11800
|
-
let
|
11801
|
-
let
|
11802
|
-
|
11803
|
-
|
11804
|
-
|
11805
|
-
|
11806
|
-
|
11807
|
-
|
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;
|
12241
|
+
getNALuType(data, offset) {
|
12242
|
+
return (data[offset] & 0x7e) >>> 1;
|
12243
|
+
}
|
12244
|
+
ebsp2rbsp(arr) {
|
12245
|
+
const dst = new Uint8Array(arr.byteLength);
|
12246
|
+
let dstIdx = 0;
|
12247
|
+
for (let i = 0; i < arr.byteLength; i++) {
|
12248
|
+
if (i >= 2) {
|
12249
|
+
// Unescape: Skip 0x03 after 00 00
|
12250
|
+
if (arr[i] === 0x03 && arr[i - 1] === 0x00 && arr[i - 2] === 0x00) {
|
12251
|
+
continue;
|
12252
|
+
}
|
11826
12253
|
}
|
11827
|
-
|
11828
|
-
|
11829
|
-
|
11830
|
-
|
11831
|
-
|
11832
|
-
|
11833
|
-
|
11834
|
-
|
11835
|
-
|
11836
|
-
|
11837
|
-
|
11838
|
-
|
11839
|
-
|
11840
|
-
|
11841
|
-
|
11842
|
-
|
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);
|
11853
|
-
}
|
11854
|
-
}
|
11855
|
-
// If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
|
12254
|
+
dst[dstIdx] = arr[i];
|
12255
|
+
dstIdx++;
|
12256
|
+
}
|
12257
|
+
return new Uint8Array(dst.buffer, 0, dstIdx);
|
12258
|
+
}
|
12259
|
+
readVPS(vps) {
|
12260
|
+
const eg = new ExpGolomb(vps);
|
12261
|
+
// remove header
|
12262
|
+
eg.readUByte();
|
12263
|
+
eg.readUByte();
|
12264
|
+
eg.readBits(4); // video_parameter_set_id
|
12265
|
+
eg.skipBits(2);
|
12266
|
+
eg.readBits(6); // max_layers_minus1
|
12267
|
+
const max_sub_layers_minus1 = eg.readBits(3);
|
12268
|
+
const temporal_id_nesting_flag = eg.readBoolean();
|
12269
|
+
// ...vui fps can be here, but empty fps value is not critical for metadata
|
11856
12270
|
|
11857
|
-
|
11858
|
-
|
11859
|
-
|
11860
|
-
|
12271
|
+
return {
|
12272
|
+
numTemporalLayers: max_sub_layers_minus1 + 1,
|
12273
|
+
temporalIdNested: temporal_id_nesting_flag
|
12274
|
+
};
|
12275
|
+
}
|
12276
|
+
readSPS(sps) {
|
12277
|
+
const eg = new ExpGolomb(this.ebsp2rbsp(sps));
|
12278
|
+
eg.readUByte();
|
12279
|
+
eg.readUByte();
|
12280
|
+
eg.readBits(4); //video_parameter_set_id
|
12281
|
+
const max_sub_layers_minus1 = eg.readBits(3);
|
12282
|
+
eg.readBoolean(); // temporal_id_nesting_flag
|
12283
|
+
|
12284
|
+
// profile_tier_level
|
12285
|
+
const general_profile_space = eg.readBits(2);
|
12286
|
+
const general_tier_flag = eg.readBoolean();
|
12287
|
+
const general_profile_idc = eg.readBits(5);
|
12288
|
+
const general_profile_compatibility_flags_1 = eg.readUByte();
|
12289
|
+
const general_profile_compatibility_flags_2 = eg.readUByte();
|
12290
|
+
const general_profile_compatibility_flags_3 = eg.readUByte();
|
12291
|
+
const general_profile_compatibility_flags_4 = eg.readUByte();
|
12292
|
+
const general_constraint_indicator_flags_1 = eg.readUByte();
|
12293
|
+
const general_constraint_indicator_flags_2 = eg.readUByte();
|
12294
|
+
const general_constraint_indicator_flags_3 = eg.readUByte();
|
12295
|
+
const general_constraint_indicator_flags_4 = eg.readUByte();
|
12296
|
+
const general_constraint_indicator_flags_5 = eg.readUByte();
|
12297
|
+
const general_constraint_indicator_flags_6 = eg.readUByte();
|
12298
|
+
const general_level_idc = eg.readUByte();
|
12299
|
+
const sub_layer_profile_present_flags = [];
|
12300
|
+
const sub_layer_level_present_flags = [];
|
12301
|
+
for (let i = 0; i < max_sub_layers_minus1; i++) {
|
12302
|
+
sub_layer_profile_present_flags.push(eg.readBoolean());
|
12303
|
+
sub_layer_level_present_flags.push(eg.readBoolean());
|
12304
|
+
}
|
12305
|
+
if (max_sub_layers_minus1 > 0) {
|
12306
|
+
for (let i = max_sub_layers_minus1; i < 8; i++) {
|
12307
|
+
eg.readBits(2);
|
12308
|
+
}
|
12309
|
+
}
|
12310
|
+
for (let i = 0; i < max_sub_layers_minus1; i++) {
|
12311
|
+
if (sub_layer_profile_present_flags[i]) {
|
12312
|
+
eg.readUByte(); // sub_layer_profile_space, sub_layer_tier_flag, sub_layer_profile_idc
|
12313
|
+
eg.readUByte();
|
12314
|
+
eg.readUByte();
|
12315
|
+
eg.readUByte();
|
12316
|
+
eg.readUByte(); // sub_layer_profile_compatibility_flag
|
12317
|
+
eg.readUByte();
|
12318
|
+
eg.readUByte();
|
12319
|
+
eg.readUByte();
|
12320
|
+
eg.readUByte();
|
12321
|
+
eg.readUByte();
|
12322
|
+
eg.readUByte();
|
12323
|
+
}
|
12324
|
+
if (sub_layer_level_present_flags[i]) {
|
12325
|
+
eg.readUByte();
|
12326
|
+
}
|
12327
|
+
}
|
12328
|
+
eg.readUEG(); // seq_parameter_set_id
|
12329
|
+
const chroma_format_idc = eg.readUEG();
|
12330
|
+
if (chroma_format_idc == 3) {
|
12331
|
+
eg.skipBits(1); //separate_colour_plane_flag
|
12332
|
+
}
|
12333
|
+
const pic_width_in_luma_samples = eg.readUEG();
|
12334
|
+
const pic_height_in_luma_samples = eg.readUEG();
|
12335
|
+
const conformance_window_flag = eg.readBoolean();
|
12336
|
+
let pic_left_offset = 0,
|
12337
|
+
pic_right_offset = 0,
|
12338
|
+
pic_top_offset = 0,
|
12339
|
+
pic_bottom_offset = 0;
|
12340
|
+
if (conformance_window_flag) {
|
12341
|
+
pic_left_offset += eg.readUEG();
|
12342
|
+
pic_right_offset += eg.readUEG();
|
12343
|
+
pic_top_offset += eg.readUEG();
|
12344
|
+
pic_bottom_offset += eg.readUEG();
|
12345
|
+
}
|
12346
|
+
const bit_depth_luma_minus8 = eg.readUEG();
|
12347
|
+
const bit_depth_chroma_minus8 = eg.readUEG();
|
12348
|
+
const log2_max_pic_order_cnt_lsb_minus4 = eg.readUEG();
|
12349
|
+
const sub_layer_ordering_info_present_flag = eg.readBoolean();
|
12350
|
+
for (let i = sub_layer_ordering_info_present_flag ? 0 : max_sub_layers_minus1; i <= max_sub_layers_minus1; i++) {
|
12351
|
+
eg.skipUEG(); // max_dec_pic_buffering_minus1[i]
|
12352
|
+
eg.skipUEG(); // max_num_reorder_pics[i]
|
12353
|
+
eg.skipUEG(); // max_latency_increase_plus1[i]
|
12354
|
+
}
|
12355
|
+
eg.skipUEG(); // log2_min_luma_coding_block_size_minus3
|
12356
|
+
eg.skipUEG(); // log2_diff_max_min_luma_coding_block_size
|
12357
|
+
eg.skipUEG(); // log2_min_transform_block_size_minus2
|
12358
|
+
eg.skipUEG(); // log2_diff_max_min_transform_block_size
|
12359
|
+
eg.skipUEG(); // max_transform_hierarchy_depth_inter
|
12360
|
+
eg.skipUEG(); // max_transform_hierarchy_depth_intra
|
12361
|
+
const scaling_list_enabled_flag = eg.readBoolean();
|
12362
|
+
if (scaling_list_enabled_flag) {
|
12363
|
+
const sps_scaling_list_data_present_flag = eg.readBoolean();
|
12364
|
+
if (sps_scaling_list_data_present_flag) {
|
12365
|
+
for (let sizeId = 0; sizeId < 4; sizeId++) {
|
12366
|
+
for (let matrixId = 0; matrixId < (sizeId === 3 ? 2 : 6); matrixId++) {
|
12367
|
+
const scaling_list_pred_mode_flag = eg.readBoolean();
|
12368
|
+
if (!scaling_list_pred_mode_flag) {
|
12369
|
+
eg.readUEG(); // scaling_list_pred_matrix_id_delta
|
12370
|
+
} else {
|
12371
|
+
const coefNum = Math.min(64, 1 << 4 + (sizeId << 1));
|
12372
|
+
if (sizeId > 1) {
|
12373
|
+
eg.readEG();
|
12374
|
+
}
|
12375
|
+
for (let i = 0; i < coefNum; i++) {
|
12376
|
+
eg.readEG();
|
12377
|
+
}
|
11861
12378
|
}
|
11862
12379
|
}
|
11863
12380
|
}
|
11864
|
-
// check if we can read unit type
|
11865
|
-
if (i < len) {
|
11866
|
-
unitType = array[i] & 0x1f;
|
11867
|
-
// logger.log('find NALU @ offset:' + i + ',type:' + unitType);
|
11868
|
-
lastUnitStart = i;
|
11869
|
-
lastUnitType = unitType;
|
11870
|
-
state = 0;
|
11871
|
-
} else {
|
11872
|
-
// not enough byte to read unit type. let's read it on next PES parsing
|
11873
|
-
state = -1;
|
11874
|
-
}
|
11875
|
-
} else {
|
11876
|
-
state = 0;
|
11877
12381
|
}
|
11878
12382
|
}
|
11879
|
-
|
11880
|
-
|
11881
|
-
|
11882
|
-
|
11883
|
-
|
11884
|
-
|
11885
|
-
|
11886
|
-
|
11887
|
-
}
|
11888
|
-
|
11889
|
-
|
11890
|
-
|
11891
|
-
|
11892
|
-
if (
|
11893
|
-
|
12383
|
+
eg.readBoolean(); // amp_enabled_flag
|
12384
|
+
eg.readBoolean(); // sample_adaptive_offset_enabled_flag
|
12385
|
+
const pcm_enabled_flag = eg.readBoolean();
|
12386
|
+
if (pcm_enabled_flag) {
|
12387
|
+
eg.readUByte();
|
12388
|
+
eg.skipUEG();
|
12389
|
+
eg.skipUEG();
|
12390
|
+
eg.readBoolean();
|
12391
|
+
}
|
12392
|
+
const num_short_term_ref_pic_sets = eg.readUEG();
|
12393
|
+
let num_delta_pocs = 0;
|
12394
|
+
for (let i = 0; i < num_short_term_ref_pic_sets; i++) {
|
12395
|
+
let inter_ref_pic_set_prediction_flag = false;
|
12396
|
+
if (i !== 0) {
|
12397
|
+
inter_ref_pic_set_prediction_flag = eg.readBoolean();
|
12398
|
+
}
|
12399
|
+
if (inter_ref_pic_set_prediction_flag) {
|
12400
|
+
if (i === num_short_term_ref_pic_sets) {
|
12401
|
+
eg.readUEG();
|
12402
|
+
}
|
12403
|
+
eg.readBoolean();
|
12404
|
+
eg.readUEG();
|
12405
|
+
let next_num_delta_pocs = 0;
|
12406
|
+
for (let j = 0; j <= num_delta_pocs; j++) {
|
12407
|
+
const used_by_curr_pic_flag = eg.readBoolean();
|
12408
|
+
let use_delta_flag = false;
|
12409
|
+
if (!used_by_curr_pic_flag) {
|
12410
|
+
use_delta_flag = eg.readBoolean();
|
12411
|
+
}
|
12412
|
+
if (used_by_curr_pic_flag || use_delta_flag) {
|
12413
|
+
next_num_delta_pocs++;
|
12414
|
+
}
|
12415
|
+
}
|
12416
|
+
num_delta_pocs = next_num_delta_pocs;
|
12417
|
+
} else {
|
12418
|
+
const num_negative_pics = eg.readUEG();
|
12419
|
+
const num_positive_pics = eg.readUEG();
|
12420
|
+
num_delta_pocs = num_negative_pics + num_positive_pics;
|
12421
|
+
for (let j = 0; j < num_negative_pics; j++) {
|
12422
|
+
eg.readUEG();
|
12423
|
+
eg.readBoolean();
|
12424
|
+
}
|
12425
|
+
for (let j = 0; j < num_positive_pics; j++) {
|
12426
|
+
eg.readUEG();
|
12427
|
+
eg.readBoolean();
|
12428
|
+
}
|
12429
|
+
}
|
12430
|
+
}
|
12431
|
+
const long_term_ref_pics_present_flag = eg.readBoolean();
|
12432
|
+
if (long_term_ref_pics_present_flag) {
|
12433
|
+
const num_long_term_ref_pics_sps = eg.readUEG();
|
12434
|
+
for (let i = 0; i < num_long_term_ref_pics_sps; i++) {
|
12435
|
+
for (let j = 0; j < log2_max_pic_order_cnt_lsb_minus4 + 4; j++) {
|
12436
|
+
eg.readBits(1);
|
12437
|
+
}
|
12438
|
+
eg.readBits(1);
|
12439
|
+
}
|
12440
|
+
}
|
12441
|
+
let min_spatial_segmentation_idc = 0;
|
12442
|
+
let sar_width = 1,
|
12443
|
+
sar_height = 1;
|
12444
|
+
let fps_fixed = true,
|
12445
|
+
fps_den = 1,
|
12446
|
+
fps_num = 0;
|
12447
|
+
eg.readBoolean(); // sps_temporal_mvp_enabled_flag
|
12448
|
+
eg.readBoolean(); // strong_intra_smoothing_enabled_flag
|
12449
|
+
let default_display_window_flag = false;
|
12450
|
+
const vui_parameters_present_flag = eg.readBoolean();
|
12451
|
+
if (vui_parameters_present_flag) {
|
12452
|
+
const aspect_ratio_info_present_flag = eg.readBoolean();
|
12453
|
+
if (aspect_ratio_info_present_flag) {
|
12454
|
+
const aspect_ratio_idc = eg.readUByte();
|
12455
|
+
const sar_width_table = [1, 12, 10, 16, 40, 24, 20, 32, 80, 18, 15, 64, 160, 4, 3, 2];
|
12456
|
+
const sar_height_table = [1, 11, 11, 11, 33, 11, 11, 11, 33, 11, 11, 33, 99, 3, 2, 1];
|
12457
|
+
if (aspect_ratio_idc > 0 && aspect_ratio_idc < 16) {
|
12458
|
+
sar_width = sar_width_table[aspect_ratio_idc - 1];
|
12459
|
+
sar_height = sar_height_table[aspect_ratio_idc - 1];
|
12460
|
+
} else if (aspect_ratio_idc === 255) {
|
12461
|
+
sar_width = eg.readBits(16);
|
12462
|
+
sar_height = eg.readBits(16);
|
12463
|
+
}
|
12464
|
+
}
|
12465
|
+
const overscan_info_present_flag = eg.readBoolean();
|
12466
|
+
if (overscan_info_present_flag) {
|
12467
|
+
eg.readBoolean();
|
12468
|
+
}
|
12469
|
+
const video_signal_type_present_flag = eg.readBoolean();
|
12470
|
+
if (video_signal_type_present_flag) {
|
12471
|
+
eg.readBits(3);
|
12472
|
+
eg.readBoolean();
|
12473
|
+
const colour_description_present_flag = eg.readBoolean();
|
12474
|
+
if (colour_description_present_flag) {
|
12475
|
+
eg.readUByte();
|
12476
|
+
eg.readUByte();
|
12477
|
+
eg.readUByte();
|
12478
|
+
}
|
12479
|
+
}
|
12480
|
+
const chroma_loc_info_present_flag = eg.readBoolean();
|
12481
|
+
if (chroma_loc_info_present_flag) {
|
12482
|
+
eg.readUEG();
|
12483
|
+
eg.readUEG();
|
12484
|
+
}
|
12485
|
+
eg.readBoolean(); // neutral_chroma_indication_flag
|
12486
|
+
eg.readBoolean(); // field_seq_flag
|
12487
|
+
eg.readBoolean(); // frame_field_info_present_flag
|
12488
|
+
default_display_window_flag = eg.readBoolean();
|
12489
|
+
if (default_display_window_flag) {
|
12490
|
+
pic_left_offset += eg.readUEG();
|
12491
|
+
pic_right_offset += eg.readUEG();
|
12492
|
+
pic_top_offset += eg.readUEG();
|
12493
|
+
pic_bottom_offset += eg.readUEG();
|
12494
|
+
}
|
12495
|
+
const vui_timing_info_present_flag = eg.readBoolean();
|
12496
|
+
if (vui_timing_info_present_flag) {
|
12497
|
+
fps_den = eg.readBits(32);
|
12498
|
+
fps_num = eg.readBits(32);
|
12499
|
+
const vui_poc_proportional_to_timing_flag = eg.readBoolean();
|
12500
|
+
if (vui_poc_proportional_to_timing_flag) {
|
12501
|
+
eg.readUEG();
|
12502
|
+
}
|
12503
|
+
const vui_hrd_parameters_present_flag = eg.readBoolean();
|
12504
|
+
if (vui_hrd_parameters_present_flag) {
|
12505
|
+
//const commonInfPresentFlag = true;
|
12506
|
+
//if (commonInfPresentFlag) {
|
12507
|
+
const nal_hrd_parameters_present_flag = eg.readBoolean();
|
12508
|
+
const vcl_hrd_parameters_present_flag = eg.readBoolean();
|
12509
|
+
let sub_pic_hrd_params_present_flag = false;
|
12510
|
+
if (nal_hrd_parameters_present_flag || vcl_hrd_parameters_present_flag) {
|
12511
|
+
sub_pic_hrd_params_present_flag = eg.readBoolean();
|
12512
|
+
if (sub_pic_hrd_params_present_flag) {
|
12513
|
+
eg.readUByte();
|
12514
|
+
eg.readBits(5);
|
12515
|
+
eg.readBoolean();
|
12516
|
+
eg.readBits(5);
|
12517
|
+
}
|
12518
|
+
eg.readBits(4); // bit_rate_scale
|
12519
|
+
eg.readBits(4); // cpb_size_scale
|
12520
|
+
if (sub_pic_hrd_params_present_flag) {
|
12521
|
+
eg.readBits(4);
|
12522
|
+
}
|
12523
|
+
eg.readBits(5);
|
12524
|
+
eg.readBits(5);
|
12525
|
+
eg.readBits(5);
|
12526
|
+
}
|
12527
|
+
//}
|
12528
|
+
for (let i = 0; i <= max_sub_layers_minus1; i++) {
|
12529
|
+
fps_fixed = eg.readBoolean(); // fixed_pic_rate_general_flag
|
12530
|
+
const fixed_pic_rate_within_cvs_flag = fps_fixed || eg.readBoolean();
|
12531
|
+
let low_delay_hrd_flag = false;
|
12532
|
+
if (fixed_pic_rate_within_cvs_flag) {
|
12533
|
+
eg.readEG();
|
12534
|
+
} else {
|
12535
|
+
low_delay_hrd_flag = eg.readBoolean();
|
12536
|
+
}
|
12537
|
+
const cpb_cnt = low_delay_hrd_flag ? 1 : eg.readUEG() + 1;
|
12538
|
+
if (nal_hrd_parameters_present_flag) {
|
12539
|
+
for (let j = 0; j < cpb_cnt; j++) {
|
12540
|
+
eg.readUEG();
|
12541
|
+
eg.readUEG();
|
12542
|
+
if (sub_pic_hrd_params_present_flag) {
|
12543
|
+
eg.readUEG();
|
12544
|
+
eg.readUEG();
|
12545
|
+
}
|
12546
|
+
eg.skipBits(1);
|
12547
|
+
}
|
12548
|
+
}
|
12549
|
+
if (vcl_hrd_parameters_present_flag) {
|
12550
|
+
for (let j = 0; j < cpb_cnt; j++) {
|
12551
|
+
eg.readUEG();
|
12552
|
+
eg.readUEG();
|
12553
|
+
if (sub_pic_hrd_params_present_flag) {
|
12554
|
+
eg.readUEG();
|
12555
|
+
eg.readUEG();
|
12556
|
+
}
|
12557
|
+
eg.skipBits(1);
|
12558
|
+
}
|
12559
|
+
}
|
12560
|
+
}
|
12561
|
+
}
|
11894
12562
|
}
|
12563
|
+
const bitstream_restriction_flag = eg.readBoolean();
|
12564
|
+
if (bitstream_restriction_flag) {
|
12565
|
+
eg.readBoolean(); // tiles_fixed_structure_flag
|
12566
|
+
eg.readBoolean(); // motion_vectors_over_pic_boundaries_flag
|
12567
|
+
eg.readBoolean(); // restricted_ref_pic_lists_flag
|
12568
|
+
min_spatial_segmentation_idc = eg.readUEG();
|
12569
|
+
}
|
12570
|
+
}
|
12571
|
+
let width = pic_width_in_luma_samples,
|
12572
|
+
height = pic_height_in_luma_samples;
|
12573
|
+
if (conformance_window_flag || default_display_window_flag) {
|
12574
|
+
let chroma_scale_w = 1,
|
12575
|
+
chroma_scale_h = 1;
|
12576
|
+
if (chroma_format_idc === 1) {
|
12577
|
+
// YUV 420
|
12578
|
+
chroma_scale_w = chroma_scale_h = 2;
|
12579
|
+
} else if (chroma_format_idc == 2) {
|
12580
|
+
// YUV 422
|
12581
|
+
chroma_scale_w = 2;
|
12582
|
+
}
|
12583
|
+
width = pic_width_in_luma_samples - chroma_scale_w * pic_right_offset - chroma_scale_w * pic_left_offset;
|
12584
|
+
height = pic_height_in_luma_samples - chroma_scale_h * pic_bottom_offset - chroma_scale_h * pic_top_offset;
|
12585
|
+
}
|
12586
|
+
const profile_space_string = general_profile_space ? ['A', 'B', 'C'][general_profile_space] : '';
|
12587
|
+
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;
|
12588
|
+
let profile_compatibility_rev = 0;
|
12589
|
+
for (let i = 0; i < 32; i++) {
|
12590
|
+
profile_compatibility_rev = (profile_compatibility_rev | (profile_compatibility_buf >> i & 1) << 31 - i) >>> 0; // reverse bit position (and cast as UInt32)
|
12591
|
+
}
|
12592
|
+
let profile_compatibility_flags_string = profile_compatibility_rev.toString(16);
|
12593
|
+
if (general_profile_idc === 1 && profile_compatibility_flags_string === '2') {
|
12594
|
+
profile_compatibility_flags_string = '6';
|
12595
|
+
}
|
12596
|
+
const tier_flag_string = general_tier_flag ? 'H' : 'L';
|
12597
|
+
return {
|
12598
|
+
codecString: `hvc1.${profile_space_string}${general_profile_idc}.${profile_compatibility_flags_string}.${tier_flag_string}${general_level_idc}.B0`,
|
12599
|
+
params: {
|
12600
|
+
general_tier_flag,
|
12601
|
+
general_profile_idc,
|
12602
|
+
general_profile_space,
|
12603
|
+
general_profile_compatibility_flags: [general_profile_compatibility_flags_1, general_profile_compatibility_flags_2, general_profile_compatibility_flags_3, general_profile_compatibility_flags_4],
|
12604
|
+
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],
|
12605
|
+
general_level_idc,
|
12606
|
+
bit_depth: bit_depth_luma_minus8 + 8,
|
12607
|
+
bit_depth_luma_minus8,
|
12608
|
+
bit_depth_chroma_minus8,
|
12609
|
+
min_spatial_segmentation_idc,
|
12610
|
+
chroma_format_idc: chroma_format_idc,
|
12611
|
+
frame_rate: {
|
12612
|
+
fixed: fps_fixed,
|
12613
|
+
fps: fps_num / fps_den
|
12614
|
+
}
|
12615
|
+
},
|
12616
|
+
width,
|
12617
|
+
height,
|
12618
|
+
pixelRatio: [sar_width, sar_height]
|
12619
|
+
};
|
12620
|
+
}
|
12621
|
+
readPPS(pps) {
|
12622
|
+
const eg = new ExpGolomb(this.ebsp2rbsp(pps));
|
12623
|
+
eg.readUByte();
|
12624
|
+
eg.readUByte();
|
12625
|
+
eg.skipUEG(); // pic_parameter_set_id
|
12626
|
+
eg.skipUEG(); // seq_parameter_set_id
|
12627
|
+
eg.skipBits(2); // dependent_slice_segments_enabled_flag, output_flag_present_flag
|
12628
|
+
eg.skipBits(3); // num_extra_slice_header_bits
|
12629
|
+
eg.skipBits(2); // sign_data_hiding_enabled_flag, cabac_init_present_flag
|
12630
|
+
eg.skipUEG();
|
12631
|
+
eg.skipUEG();
|
12632
|
+
eg.skipEG(); // init_qp_minus26
|
12633
|
+
eg.skipBits(2); // constrained_intra_pred_flag, transform_skip_enabled_flag
|
12634
|
+
const cu_qp_delta_enabled_flag = eg.readBoolean();
|
12635
|
+
if (cu_qp_delta_enabled_flag) {
|
12636
|
+
eg.skipUEG();
|
12637
|
+
}
|
12638
|
+
eg.skipEG(); // cb_qp_offset
|
12639
|
+
eg.skipEG(); // cr_qp_offset
|
12640
|
+
eg.skipBits(4); // pps_slice_chroma_qp_offsets_present_flag, weighted_pred_flag, weighted_bipred_flag, transquant_bypass_enabled_flag
|
12641
|
+
const tiles_enabled_flag = eg.readBoolean();
|
12642
|
+
const entropy_coding_sync_enabled_flag = eg.readBoolean();
|
12643
|
+
let parallelismType = 1; // slice-based parallel decoding
|
12644
|
+
if (entropy_coding_sync_enabled_flag && tiles_enabled_flag) {
|
12645
|
+
parallelismType = 0; // mixed-type parallel decoding
|
12646
|
+
} else if (entropy_coding_sync_enabled_flag) {
|
12647
|
+
parallelismType = 3; // wavefront-based parallel decoding
|
12648
|
+
} else if (tiles_enabled_flag) {
|
12649
|
+
parallelismType = 2; // tile-based parallel decoding
|
11895
12650
|
}
|
11896
|
-
|
11897
|
-
|
12651
|
+
return {
|
12652
|
+
parallelismType
|
12653
|
+
};
|
12654
|
+
}
|
12655
|
+
matchSPS(sps1, sps2) {
|
12656
|
+
// compare without headers and VPS related params
|
12657
|
+
return String.fromCharCode.apply(null, sps1).substr(3) === String.fromCharCode.apply(null, sps2).substr(3);
|
11898
12658
|
}
|
11899
12659
|
}
|
11900
12660
|
|
@@ -11912,7 +12672,7 @@ class SampleAesDecrypter {
|
|
11912
12672
|
});
|
11913
12673
|
}
|
11914
12674
|
decryptBuffer(encryptedData) {
|
11915
|
-
return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer);
|
12675
|
+
return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer, DecrypterAesMode.cbc);
|
11916
12676
|
}
|
11917
12677
|
|
11918
12678
|
// AAC - encrypt all full 16 bytes blocks starting from offset 16
|
@@ -12026,7 +12786,7 @@ class TSDemuxer {
|
|
12026
12786
|
this.observer = observer;
|
12027
12787
|
this.config = config;
|
12028
12788
|
this.typeSupported = typeSupported;
|
12029
|
-
this.videoParser =
|
12789
|
+
this.videoParser = null;
|
12030
12790
|
}
|
12031
12791
|
static probe(data) {
|
12032
12792
|
const syncOffset = TSDemuxer.syncOffset(data);
|
@@ -12191,7 +12951,21 @@ class TSDemuxer {
|
|
12191
12951
|
case videoPid:
|
12192
12952
|
if (stt) {
|
12193
12953
|
if (videoData && (pes = parsePES(videoData))) {
|
12194
|
-
this.videoParser
|
12954
|
+
if (this.videoParser === null) {
|
12955
|
+
switch (videoTrack.segmentCodec) {
|
12956
|
+
case 'avc':
|
12957
|
+
this.videoParser = new AvcVideoParser();
|
12958
|
+
break;
|
12959
|
+
case 'hevc':
|
12960
|
+
{
|
12961
|
+
this.videoParser = new HevcVideoParser();
|
12962
|
+
}
|
12963
|
+
break;
|
12964
|
+
}
|
12965
|
+
}
|
12966
|
+
if (this.videoParser !== null) {
|
12967
|
+
this.videoParser.parsePES(videoTrack, textTrack, pes, false, this._duration);
|
12968
|
+
}
|
12195
12969
|
}
|
12196
12970
|
videoData = {
|
12197
12971
|
data: [],
|
@@ -12358,8 +13132,22 @@ class TSDemuxer {
|
|
12358
13132
|
// try to parse last PES packets
|
12359
13133
|
let pes;
|
12360
13134
|
if (videoData && (pes = parsePES(videoData))) {
|
12361
|
-
this.videoParser
|
12362
|
-
|
13135
|
+
if (this.videoParser === null) {
|
13136
|
+
switch (videoTrack.segmentCodec) {
|
13137
|
+
case 'avc':
|
13138
|
+
this.videoParser = new AvcVideoParser();
|
13139
|
+
break;
|
13140
|
+
case 'hevc':
|
13141
|
+
{
|
13142
|
+
this.videoParser = new HevcVideoParser();
|
13143
|
+
}
|
13144
|
+
break;
|
13145
|
+
}
|
13146
|
+
}
|
13147
|
+
if (this.videoParser !== null) {
|
13148
|
+
this.videoParser.parsePES(videoTrack, textTrack, pes, true, this._duration);
|
13149
|
+
videoTrack.pesData = null;
|
13150
|
+
}
|
12363
13151
|
} else {
|
12364
13152
|
// either avcData null or PES truncated, keep it for next frag parsing
|
12365
13153
|
videoTrack.pesData = videoData;
|
@@ -12692,7 +13480,14 @@ function parsePMT(data, offset, typeSupported, isSampleAes) {
|
|
12692
13480
|
logger.warn('Unsupported EC-3 in M2TS found');
|
12693
13481
|
break;
|
12694
13482
|
case 0x24:
|
12695
|
-
|
13483
|
+
// ITU-T Rec. H.265 and ISO/IEC 23008-2 (HEVC)
|
13484
|
+
{
|
13485
|
+
if (result.videoPid === -1) {
|
13486
|
+
result.videoPid = pid;
|
13487
|
+
result.segmentVideoCodec = 'hevc';
|
13488
|
+
logger.log('HEVC in M2TS found');
|
13489
|
+
}
|
13490
|
+
}
|
12696
13491
|
break;
|
12697
13492
|
}
|
12698
13493
|
// move to the next table entry
|
@@ -12915,6 +13710,8 @@ class MP4 {
|
|
12915
13710
|
avc1: [],
|
12916
13711
|
// codingname
|
12917
13712
|
avcC: [],
|
13713
|
+
hvc1: [],
|
13714
|
+
hvcC: [],
|
12918
13715
|
btrt: [],
|
12919
13716
|
dinf: [],
|
12920
13717
|
dref: [],
|
@@ -13339,8 +14136,10 @@ class MP4 {
|
|
13339
14136
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.ac3(track));
|
13340
14137
|
}
|
13341
14138
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track));
|
13342
|
-
} else {
|
14139
|
+
} else if (track.segmentCodec === 'avc') {
|
13343
14140
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
|
14141
|
+
} else {
|
14142
|
+
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.hvc1(track));
|
13344
14143
|
}
|
13345
14144
|
}
|
13346
14145
|
static tkhd(track) {
|
@@ -13478,6 +14277,84 @@ class MP4 {
|
|
13478
14277
|
const result = appendUint8Array(MP4.FTYP, movie);
|
13479
14278
|
return result;
|
13480
14279
|
}
|
14280
|
+
static hvc1(track) {
|
14281
|
+
const ps = track.params;
|
14282
|
+
const units = [track.vps, track.sps, track.pps];
|
14283
|
+
const NALuLengthSize = 4;
|
14284
|
+
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]);
|
14285
|
+
|
14286
|
+
// compute hvcC size in bytes
|
14287
|
+
let length = config.length;
|
14288
|
+
for (let i = 0; i < units.length; i += 1) {
|
14289
|
+
length += 3;
|
14290
|
+
for (let j = 0; j < units[i].length; j += 1) {
|
14291
|
+
length += 2 + units[i][j].length;
|
14292
|
+
}
|
14293
|
+
}
|
14294
|
+
const hvcC = new Uint8Array(length);
|
14295
|
+
hvcC.set(config, 0);
|
14296
|
+
length = config.length;
|
14297
|
+
// append parameter set units: one vps, one or more sps and pps
|
14298
|
+
const iMax = units.length - 1;
|
14299
|
+
for (let i = 0; i < units.length; i += 1) {
|
14300
|
+
hvcC.set(new Uint8Array([32 + i | (i === iMax ? 128 : 0), 0x00, units[i].length]), length);
|
14301
|
+
length += 3;
|
14302
|
+
for (let j = 0; j < units[i].length; j += 1) {
|
14303
|
+
hvcC.set(new Uint8Array([units[i][j].length >> 8, units[i][j].length & 255]), length);
|
14304
|
+
length += 2;
|
14305
|
+
hvcC.set(units[i][j], length);
|
14306
|
+
length += units[i][j].length;
|
14307
|
+
}
|
14308
|
+
}
|
14309
|
+
const hvcc = MP4.box(MP4.types.hvcC, hvcC);
|
14310
|
+
const width = track.width;
|
14311
|
+
const height = track.height;
|
14312
|
+
const hSpacing = track.pixelRatio[0];
|
14313
|
+
const vSpacing = track.pixelRatio[1];
|
14314
|
+
return MP4.box(MP4.types.hvc1, new Uint8Array([0x00, 0x00, 0x00,
|
14315
|
+
// reserved
|
14316
|
+
0x00, 0x00, 0x00,
|
14317
|
+
// reserved
|
14318
|
+
0x00, 0x01,
|
14319
|
+
// data_reference_index
|
14320
|
+
0x00, 0x00,
|
14321
|
+
// pre_defined
|
14322
|
+
0x00, 0x00,
|
14323
|
+
// reserved
|
14324
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
14325
|
+
// pre_defined
|
14326
|
+
width >> 8 & 0xff, width & 0xff,
|
14327
|
+
// width
|
14328
|
+
height >> 8 & 0xff, height & 0xff,
|
14329
|
+
// height
|
14330
|
+
0x00, 0x48, 0x00, 0x00,
|
14331
|
+
// horizresolution
|
14332
|
+
0x00, 0x48, 0x00, 0x00,
|
14333
|
+
// vertresolution
|
14334
|
+
0x00, 0x00, 0x00, 0x00,
|
14335
|
+
// reserved
|
14336
|
+
0x00, 0x01,
|
14337
|
+
// frame_count
|
14338
|
+
0x12, 0x64, 0x61, 0x69, 0x6c,
|
14339
|
+
// dailymotion/hls.js
|
14340
|
+
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,
|
14341
|
+
// compressorname
|
14342
|
+
0x00, 0x18,
|
14343
|
+
// depth = 24
|
14344
|
+
0x11, 0x11]),
|
14345
|
+
// pre_defined = -1
|
14346
|
+
hvcc, MP4.box(MP4.types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80,
|
14347
|
+
// bufferSizeDB
|
14348
|
+
0x00, 0x2d, 0xc6, 0xc0,
|
14349
|
+
// maxBitrate
|
14350
|
+
0x00, 0x2d, 0xc6, 0xc0])),
|
14351
|
+
// avgBitrate
|
14352
|
+
MP4.box(MP4.types.pasp, new Uint8Array([hSpacing >> 24,
|
14353
|
+
// hSpacing
|
14354
|
+
hSpacing >> 16 & 0xff, hSpacing >> 8 & 0xff, hSpacing & 0xff, vSpacing >> 24,
|
14355
|
+
// vSpacing
|
14356
|
+
vSpacing >> 16 & 0xff, vSpacing >> 8 & 0xff, vSpacing & 0xff])));
|
14357
|
+
}
|
13481
14358
|
}
|
13482
14359
|
MP4.types = void 0;
|
13483
14360
|
MP4.HDLR_TYPES = void 0;
|
@@ -13859,9 +14736,9 @@ class MP4Remuxer {
|
|
13859
14736
|
const foundOverlap = delta < -1;
|
13860
14737
|
if (foundHole || foundOverlap) {
|
13861
14738
|
if (foundHole) {
|
13862
|
-
logger.warn(
|
14739
|
+
logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
|
13863
14740
|
} else {
|
13864
|
-
logger.warn(
|
14741
|
+
logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
|
13865
14742
|
}
|
13866
14743
|
if (!foundOverlap || nextAvcDts >= inputSamples[0].pts || chromeVersion) {
|
13867
14744
|
firstDTS = nextAvcDts;
|
@@ -13870,12 +14747,24 @@ class MP4Remuxer {
|
|
13870
14747
|
inputSamples[0].dts = firstDTS;
|
13871
14748
|
inputSamples[0].pts = firstPTS;
|
13872
14749
|
} else {
|
14750
|
+
let isPTSOrderRetained = true;
|
13873
14751
|
for (let i = 0; i < inputSamples.length; i++) {
|
13874
|
-
if (inputSamples[i].dts > firstPTS) {
|
14752
|
+
if (inputSamples[i].dts > firstPTS && isPTSOrderRetained) {
|
13875
14753
|
break;
|
13876
14754
|
}
|
14755
|
+
const prevPTS = inputSamples[i].pts;
|
13877
14756
|
inputSamples[i].dts -= delta;
|
13878
14757
|
inputSamples[i].pts -= delta;
|
14758
|
+
|
14759
|
+
// check to see if this sample's PTS order has changed
|
14760
|
+
// relative to the next one
|
14761
|
+
if (i < inputSamples.length - 1) {
|
14762
|
+
const nextSamplePTS = inputSamples[i + 1].pts;
|
14763
|
+
const currentSamplePTS = inputSamples[i].pts;
|
14764
|
+
const currentOrder = nextSamplePTS <= currentSamplePTS;
|
14765
|
+
const prevOrder = nextSamplePTS <= prevPTS;
|
14766
|
+
isPTSOrderRetained = currentOrder == prevOrder;
|
14767
|
+
}
|
13879
14768
|
}
|
13880
14769
|
}
|
13881
14770
|
logger.log(`Video: Initial PTS/DTS adjusted: ${toMsFromMpegTsClock(firstPTS, true)}/${toMsFromMpegTsClock(firstDTS, true)}, delta: ${toMsFromMpegTsClock(delta, true)} ms`);
|
@@ -14023,7 +14912,7 @@ class MP4Remuxer {
|
|
14023
14912
|
}
|
14024
14913
|
}
|
14025
14914
|
}
|
14026
|
-
// next AVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
|
14915
|
+
// next AVC/HEVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
|
14027
14916
|
mp4SampleDuration = stretchedLastFrame || !mp4SampleDuration ? averageSampleDuration : mp4SampleDuration;
|
14028
14917
|
this.nextAvcDts = nextAvcDts = lastDTS + mp4SampleDuration;
|
14029
14918
|
this.videoSampleDuration = mp4SampleDuration;
|
@@ -14156,7 +15045,7 @@ class MP4Remuxer {
|
|
14156
15045
|
logger.warn(`[mp4-remuxer]: Injecting ${missing} audio frame @ ${(nextPts / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`);
|
14157
15046
|
for (let j = 0; j < missing; j++) {
|
14158
15047
|
const newStamp = Math.max(nextPts, 0);
|
14159
|
-
let fillFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
|
15048
|
+
let fillFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
|
14160
15049
|
if (!fillFrame) {
|
14161
15050
|
logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.');
|
14162
15051
|
fillFrame = sample.unit.subarray();
|
@@ -14284,7 +15173,7 @@ class MP4Remuxer {
|
|
14284
15173
|
// samples count of this segment's duration
|
14285
15174
|
const nbSamples = Math.ceil((endDTS - startDTS) / frameDuration);
|
14286
15175
|
// silent frame
|
14287
|
-
const silentFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
|
15176
|
+
const silentFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
|
14288
15177
|
logger.warn('[mp4-remuxer]: remux empty Audio');
|
14289
15178
|
// Can't remux if we can't generate a silent frame...
|
14290
15179
|
if (!silentFrame) {
|
@@ -14678,13 +15567,15 @@ class Transmuxer {
|
|
14678
15567
|
initSegmentData
|
14679
15568
|
} = transmuxConfig;
|
14680
15569
|
const keyData = getEncryptionType(uintData, decryptdata);
|
14681
|
-
if (keyData && keyData.method
|
15570
|
+
if (keyData && isFullSegmentEncryption(keyData.method)) {
|
14682
15571
|
const decrypter = this.getDecrypter();
|
15572
|
+
const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
|
15573
|
+
|
14683
15574
|
// Software decryption is synchronous; webCrypto is not
|
14684
15575
|
if (decrypter.isSync()) {
|
14685
15576
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
14686
15577
|
// data is handled in the flush() call
|
14687
|
-
let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer);
|
15578
|
+
let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode);
|
14688
15579
|
// For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
|
14689
15580
|
const loadingParts = chunkMeta.part > -1;
|
14690
15581
|
if (loadingParts) {
|
@@ -14696,7 +15587,7 @@ class Transmuxer {
|
|
14696
15587
|
}
|
14697
15588
|
uintData = new Uint8Array(decryptedData);
|
14698
15589
|
} else {
|
14699
|
-
this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer).then(decryptedData => {
|
15590
|
+
this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode).then(decryptedData => {
|
14700
15591
|
// Calling push here is important; if flush() is called while this is still resolving, this ensures that
|
14701
15592
|
// the decrypted data has been transmuxed
|
14702
15593
|
const result = this.push(decryptedData, null, chunkMeta);
|
@@ -15350,14 +16241,7 @@ class TransmuxerInterface {
|
|
15350
16241
|
this.observer = new EventEmitter();
|
15351
16242
|
this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
|
15352
16243
|
this.observer.on(Events.ERROR, forwardMessage);
|
15353
|
-
const
|
15354
|
-
isTypeSupported: () => false
|
15355
|
-
};
|
15356
|
-
const m2tsTypeSupported = {
|
15357
|
-
mpeg: MediaSource.isTypeSupported('audio/mpeg'),
|
15358
|
-
mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
|
15359
|
-
ac3: MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
|
15360
|
-
};
|
16244
|
+
const m2tsTypeSupported = getM2TSSupportedAudioTypes(config.preferManagedMediaSource);
|
15361
16245
|
|
15362
16246
|
// navigator.vendor is not always available in Web Worker
|
15363
16247
|
// refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
|
@@ -15645,7 +16529,7 @@ const TICK_INTERVAL$2 = 100; // how often to tick in ms
|
|
15645
16529
|
|
15646
16530
|
class AudioStreamController extends BaseStreamController {
|
15647
16531
|
constructor(hls, fragmentTracker, keyLoader) {
|
15648
|
-
super(hls, fragmentTracker, keyLoader, '
|
16532
|
+
super(hls, fragmentTracker, keyLoader, 'audio-stream-controller', PlaylistLevelType.AUDIO);
|
15649
16533
|
this.videoBuffer = null;
|
15650
16534
|
this.videoTrackCC = -1;
|
15651
16535
|
this.waitingVideoCC = -1;
|
@@ -15657,27 +16541,24 @@ class AudioStreamController extends BaseStreamController {
|
|
15657
16541
|
this.flushing = false;
|
15658
16542
|
this.bufferFlushed = false;
|
15659
16543
|
this.cachedTrackLoadedData = null;
|
15660
|
-
this.
|
16544
|
+
this.registerListeners();
|
15661
16545
|
}
|
15662
16546
|
onHandlerDestroying() {
|
15663
|
-
this.
|
16547
|
+
this.unregisterListeners();
|
15664
16548
|
super.onHandlerDestroying();
|
15665
16549
|
this.mainDetails = null;
|
15666
16550
|
this.bufferedTrack = null;
|
15667
16551
|
this.switchingTrack = null;
|
15668
16552
|
}
|
15669
|
-
|
16553
|
+
registerListeners() {
|
16554
|
+
super.registerListeners();
|
15670
16555
|
const {
|
15671
16556
|
hls
|
15672
16557
|
} = this;
|
15673
|
-
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
15674
|
-
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
15675
|
-
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
15676
16558
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
15677
16559
|
hls.on(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
|
15678
16560
|
hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
15679
16561
|
hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
|
15680
|
-
hls.on(Events.ERROR, this.onError, this);
|
15681
16562
|
hls.on(Events.BUFFER_RESET, this.onBufferReset, this);
|
15682
16563
|
hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
15683
16564
|
hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
@@ -15685,18 +16566,18 @@ class AudioStreamController extends BaseStreamController {
|
|
15685
16566
|
hls.on(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);
|
15686
16567
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
15687
16568
|
}
|
15688
|
-
|
16569
|
+
unregisterListeners() {
|
15689
16570
|
const {
|
15690
16571
|
hls
|
15691
16572
|
} = this;
|
15692
|
-
hls
|
15693
|
-
|
15694
|
-
|
16573
|
+
if (!hls) {
|
16574
|
+
return;
|
16575
|
+
}
|
16576
|
+
super.unregisterListeners();
|
15695
16577
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
15696
16578
|
hls.off(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
|
15697
16579
|
hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
15698
16580
|
hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
|
15699
|
-
hls.off(Events.ERROR, this.onError, this);
|
15700
16581
|
hls.off(Events.BUFFER_RESET, this.onBufferReset, this);
|
15701
16582
|
hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
15702
16583
|
hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
@@ -15839,7 +16720,9 @@ class AudioStreamController extends BaseStreamController {
|
|
15839
16720
|
this.fragmentTracker.removeFragment(waitingData.frag);
|
15840
16721
|
this.waitingData = null;
|
15841
16722
|
this.waitingVideoCC = -1;
|
15842
|
-
this.state
|
16723
|
+
if (this.state !== State.STOPPED) {
|
16724
|
+
this.state = State.IDLE;
|
16725
|
+
}
|
15843
16726
|
}
|
15844
16727
|
}
|
15845
16728
|
resetLoadingState() {
|
@@ -15865,12 +16748,13 @@ class AudioStreamController extends BaseStreamController {
|
|
15865
16748
|
} = this;
|
15866
16749
|
const config = hls.config;
|
15867
16750
|
|
15868
|
-
// 1. if
|
16751
|
+
// 1. if buffering is suspended
|
16752
|
+
// 2. if video not attached AND
|
15869
16753
|
// start fragment already requested OR start frag prefetch not enabled
|
15870
|
-
//
|
16754
|
+
// 3. if tracks or track not loaded and selected
|
15871
16755
|
// then exit loop
|
15872
16756
|
// => if media not attached but start frag prefetch is enabled and start frag not requested yet, we will not exit loop
|
15873
|
-
if (!media && (this.startFragRequested || !config.startFragPrefetch) || !(levels != null && levels[trackId])) {
|
16757
|
+
if (!this.buffering || !media && (this.startFragRequested || !config.startFragPrefetch) || !(levels != null && levels[trackId])) {
|
15874
16758
|
return;
|
15875
16759
|
}
|
15876
16760
|
const levelInfo = levels[trackId];
|
@@ -15899,9 +16783,8 @@ class AudioStreamController extends BaseStreamController {
|
|
15899
16783
|
this.state = State.ENDED;
|
15900
16784
|
return;
|
15901
16785
|
}
|
15902
|
-
const mainBufferInfo = this.getFwdBufferInfo(this.videoBuffer ? this.videoBuffer : this.media, PlaylistLevelType.MAIN);
|
15903
16786
|
const bufferLen = bufferInfo.len;
|
15904
|
-
const maxBufLen =
|
16787
|
+
const maxBufLen = hls.maxBufferLength;
|
15905
16788
|
const fragments = trackDetails.fragments;
|
15906
16789
|
const start = fragments[0].start;
|
15907
16790
|
let targetBufferTime = this.flushing ? this.getLoadPosition() : bufferInfo.end;
|
@@ -15936,32 +16819,25 @@ class AudioStreamController extends BaseStreamController {
|
|
15936
16819
|
this.bufferFlushed = true;
|
15937
16820
|
return;
|
15938
16821
|
}
|
15939
|
-
|
15940
|
-
|
15941
|
-
|
15942
|
-
|
15943
|
-
|
15944
|
-
|
15945
|
-
|
15946
|
-
|
15947
|
-
|
15948
|
-
|
15949
|
-
|
15950
|
-
|
15951
|
-
|
15952
|
-
|
15953
|
-
|
16822
|
+
if (!trackDetails.live || targetBufferTime < this.hls.liveSyncPosition) {
|
16823
|
+
// Request audio segments up to one fragment ahead of main buffer
|
16824
|
+
const mainBufferInfo = this.getFwdBufferInfo(this.videoBuffer ? this.videoBuffer : this.media, PlaylistLevelType.MAIN);
|
16825
|
+
const atBufferSyncLimit = !!mainBufferInfo && frag.start > mainBufferInfo.end + frag.duration;
|
16826
|
+
if (atBufferSyncLimit) {
|
16827
|
+
// Check fragment-tracker for main fragments since GAP segments do not show up in bufferInfo
|
16828
|
+
const mainFrag = this.fragmentTracker.getFragAtPos(frag.start, PlaylistLevelType.MAIN);
|
16829
|
+
if (mainFrag === null) {
|
16830
|
+
return;
|
16831
|
+
}
|
16832
|
+
// Bridge gaps in main buffer (also prevents loop loading at gaps)
|
16833
|
+
atGap || (atGap = !!mainFrag.gap || mainBufferInfo.len === 0);
|
16834
|
+
if (!atGap || bufferInfo.nextStart && bufferInfo.nextStart < mainFrag.end) {
|
16835
|
+
return;
|
16836
|
+
}
|
15954
16837
|
}
|
15955
16838
|
}
|
15956
16839
|
this.loadFragment(frag, levelInfo, targetBufferTime);
|
15957
16840
|
}
|
15958
|
-
getMaxBufferLength(mainBufferLength) {
|
15959
|
-
const maxConfigBuffer = super.getMaxBufferLength();
|
15960
|
-
if (!mainBufferLength) {
|
15961
|
-
return maxConfigBuffer;
|
15962
|
-
}
|
15963
|
-
return Math.min(Math.max(maxConfigBuffer, mainBufferLength), this.config.maxMaxBufferLength);
|
15964
|
-
}
|
15965
16841
|
onMediaDetaching() {
|
15966
16842
|
this.videoBuffer = null;
|
15967
16843
|
this.bufferFlushed = this.flushing = false;
|
@@ -15986,26 +16862,25 @@ class AudioStreamController extends BaseStreamController {
|
|
15986
16862
|
this.removeUnbufferedFrags(fragCurrent.start);
|
15987
16863
|
}
|
15988
16864
|
this.resetLoadingState();
|
15989
|
-
// destroy useless transmuxer when switching audio to main
|
15990
|
-
if (!altAudio) {
|
15991
|
-
this.resetTransmuxer();
|
15992
|
-
} else {
|
15993
|
-
// switching to audio track, start timer if not already started
|
15994
|
-
this.setInterval(TICK_INTERVAL$2);
|
15995
|
-
}
|
15996
16865
|
|
15997
16866
|
// should we switch tracks ?
|
15998
16867
|
if (altAudio) {
|
15999
16868
|
this.switchingTrack = data;
|
16000
16869
|
// main audio track are handled by stream-controller, just do something if switching to alt audio track
|
16001
|
-
this.state = State.IDLE;
|
16002
16870
|
this.flushAudioIfNeeded(data);
|
16871
|
+
if (this.state !== State.STOPPED) {
|
16872
|
+
// switching to audio track, start timer if not already started
|
16873
|
+
this.setInterval(TICK_INTERVAL$2);
|
16874
|
+
this.state = State.IDLE;
|
16875
|
+
this.tick();
|
16876
|
+
}
|
16003
16877
|
} else {
|
16878
|
+
// destroy useless transmuxer when switching audio to main
|
16879
|
+
this.resetTransmuxer();
|
16004
16880
|
this.switchingTrack = null;
|
16005
16881
|
this.bufferedTrack = data;
|
16006
|
-
this.
|
16882
|
+
this.clearInterval();
|
16007
16883
|
}
|
16008
|
-
this.tick();
|
16009
16884
|
}
|
16010
16885
|
onManifestLoading() {
|
16011
16886
|
this.fragmentTracker.removeAllFragments();
|
@@ -16428,7 +17303,7 @@ class AudioStreamController extends BaseStreamController {
|
|
16428
17303
|
|
16429
17304
|
class AudioTrackController extends BasePlaylistController {
|
16430
17305
|
constructor(hls) {
|
16431
|
-
super(hls, '
|
17306
|
+
super(hls, 'audio-track-controller');
|
16432
17307
|
this.tracks = [];
|
16433
17308
|
this.groupIds = null;
|
16434
17309
|
this.tracksInGroup = [];
|
@@ -16747,26 +17622,23 @@ const TICK_INTERVAL$1 = 500; // how often to tick in ms
|
|
16747
17622
|
|
16748
17623
|
class SubtitleStreamController extends BaseStreamController {
|
16749
17624
|
constructor(hls, fragmentTracker, keyLoader) {
|
16750
|
-
super(hls, fragmentTracker, keyLoader, '
|
17625
|
+
super(hls, fragmentTracker, keyLoader, 'subtitle-stream-controller', PlaylistLevelType.SUBTITLE);
|
16751
17626
|
this.currentTrackId = -1;
|
16752
17627
|
this.tracksBuffered = [];
|
16753
17628
|
this.mainDetails = null;
|
16754
|
-
this.
|
17629
|
+
this.registerListeners();
|
16755
17630
|
}
|
16756
17631
|
onHandlerDestroying() {
|
16757
|
-
this.
|
17632
|
+
this.unregisterListeners();
|
16758
17633
|
super.onHandlerDestroying();
|
16759
17634
|
this.mainDetails = null;
|
16760
17635
|
}
|
16761
|
-
|
17636
|
+
registerListeners() {
|
17637
|
+
super.registerListeners();
|
16762
17638
|
const {
|
16763
17639
|
hls
|
16764
17640
|
} = this;
|
16765
|
-
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
16766
|
-
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
16767
|
-
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
16768
17641
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
16769
|
-
hls.on(Events.ERROR, this.onError, this);
|
16770
17642
|
hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
|
16771
17643
|
hls.on(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
|
16772
17644
|
hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
|
@@ -16774,15 +17646,12 @@ class SubtitleStreamController extends BaseStreamController {
|
|
16774
17646
|
hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
16775
17647
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
16776
17648
|
}
|
16777
|
-
|
17649
|
+
unregisterListeners() {
|
17650
|
+
super.unregisterListeners();
|
16778
17651
|
const {
|
16779
17652
|
hls
|
16780
17653
|
} = this;
|
16781
|
-
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
16782
|
-
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
16783
|
-
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
16784
17654
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
16785
|
-
hls.off(Events.ERROR, this.onError, this);
|
16786
17655
|
hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
|
16787
17656
|
hls.off(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
|
16788
17657
|
hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
|
@@ -16928,7 +17797,7 @@ class SubtitleStreamController extends BaseStreamController {
|
|
16928
17797
|
} else {
|
16929
17798
|
this.mediaBuffer = null;
|
16930
17799
|
}
|
16931
|
-
if (currentTrack) {
|
17800
|
+
if (currentTrack && this.state !== State.STOPPED) {
|
16932
17801
|
this.setInterval(TICK_INTERVAL$1);
|
16933
17802
|
}
|
16934
17803
|
}
|
@@ -17009,10 +17878,10 @@ class SubtitleStreamController extends BaseStreamController {
|
|
17009
17878
|
return;
|
17010
17879
|
}
|
17011
17880
|
// check to see if the payload needs to be decrypted
|
17012
|
-
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method
|
17881
|
+
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
|
17013
17882
|
const startTime = performance.now();
|
17014
17883
|
// decrypt the subtitles
|
17015
|
-
this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
|
17884
|
+
this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch(err => {
|
17016
17885
|
hls.trigger(Events.ERROR, {
|
17017
17886
|
type: ErrorTypes.MEDIA_ERROR,
|
17018
17887
|
details: ErrorDetails.FRAG_DECRYPT_ERROR,
|
@@ -17061,9 +17930,8 @@ class SubtitleStreamController extends BaseStreamController {
|
|
17061
17930
|
end: targetBufferTime,
|
17062
17931
|
len: bufferLen
|
17063
17932
|
} = bufferedInfo;
|
17064
|
-
const mainBufferInfo = this.getFwdBufferInfo(this.media, PlaylistLevelType.MAIN);
|
17065
17933
|
const trackDetails = track.details;
|
17066
|
-
const maxBufLen = this.
|
17934
|
+
const maxBufLen = this.hls.maxBufferLength + trackDetails.levelTargetDuration;
|
17067
17935
|
if (bufferLen > maxBufLen) {
|
17068
17936
|
return;
|
17069
17937
|
}
|
@@ -17100,13 +17968,6 @@ class SubtitleStreamController extends BaseStreamController {
|
|
17100
17968
|
}
|
17101
17969
|
}
|
17102
17970
|
}
|
17103
|
-
getMaxBufferLength(mainBufferLength) {
|
17104
|
-
const maxConfigBuffer = super.getMaxBufferLength();
|
17105
|
-
if (!mainBufferLength) {
|
17106
|
-
return maxConfigBuffer;
|
17107
|
-
}
|
17108
|
-
return Math.max(maxConfigBuffer, mainBufferLength);
|
17109
|
-
}
|
17110
17971
|
loadFragment(frag, level, targetBufferTime) {
|
17111
17972
|
this.fragCurrent = frag;
|
17112
17973
|
if (frag.sn === 'initSegment') {
|
@@ -17146,7 +18007,7 @@ class BufferableInstance {
|
|
17146
18007
|
|
17147
18008
|
class SubtitleTrackController extends BasePlaylistController {
|
17148
18009
|
constructor(hls) {
|
17149
|
-
super(hls, '
|
18010
|
+
super(hls, 'subtitle-track-controller');
|
17150
18011
|
this.media = null;
|
17151
18012
|
this.tracks = [];
|
17152
18013
|
this.groupIds = null;
|
@@ -17155,10 +18016,10 @@ class SubtitleTrackController extends BasePlaylistController {
|
|
17155
18016
|
this.currentTrack = null;
|
17156
18017
|
this.selectDefaultTrack = true;
|
17157
18018
|
this.queuedDefaultTrack = -1;
|
17158
|
-
this.asyncPollTrackChange = () => this.pollTrackChange(0);
|
17159
18019
|
this.useTextTrackPolling = false;
|
17160
18020
|
this.subtitlePollingInterval = -1;
|
17161
18021
|
this._subtitleDisplay = true;
|
18022
|
+
this.asyncPollTrackChange = () => this.pollTrackChange(0);
|
17162
18023
|
this.onTextTracksChanged = () => {
|
17163
18024
|
if (!this.useTextTrackPolling) {
|
17164
18025
|
self.clearInterval(this.subtitlePollingInterval);
|
@@ -17192,6 +18053,7 @@ class SubtitleTrackController extends BasePlaylistController {
|
|
17192
18053
|
this.tracks.length = 0;
|
17193
18054
|
this.tracksInGroup.length = 0;
|
17194
18055
|
this.currentTrack = null;
|
18056
|
+
// @ts-ignore
|
17195
18057
|
this.onTextTracksChanged = this.asyncPollTrackChange = null;
|
17196
18058
|
super.destroy();
|
17197
18059
|
}
|
@@ -17603,24 +18465,22 @@ class BufferOperationQueue {
|
|
17603
18465
|
this.executeNext(type);
|
17604
18466
|
}
|
17605
18467
|
}
|
17606
|
-
insertAbort(operation, type) {
|
17607
|
-
const queue = this.queues[type];
|
17608
|
-
queue.unshift(operation);
|
17609
|
-
this.executeNext(type);
|
17610
|
-
}
|
17611
18468
|
appendBlocker(type) {
|
17612
|
-
|
17613
|
-
|
17614
|
-
|
18469
|
+
return new Promise(resolve => {
|
18470
|
+
const operation = {
|
18471
|
+
execute: resolve,
|
18472
|
+
onStart: () => {},
|
18473
|
+
onComplete: () => {},
|
18474
|
+
onError: () => {}
|
18475
|
+
};
|
18476
|
+
this.append(operation, type);
|
17615
18477
|
});
|
17616
|
-
|
17617
|
-
|
17618
|
-
|
17619
|
-
|
17620
|
-
|
17621
|
-
}
|
17622
|
-
this.append(operation, type);
|
17623
|
-
return promise;
|
18478
|
+
}
|
18479
|
+
unblockAudio(op) {
|
18480
|
+
const queue = this.queues.audio;
|
18481
|
+
if (queue[0] === op) {
|
18482
|
+
this.shiftAndExecuteNext('audio');
|
18483
|
+
}
|
17624
18484
|
}
|
17625
18485
|
executeNext(type) {
|
17626
18486
|
const queue = this.queues[type];
|
@@ -17652,8 +18512,9 @@ class BufferOperationQueue {
|
|
17652
18512
|
}
|
17653
18513
|
|
17654
18514
|
const VIDEO_CODEC_PROFILE_REPLACE = /(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\.[^.,]+)+/;
|
17655
|
-
class BufferController {
|
17656
|
-
constructor(hls) {
|
18515
|
+
class BufferController extends Logger {
|
18516
|
+
constructor(hls, fragmentTracker) {
|
18517
|
+
super('buffer-controller', hls.logger);
|
17657
18518
|
// The level details used to determine duration, target-duration and live
|
17658
18519
|
this.details = null;
|
17659
18520
|
// cache the self generated object url to detect hijack of video tag
|
@@ -17663,6 +18524,7 @@ class BufferController {
|
|
17663
18524
|
// References to event listeners for each SourceBuffer, so that they can be referenced for event removal
|
17664
18525
|
this.listeners = void 0;
|
17665
18526
|
this.hls = void 0;
|
18527
|
+
this.fragmentTracker = void 0;
|
17666
18528
|
// The number of BUFFER_CODEC events received before any sourceBuffers are created
|
17667
18529
|
this.bufferCodecEventsExpected = 0;
|
17668
18530
|
// The total number of BUFFER_CODEC events received
|
@@ -17673,6 +18535,10 @@ class BufferController {
|
|
17673
18535
|
this.mediaSource = null;
|
17674
18536
|
// Last MP3 audio chunk appended
|
17675
18537
|
this.lastMpegAudioChunk = null;
|
18538
|
+
// Audio fragment blocked from appending until corresponding video appends or context changes
|
18539
|
+
this.blockedAudioAppend = null;
|
18540
|
+
// Keep track of video append position for unblocking audio
|
18541
|
+
this.lastVideoAppendEnd = 0;
|
17676
18542
|
this.appendSource = void 0;
|
17677
18543
|
// counters
|
17678
18544
|
this.appendErrors = {
|
@@ -17683,9 +18549,6 @@ class BufferController {
|
|
17683
18549
|
this.tracks = {};
|
17684
18550
|
this.pendingTracks = {};
|
17685
18551
|
this.sourceBuffer = void 0;
|
17686
|
-
this.log = void 0;
|
17687
|
-
this.warn = void 0;
|
17688
|
-
this.error = void 0;
|
17689
18552
|
this._onEndStreaming = event => {
|
17690
18553
|
if (!this.hls) {
|
17691
18554
|
return;
|
@@ -17707,7 +18570,10 @@ class BufferController {
|
|
17707
18570
|
this.log('Media source opened');
|
17708
18571
|
if (media) {
|
17709
18572
|
media.removeEventListener('emptied', this._onMediaEmptied);
|
17710
|
-
this.
|
18573
|
+
const durationAndRange = this.getDurationAndRange();
|
18574
|
+
if (durationAndRange) {
|
18575
|
+
this.updateMediaSource(durationAndRange);
|
18576
|
+
}
|
17711
18577
|
this.hls.trigger(Events.MEDIA_ATTACHED, {
|
17712
18578
|
media,
|
17713
18579
|
mediaSource: mediaSource
|
@@ -17731,15 +18597,12 @@ class BufferController {
|
|
17731
18597
|
_objectUrl
|
17732
18598
|
} = this;
|
17733
18599
|
if (mediaSrc !== _objectUrl) {
|
17734
|
-
|
18600
|
+
this.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
|
17735
18601
|
}
|
17736
18602
|
};
|
17737
18603
|
this.hls = hls;
|
17738
|
-
|
18604
|
+
this.fragmentTracker = fragmentTracker;
|
17739
18605
|
this.appendSource = hls.config.preferManagedMediaSource && typeof self !== 'undefined' && self.ManagedMediaSource;
|
17740
|
-
this.log = logger.log.bind(logger, logPrefix);
|
17741
|
-
this.warn = logger.warn.bind(logger, logPrefix);
|
17742
|
-
this.error = logger.error.bind(logger, logPrefix);
|
17743
18606
|
this._initSourceBuffer();
|
17744
18607
|
this.registerListeners();
|
17745
18608
|
}
|
@@ -17751,7 +18614,13 @@ class BufferController {
|
|
17751
18614
|
this.details = null;
|
17752
18615
|
this.lastMpegAudioChunk = null;
|
17753
18616
|
// @ts-ignore
|
17754
|
-
this.hls = null;
|
18617
|
+
this.hls = this.fragmentTracker = null;
|
18618
|
+
// @ts-ignore
|
18619
|
+
this._onMediaSourceOpen = this._onMediaSourceClose = null;
|
18620
|
+
// @ts-ignore
|
18621
|
+
this._onMediaSourceEnded = null;
|
18622
|
+
// @ts-ignore
|
18623
|
+
this._onStartStreaming = this._onEndStreaming = null;
|
17755
18624
|
}
|
17756
18625
|
registerListeners() {
|
17757
18626
|
const {
|
@@ -17801,6 +18670,8 @@ class BufferController {
|
|
17801
18670
|
audiovideo: 0
|
17802
18671
|
};
|
17803
18672
|
this.lastMpegAudioChunk = null;
|
18673
|
+
this.blockedAudioAppend = null;
|
18674
|
+
this.lastVideoAppendEnd = 0;
|
17804
18675
|
}
|
17805
18676
|
onManifestLoading() {
|
17806
18677
|
this.bufferCodecEventsExpected = this._bufferCodecEventsTotal = 0;
|
@@ -17918,6 +18789,7 @@ class BufferController {
|
|
17918
18789
|
this.resetBuffer(type);
|
17919
18790
|
});
|
17920
18791
|
this._initSourceBuffer();
|
18792
|
+
this.hls.resumeBuffering();
|
17921
18793
|
}
|
17922
18794
|
resetBuffer(type) {
|
17923
18795
|
const sb = this.sourceBuffer[type];
|
@@ -17941,9 +18813,10 @@ class BufferController {
|
|
17941
18813
|
const trackNames = Object.keys(data);
|
17942
18814
|
trackNames.forEach(trackName => {
|
17943
18815
|
if (sourceBufferCount) {
|
18816
|
+
var _track$buffer;
|
17944
18817
|
// check if SourceBuffer codec needs to change
|
17945
18818
|
const track = this.tracks[trackName];
|
17946
|
-
if (track && typeof track.buffer.changeType === 'function') {
|
18819
|
+
if (track && typeof ((_track$buffer = track.buffer) == null ? void 0 : _track$buffer.changeType) === 'function') {
|
17947
18820
|
var _trackCodec;
|
17948
18821
|
const {
|
17949
18822
|
id,
|
@@ -18013,20 +18886,54 @@ class BufferController {
|
|
18013
18886
|
};
|
18014
18887
|
operationQueue.append(operation, type, !!this.pendingTracks[type]);
|
18015
18888
|
}
|
18889
|
+
blockAudio(partOrFrag) {
|
18890
|
+
var _this$fragmentTracker;
|
18891
|
+
const pStart = partOrFrag.start;
|
18892
|
+
const pTime = pStart + partOrFrag.duration * 0.05;
|
18893
|
+
const atGap = ((_this$fragmentTracker = this.fragmentTracker.getAppendedFrag(pStart, PlaylistLevelType.MAIN)) == null ? void 0 : _this$fragmentTracker.gap) === true;
|
18894
|
+
if (atGap) {
|
18895
|
+
return;
|
18896
|
+
}
|
18897
|
+
const op = {
|
18898
|
+
execute: () => {
|
18899
|
+
var _this$fragmentTracker2;
|
18900
|
+
if (this.lastVideoAppendEnd > pTime || this.sourceBuffer.video && BufferHelper.isBuffered(this.sourceBuffer.video, pTime) || ((_this$fragmentTracker2 = this.fragmentTracker.getAppendedFrag(pTime, PlaylistLevelType.MAIN)) == null ? void 0 : _this$fragmentTracker2.gap) === true) {
|
18901
|
+
this.blockedAudioAppend = null;
|
18902
|
+
this.operationQueue.shiftAndExecuteNext('audio');
|
18903
|
+
}
|
18904
|
+
},
|
18905
|
+
onStart: () => {},
|
18906
|
+
onComplete: () => {},
|
18907
|
+
onError: () => {}
|
18908
|
+
};
|
18909
|
+
this.blockedAudioAppend = {
|
18910
|
+
op,
|
18911
|
+
frag: partOrFrag
|
18912
|
+
};
|
18913
|
+
this.operationQueue.append(op, 'audio', true);
|
18914
|
+
}
|
18915
|
+
unblockAudio() {
|
18916
|
+
const blockedAudioAppend = this.blockedAudioAppend;
|
18917
|
+
if (blockedAudioAppend) {
|
18918
|
+
this.blockedAudioAppend = null;
|
18919
|
+
this.operationQueue.unblockAudio(blockedAudioAppend.op);
|
18920
|
+
}
|
18921
|
+
}
|
18016
18922
|
onBufferAppending(event, eventData) {
|
18017
18923
|
const {
|
18018
|
-
hls,
|
18019
18924
|
operationQueue,
|
18020
18925
|
tracks
|
18021
18926
|
} = this;
|
18022
18927
|
const {
|
18023
18928
|
data,
|
18024
18929
|
type,
|
18930
|
+
parent,
|
18025
18931
|
frag,
|
18026
18932
|
part,
|
18027
18933
|
chunkMeta
|
18028
18934
|
} = eventData;
|
18029
18935
|
const chunkStats = chunkMeta.buffering[type];
|
18936
|
+
const sn = frag.sn;
|
18030
18937
|
const bufferAppendingStart = self.performance.now();
|
18031
18938
|
chunkStats.start = bufferAppendingStart;
|
18032
18939
|
const fragBuffering = frag.stats.buffering;
|
@@ -18049,7 +18956,36 @@ class BufferController {
|
|
18049
18956
|
checkTimestampOffset = !this.lastMpegAudioChunk || chunkMeta.id === 1 || this.lastMpegAudioChunk.sn !== chunkMeta.sn;
|
18050
18957
|
this.lastMpegAudioChunk = chunkMeta;
|
18051
18958
|
}
|
18052
|
-
|
18959
|
+
|
18960
|
+
// Block audio append until overlapping video append
|
18961
|
+
const videoSb = this.sourceBuffer.video;
|
18962
|
+
if (videoSb && sn !== 'initSegment') {
|
18963
|
+
const partOrFrag = part || frag;
|
18964
|
+
const blockedAudioAppend = this.blockedAudioAppend;
|
18965
|
+
if (type === 'audio' && parent !== 'main' && !this.blockedAudioAppend) {
|
18966
|
+
const pStart = partOrFrag.start;
|
18967
|
+
const pTime = pStart + partOrFrag.duration * 0.05;
|
18968
|
+
const vbuffered = videoSb.buffered;
|
18969
|
+
const vappending = this.operationQueue.current('video');
|
18970
|
+
if (!vbuffered.length && !vappending) {
|
18971
|
+
// wait for video before appending audio
|
18972
|
+
this.blockAudio(partOrFrag);
|
18973
|
+
} else if (!vappending && !BufferHelper.isBuffered(videoSb, pTime) && this.lastVideoAppendEnd < pTime) {
|
18974
|
+
// audio is ahead of video
|
18975
|
+
this.blockAudio(partOrFrag);
|
18976
|
+
}
|
18977
|
+
} else if (type === 'video') {
|
18978
|
+
const videoAppendEnd = partOrFrag.end;
|
18979
|
+
if (blockedAudioAppend) {
|
18980
|
+
const audioStart = blockedAudioAppend.frag.start;
|
18981
|
+
if (videoAppendEnd > audioStart || videoAppendEnd < this.lastVideoAppendEnd || BufferHelper.isBuffered(videoSb, audioStart)) {
|
18982
|
+
this.unblockAudio();
|
18983
|
+
}
|
18984
|
+
}
|
18985
|
+
this.lastVideoAppendEnd = videoAppendEnd;
|
18986
|
+
}
|
18987
|
+
}
|
18988
|
+
const fragStart = (part || frag).start;
|
18053
18989
|
const operation = {
|
18054
18990
|
execute: () => {
|
18055
18991
|
chunkStats.executeStart = self.performance.now();
|
@@ -18058,7 +18994,7 @@ class BufferController {
|
|
18058
18994
|
if (sb) {
|
18059
18995
|
const delta = fragStart - sb.timestampOffset;
|
18060
18996
|
if (Math.abs(delta) >= 0.1) {
|
18061
|
-
this.log(`Updating audio SourceBuffer timestampOffset to ${fragStart} (delta: ${delta}) sn: ${
|
18997
|
+
this.log(`Updating audio SourceBuffer timestampOffset to ${fragStart} (delta: ${delta}) sn: ${sn})`);
|
18062
18998
|
sb.timestampOffset = fragStart;
|
18063
18999
|
}
|
18064
19000
|
}
|
@@ -18125,22 +19061,21 @@ class BufferController {
|
|
18125
19061
|
/* with UHD content, we could get loop of quota exceeded error until
|
18126
19062
|
browser is able to evict some data from sourcebuffer. Retrying can help recover.
|
18127
19063
|
*/
|
18128
|
-
this.warn(`Failed ${appendErrorCount}/${hls.config.appendErrorMaxRetry} times to append segment in "${type}" sourceBuffer`);
|
18129
|
-
if (appendErrorCount >= hls.config.appendErrorMaxRetry) {
|
19064
|
+
this.warn(`Failed ${appendErrorCount}/${this.hls.config.appendErrorMaxRetry} times to append segment in "${type}" sourceBuffer`);
|
19065
|
+
if (appendErrorCount >= this.hls.config.appendErrorMaxRetry) {
|
18130
19066
|
event.fatal = true;
|
18131
19067
|
}
|
18132
19068
|
}
|
18133
|
-
hls.trigger(Events.ERROR, event);
|
19069
|
+
this.hls.trigger(Events.ERROR, event);
|
18134
19070
|
}
|
18135
19071
|
};
|
18136
19072
|
operationQueue.append(operation, type, !!this.pendingTracks[type]);
|
18137
19073
|
}
|
18138
|
-
|
18139
|
-
|
18140
|
-
|
18141
|
-
|
18142
|
-
|
18143
|
-
execute: this.removeExecutor.bind(this, type, data.startOffset, data.endOffset),
|
19074
|
+
getFlushOp(type, start, end) {
|
19075
|
+
return {
|
19076
|
+
execute: () => {
|
19077
|
+
this.removeExecutor(type, start, end);
|
19078
|
+
},
|
18144
19079
|
onStart: () => {
|
18145
19080
|
// logger.debug(`[buffer-controller]: Started flushing ${data.startOffset} -> ${data.endOffset} for ${type} Source Buffer`);
|
18146
19081
|
},
|
@@ -18153,12 +19088,22 @@ class BufferController {
|
|
18153
19088
|
onError: error => {
|
18154
19089
|
this.warn(`Failed to remove from ${type} SourceBuffer`, error);
|
18155
19090
|
}
|
18156
|
-
}
|
18157
|
-
|
18158
|
-
|
19091
|
+
};
|
19092
|
+
}
|
19093
|
+
onBufferFlushing(event, data) {
|
19094
|
+
const {
|
19095
|
+
operationQueue
|
19096
|
+
} = this;
|
19097
|
+
const {
|
19098
|
+
type,
|
19099
|
+
startOffset,
|
19100
|
+
endOffset
|
19101
|
+
} = data;
|
19102
|
+
if (type) {
|
19103
|
+
operationQueue.append(this.getFlushOp(type, startOffset, endOffset), type);
|
18159
19104
|
} else {
|
18160
|
-
this.getSourceBufferTypes().forEach(
|
18161
|
-
operationQueue.append(
|
19105
|
+
this.getSourceBufferTypes().forEach(sbType => {
|
19106
|
+
operationQueue.append(this.getFlushOp(sbType, startOffset, endOffset), sbType);
|
18162
19107
|
});
|
18163
19108
|
}
|
18164
19109
|
}
|
@@ -18205,6 +19150,9 @@ class BufferController {
|
|
18205
19150
|
// on BUFFER_EOS mark matching sourcebuffer(s) as ended and trigger checkEos()
|
18206
19151
|
// an undefined data.type will mark all buffers as EOS.
|
18207
19152
|
onBufferEos(event, data) {
|
19153
|
+
if (data.type === 'video') {
|
19154
|
+
this.unblockAudio();
|
19155
|
+
}
|
18208
19156
|
const ended = this.getSourceBufferTypes().reduce((acc, type) => {
|
18209
19157
|
const sb = this.sourceBuffer[type];
|
18210
19158
|
if (sb && (!data.type || data.type === type)) {
|
@@ -18247,10 +19195,14 @@ class BufferController {
|
|
18247
19195
|
return;
|
18248
19196
|
}
|
18249
19197
|
this.details = details;
|
19198
|
+
const durationAndRange = this.getDurationAndRange();
|
19199
|
+
if (!durationAndRange) {
|
19200
|
+
return;
|
19201
|
+
}
|
18250
19202
|
if (this.getSourceBufferTypes().length) {
|
18251
|
-
this.blockBuffers(this.
|
19203
|
+
this.blockBuffers(() => this.updateMediaSource(durationAndRange));
|
18252
19204
|
} else {
|
18253
|
-
this.
|
19205
|
+
this.updateMediaSource(durationAndRange);
|
18254
19206
|
}
|
18255
19207
|
}
|
18256
19208
|
trimBuffers() {
|
@@ -18355,9 +19307,9 @@ class BufferController {
|
|
18355
19307
|
* 'liveDurationInfinity` is set to `true`
|
18356
19308
|
* More details: https://github.com/video-dev/hls.js/issues/355
|
18357
19309
|
*/
|
18358
|
-
|
19310
|
+
getDurationAndRange() {
|
18359
19311
|
if (!this.details || !this.media || !this.mediaSource || this.mediaSource.readyState !== 'open') {
|
18360
|
-
return;
|
19312
|
+
return null;
|
18361
19313
|
}
|
18362
19314
|
const {
|
18363
19315
|
details,
|
@@ -18371,25 +19323,41 @@ class BufferController {
|
|
18371
19323
|
if (details.live && hls.config.liveDurationInfinity) {
|
18372
19324
|
// Override duration to Infinity
|
18373
19325
|
mediaSource.duration = Infinity;
|
18374
|
-
|
19326
|
+
const len = details.fragments.length;
|
19327
|
+
if (len && details.live && !!mediaSource.setLiveSeekableRange) {
|
19328
|
+
const start = Math.max(0, details.fragments[0].start);
|
19329
|
+
const end = Math.max(start, start + details.totalduration);
|
19330
|
+
return {
|
19331
|
+
duration: Infinity,
|
19332
|
+
start,
|
19333
|
+
end
|
19334
|
+
};
|
19335
|
+
}
|
19336
|
+
return {
|
19337
|
+
duration: Infinity
|
19338
|
+
};
|
18375
19339
|
} else if (levelDuration > msDuration && levelDuration > mediaDuration || !isFiniteNumber(mediaDuration)) {
|
18376
|
-
|
18377
|
-
|
18378
|
-
|
18379
|
-
// flushing already buffered portion when switching between quality level
|
18380
|
-
this.log(`Updating Media Source duration to ${levelDuration.toFixed(3)}`);
|
18381
|
-
mediaSource.duration = levelDuration;
|
19340
|
+
return {
|
19341
|
+
duration: levelDuration
|
19342
|
+
};
|
18382
19343
|
}
|
19344
|
+
return null;
|
18383
19345
|
}
|
18384
|
-
|
18385
|
-
|
18386
|
-
|
18387
|
-
|
18388
|
-
|
18389
|
-
|
18390
|
-
|
18391
|
-
|
18392
|
-
|
19346
|
+
updateMediaSource({
|
19347
|
+
duration,
|
19348
|
+
start,
|
19349
|
+
end
|
19350
|
+
}) {
|
19351
|
+
if (!this.media || !this.mediaSource || this.mediaSource.readyState !== 'open') {
|
19352
|
+
return;
|
19353
|
+
}
|
19354
|
+
if (isFiniteNumber(duration)) {
|
19355
|
+
this.log(`Updating Media Source duration to ${duration.toFixed(3)}`);
|
19356
|
+
}
|
19357
|
+
this.mediaSource.duration = duration;
|
19358
|
+
if (start !== undefined && end !== undefined) {
|
19359
|
+
this.log(`Media Source duration is set to ${this.mediaSource.duration}. Setting seekable range to ${start}-${end}.`);
|
19360
|
+
this.mediaSource.setLiveSeekableRange(start, end);
|
18393
19361
|
}
|
18394
19362
|
}
|
18395
19363
|
checkPendingTracks() {
|
@@ -18574,6 +19542,7 @@ class BufferController {
|
|
18574
19542
|
}
|
18575
19543
|
return;
|
18576
19544
|
}
|
19545
|
+
sb.ending = false;
|
18577
19546
|
sb.ended = false;
|
18578
19547
|
sb.appendBuffer(data);
|
18579
19548
|
}
|
@@ -18593,10 +19562,14 @@ class BufferController {
|
|
18593
19562
|
|
18594
19563
|
// logger.debug(`[buffer-controller]: Blocking ${buffers} SourceBuffer`);
|
18595
19564
|
const blockingOperations = buffers.map(type => operationQueue.appendBlocker(type));
|
18596
|
-
|
19565
|
+
const audioBlocked = buffers.length > 1 && !!this.blockedAudioAppend;
|
19566
|
+
if (audioBlocked) {
|
19567
|
+
this.unblockAudio();
|
19568
|
+
}
|
19569
|
+
Promise.all(blockingOperations).then(result => {
|
18597
19570
|
// logger.debug(`[buffer-controller]: Blocking operation resolved; unblocking ${buffers} SourceBuffer`);
|
18598
19571
|
onUnblocked();
|
18599
|
-
buffers.forEach(type => {
|
19572
|
+
buffers.forEach((type, i) => {
|
18600
19573
|
const sb = this.sourceBuffer[type];
|
18601
19574
|
// Only cycle the queue if the SB is not updating. There's a bug in Chrome which sets the SB updating flag to
|
18602
19575
|
// true when changing the MediaSource duration (https://bugs.chromium.org/p/chromium/issues/detail?id=959359&can=2&q=mediasource%20duration)
|
@@ -21022,14 +21995,12 @@ class TimelineController {
|
|
21022
21995
|
this.cea608Parser1 = this.cea608Parser2 = undefined;
|
21023
21996
|
}
|
21024
21997
|
initCea608Parsers() {
|
21025
|
-
|
21026
|
-
|
21027
|
-
|
21028
|
-
|
21029
|
-
|
21030
|
-
|
21031
|
-
this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
|
21032
|
-
}
|
21998
|
+
const channel1 = new OutputFilter(this, 'textTrack1');
|
21999
|
+
const channel2 = new OutputFilter(this, 'textTrack2');
|
22000
|
+
const channel3 = new OutputFilter(this, 'textTrack3');
|
22001
|
+
const channel4 = new OutputFilter(this, 'textTrack4');
|
22002
|
+
this.cea608Parser1 = new Cea608Parser(1, channel1, channel2);
|
22003
|
+
this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
|
21033
22004
|
}
|
21034
22005
|
addCues(trackName, startTime, endTime, screen, cueRanges) {
|
21035
22006
|
// skip cues which overlap more than 50% with previously parsed time ranges
|
@@ -21267,7 +22238,7 @@ class TimelineController {
|
|
21267
22238
|
if (inUseTracks != null && inUseTracks.length) {
|
21268
22239
|
const unusedTextTracks = inUseTracks.filter(t => t !== null).map(t => t.label);
|
21269
22240
|
if (unusedTextTracks.length) {
|
21270
|
-
logger.warn(`Media element contains unused subtitle tracks: ${unusedTextTracks.join(', ')}. Replace media element for each source to clear TextTracks and captions menu.`);
|
22241
|
+
this.hls.logger.warn(`Media element contains unused subtitle tracks: ${unusedTextTracks.join(', ')}. Replace media element for each source to clear TextTracks and captions menu.`);
|
21271
22242
|
}
|
21272
22243
|
}
|
21273
22244
|
} else if (this.tracks.length) {
|
@@ -21312,26 +22283,23 @@ class TimelineController {
|
|
21312
22283
|
return level == null ? void 0 : level.attrs['CLOSED-CAPTIONS'];
|
21313
22284
|
}
|
21314
22285
|
onFragLoading(event, data) {
|
21315
|
-
this.initCea608Parsers();
|
21316
|
-
const {
|
21317
|
-
cea608Parser1,
|
21318
|
-
cea608Parser2,
|
21319
|
-
lastCc,
|
21320
|
-
lastSn,
|
21321
|
-
lastPartIndex
|
21322
|
-
} = this;
|
21323
|
-
if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
|
21324
|
-
return;
|
21325
|
-
}
|
21326
22286
|
// if this frag isn't contiguous, clear the parser so cues with bad start/end times aren't added to the textTrack
|
21327
|
-
if (data.frag.type === PlaylistLevelType.MAIN) {
|
22287
|
+
if (this.enabled && data.frag.type === PlaylistLevelType.MAIN) {
|
21328
22288
|
var _data$part$index, _data$part;
|
22289
|
+
const {
|
22290
|
+
cea608Parser1,
|
22291
|
+
cea608Parser2,
|
22292
|
+
lastSn
|
22293
|
+
} = this;
|
22294
|
+
if (!cea608Parser1 || !cea608Parser2) {
|
22295
|
+
return;
|
22296
|
+
}
|
21329
22297
|
const {
|
21330
22298
|
cc,
|
21331
22299
|
sn
|
21332
22300
|
} = data.frag;
|
21333
|
-
const partIndex = (_data$part$index =
|
21334
|
-
if (!(sn === lastSn + 1 || sn === lastSn && partIndex === lastPartIndex + 1 || cc === lastCc)) {
|
22301
|
+
const partIndex = (_data$part$index = (_data$part = data.part) == null ? void 0 : _data$part.index) != null ? _data$part$index : -1;
|
22302
|
+
if (!(sn === lastSn + 1 || sn === lastSn && partIndex === this.lastPartIndex + 1 || cc === this.lastCc)) {
|
21335
22303
|
cea608Parser1.reset();
|
21336
22304
|
cea608Parser2.reset();
|
21337
22305
|
}
|
@@ -21388,7 +22356,7 @@ class TimelineController {
|
|
21388
22356
|
frag: frag
|
21389
22357
|
});
|
21390
22358
|
}, error => {
|
21391
|
-
logger.log(`Failed to parse IMSC1: ${error}`);
|
22359
|
+
hls.logger.log(`Failed to parse IMSC1: ${error}`);
|
21392
22360
|
hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
|
21393
22361
|
success: false,
|
21394
22362
|
frag: frag,
|
@@ -21429,7 +22397,7 @@ class TimelineController {
|
|
21429
22397
|
this._fallbackToIMSC1(frag, payload);
|
21430
22398
|
}
|
21431
22399
|
// Something went wrong while parsing. Trigger event with success false.
|
21432
|
-
logger.log(`Failed to parse VTT cue: ${error}`);
|
22400
|
+
hls.logger.log(`Failed to parse VTT cue: ${error}`);
|
21433
22401
|
if (missingInitPTS && maxAvCC > frag.cc) {
|
21434
22402
|
return;
|
21435
22403
|
}
|
@@ -21490,12 +22458,7 @@ class TimelineController {
|
|
21490
22458
|
this.captionsTracks = {};
|
21491
22459
|
}
|
21492
22460
|
onFragParsingUserdata(event, data) {
|
21493
|
-
this.
|
21494
|
-
const {
|
21495
|
-
cea608Parser1,
|
21496
|
-
cea608Parser2
|
21497
|
-
} = this;
|
21498
|
-
if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
|
22461
|
+
if (!this.enabled || !this.config.enableCEA708Captions) {
|
21499
22462
|
return;
|
21500
22463
|
}
|
21501
22464
|
const {
|
@@ -21510,9 +22473,12 @@ class TimelineController {
|
|
21510
22473
|
for (let i = 0; i < samples.length; i++) {
|
21511
22474
|
const ccBytes = samples[i].bytes;
|
21512
22475
|
if (ccBytes) {
|
22476
|
+
if (!this.cea608Parser1) {
|
22477
|
+
this.initCea608Parsers();
|
22478
|
+
}
|
21513
22479
|
const ccdatas = this.extractCea608Data(ccBytes);
|
21514
|
-
cea608Parser1.addData(samples[i].pts, ccdatas[0]);
|
21515
|
-
cea608Parser2.addData(samples[i].pts, ccdatas[1]);
|
22480
|
+
this.cea608Parser1.addData(samples[i].pts, ccdatas[0]);
|
22481
|
+
this.cea608Parser2.addData(samples[i].pts, ccdatas[1]);
|
21516
22482
|
}
|
21517
22483
|
}
|
21518
22484
|
}
|
@@ -21708,10 +22674,10 @@ class CapLevelController {
|
|
21708
22674
|
const hls = this.hls;
|
21709
22675
|
const maxLevel = this.getMaxLevel(levels.length - 1);
|
21710
22676
|
if (maxLevel !== this.autoLevelCapping) {
|
21711
|
-
logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
|
22677
|
+
hls.logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
|
21712
22678
|
}
|
21713
22679
|
hls.autoLevelCapping = maxLevel;
|
21714
|
-
if (hls.autoLevelCapping > this.autoLevelCapping && this.streamController) {
|
22680
|
+
if (hls.autoLevelEnabled && hls.autoLevelCapping > this.autoLevelCapping && this.streamController) {
|
21715
22681
|
// if auto level capping has a higher value for the previous one, flush the buffer using nextLevelSwitch
|
21716
22682
|
// usually happen when the user go to the fullscreen mode.
|
21717
22683
|
this.streamController.nextLevelSwitch();
|
@@ -21886,10 +22852,10 @@ class FPSController {
|
|
21886
22852
|
totalDroppedFrames: droppedFrames
|
21887
22853
|
});
|
21888
22854
|
if (droppedFPS > 0) {
|
21889
|
-
// logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
|
22855
|
+
// hls.logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
|
21890
22856
|
if (currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
|
21891
22857
|
let currentLevel = hls.currentLevel;
|
21892
|
-
logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
|
22858
|
+
hls.logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
|
21893
22859
|
if (currentLevel > 0 && (hls.autoLevelCapping === -1 || hls.autoLevelCapping >= currentLevel)) {
|
21894
22860
|
currentLevel = currentLevel - 1;
|
21895
22861
|
hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, {
|
@@ -21921,7 +22887,6 @@ class FPSController {
|
|
21921
22887
|
}
|
21922
22888
|
}
|
21923
22889
|
|
21924
|
-
const LOGGER_PREFIX = '[eme]';
|
21925
22890
|
/**
|
21926
22891
|
* Controller to deal with encrypted media extensions (EME)
|
21927
22892
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/Encrypted_Media_Extensions_API
|
@@ -21929,8 +22894,9 @@ const LOGGER_PREFIX = '[eme]';
|
|
21929
22894
|
* @class
|
21930
22895
|
* @constructor
|
21931
22896
|
*/
|
21932
|
-
class EMEController {
|
22897
|
+
class EMEController extends Logger {
|
21933
22898
|
constructor(hls) {
|
22899
|
+
super('eme', hls.logger);
|
21934
22900
|
this.hls = void 0;
|
21935
22901
|
this.config = void 0;
|
21936
22902
|
this.media = null;
|
@@ -21940,12 +22906,100 @@ class EMEController {
|
|
21940
22906
|
this.mediaKeySessions = [];
|
21941
22907
|
this.keyIdToKeySessionPromise = {};
|
21942
22908
|
this.setMediaKeysQueue = EMEController.CDMCleanupPromise ? [EMEController.CDMCleanupPromise] : [];
|
21943
|
-
this.onMediaEncrypted =
|
21944
|
-
|
21945
|
-
|
21946
|
-
|
21947
|
-
|
21948
|
-
|
22909
|
+
this.onMediaEncrypted = event => {
|
22910
|
+
const {
|
22911
|
+
initDataType,
|
22912
|
+
initData
|
22913
|
+
} = event;
|
22914
|
+
this.debug(`"${event.type}" event: init data type: "${initDataType}"`);
|
22915
|
+
|
22916
|
+
// Ignore event when initData is null
|
22917
|
+
if (initData === null) {
|
22918
|
+
return;
|
22919
|
+
}
|
22920
|
+
let keyId;
|
22921
|
+
let keySystemDomain;
|
22922
|
+
if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) {
|
22923
|
+
// Match sinf keyId to playlist skd://keyId=
|
22924
|
+
const json = bin2str(new Uint8Array(initData));
|
22925
|
+
try {
|
22926
|
+
const sinf = base64Decode(JSON.parse(json).sinf);
|
22927
|
+
const tenc = parseSinf(new Uint8Array(sinf));
|
22928
|
+
if (!tenc) {
|
22929
|
+
return;
|
22930
|
+
}
|
22931
|
+
keyId = tenc.subarray(8, 24);
|
22932
|
+
keySystemDomain = KeySystems.FAIRPLAY;
|
22933
|
+
} catch (error) {
|
22934
|
+
this.warn('Failed to parse sinf "encrypted" event message initData');
|
22935
|
+
return;
|
22936
|
+
}
|
22937
|
+
} else {
|
22938
|
+
// Support clear-lead key-session creation (otherwise depend on playlist keys)
|
22939
|
+
const psshInfo = parsePssh(initData);
|
22940
|
+
if (psshInfo === null) {
|
22941
|
+
return;
|
22942
|
+
}
|
22943
|
+
if (psshInfo.version === 0 && psshInfo.systemId === KeySystemIds.WIDEVINE && psshInfo.data) {
|
22944
|
+
keyId = psshInfo.data.subarray(8, 24);
|
22945
|
+
}
|
22946
|
+
keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
|
22947
|
+
}
|
22948
|
+
if (!keySystemDomain || !keyId) {
|
22949
|
+
return;
|
22950
|
+
}
|
22951
|
+
const keyIdHex = Hex.hexDump(keyId);
|
22952
|
+
const {
|
22953
|
+
keyIdToKeySessionPromise,
|
22954
|
+
mediaKeySessions
|
22955
|
+
} = this;
|
22956
|
+
let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
|
22957
|
+
for (let i = 0; i < mediaKeySessions.length; i++) {
|
22958
|
+
// Match playlist key
|
22959
|
+
const keyContext = mediaKeySessions[i];
|
22960
|
+
const decryptdata = keyContext.decryptdata;
|
22961
|
+
if (decryptdata.pssh || !decryptdata.keyId) {
|
22962
|
+
continue;
|
22963
|
+
}
|
22964
|
+
const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
|
22965
|
+
if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
|
22966
|
+
keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
|
22967
|
+
delete keyIdToKeySessionPromise[oldKeyIdHex];
|
22968
|
+
decryptdata.pssh = new Uint8Array(initData);
|
22969
|
+
decryptdata.keyId = keyId;
|
22970
|
+
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
|
22971
|
+
return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
|
22972
|
+
});
|
22973
|
+
break;
|
22974
|
+
}
|
22975
|
+
}
|
22976
|
+
if (!keySessionContextPromise) {
|
22977
|
+
// Clear-lead key (not encountered in playlist)
|
22978
|
+
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
|
22979
|
+
keySystem,
|
22980
|
+
mediaKeys
|
22981
|
+
}) => {
|
22982
|
+
var _keySystemToKeySystem;
|
22983
|
+
this.throwIfDestroyed();
|
22984
|
+
const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
|
22985
|
+
decryptdata.pssh = new Uint8Array(initData);
|
22986
|
+
decryptdata.keyId = keyId;
|
22987
|
+
return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
|
22988
|
+
this.throwIfDestroyed();
|
22989
|
+
const keySessionContext = this.createMediaKeySessionContext({
|
22990
|
+
decryptdata,
|
22991
|
+
keySystem,
|
22992
|
+
mediaKeys
|
22993
|
+
});
|
22994
|
+
return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
|
22995
|
+
});
|
22996
|
+
});
|
22997
|
+
}
|
22998
|
+
keySessionContextPromise.catch(error => this.handleError(error));
|
22999
|
+
};
|
23000
|
+
this.onWaitingForKey = event => {
|
23001
|
+
this.log(`"${event.type}" event`);
|
23002
|
+
};
|
21949
23003
|
this.hls = hls;
|
21950
23004
|
this.config = hls.config;
|
21951
23005
|
this.registerListeners();
|
@@ -21959,9 +23013,9 @@ class EMEController {
|
|
21959
23013
|
config.licenseXhrSetup = config.licenseResponseCallback = undefined;
|
21960
23014
|
config.drmSystems = config.drmSystemOptions = {};
|
21961
23015
|
// @ts-ignore
|
21962
|
-
this.hls = this.
|
23016
|
+
this.hls = this.config = this.keyIdToKeySessionPromise = null;
|
21963
23017
|
// @ts-ignore
|
21964
|
-
this.
|
23018
|
+
this.onMediaEncrypted = this.onWaitingForKey = null;
|
21965
23019
|
}
|
21966
23020
|
registerListeners() {
|
21967
23021
|
this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
@@ -22225,100 +23279,6 @@ class EMEController {
|
|
22225
23279
|
}
|
22226
23280
|
return this.attemptKeySystemAccess(keySystemsToAttempt);
|
22227
23281
|
}
|
22228
|
-
_onMediaEncrypted(event) {
|
22229
|
-
const {
|
22230
|
-
initDataType,
|
22231
|
-
initData
|
22232
|
-
} = event;
|
22233
|
-
this.debug(`"${event.type}" event: init data type: "${initDataType}"`);
|
22234
|
-
|
22235
|
-
// Ignore event when initData is null
|
22236
|
-
if (initData === null) {
|
22237
|
-
return;
|
22238
|
-
}
|
22239
|
-
let keyId;
|
22240
|
-
let keySystemDomain;
|
22241
|
-
if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) {
|
22242
|
-
// Match sinf keyId to playlist skd://keyId=
|
22243
|
-
const json = bin2str(new Uint8Array(initData));
|
22244
|
-
try {
|
22245
|
-
const sinf = base64Decode(JSON.parse(json).sinf);
|
22246
|
-
const tenc = parseSinf(new Uint8Array(sinf));
|
22247
|
-
if (!tenc) {
|
22248
|
-
return;
|
22249
|
-
}
|
22250
|
-
keyId = tenc.subarray(8, 24);
|
22251
|
-
keySystemDomain = KeySystems.FAIRPLAY;
|
22252
|
-
} catch (error) {
|
22253
|
-
this.warn('Failed to parse sinf "encrypted" event message initData');
|
22254
|
-
return;
|
22255
|
-
}
|
22256
|
-
} else {
|
22257
|
-
// Support clear-lead key-session creation (otherwise depend on playlist keys)
|
22258
|
-
const psshInfo = parsePssh(initData);
|
22259
|
-
if (psshInfo === null) {
|
22260
|
-
return;
|
22261
|
-
}
|
22262
|
-
if (psshInfo.version === 0 && psshInfo.systemId === KeySystemIds.WIDEVINE && psshInfo.data) {
|
22263
|
-
keyId = psshInfo.data.subarray(8, 24);
|
22264
|
-
}
|
22265
|
-
keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
|
22266
|
-
}
|
22267
|
-
if (!keySystemDomain || !keyId) {
|
22268
|
-
return;
|
22269
|
-
}
|
22270
|
-
const keyIdHex = Hex.hexDump(keyId);
|
22271
|
-
const {
|
22272
|
-
keyIdToKeySessionPromise,
|
22273
|
-
mediaKeySessions
|
22274
|
-
} = this;
|
22275
|
-
let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
|
22276
|
-
for (let i = 0; i < mediaKeySessions.length; i++) {
|
22277
|
-
// Match playlist key
|
22278
|
-
const keyContext = mediaKeySessions[i];
|
22279
|
-
const decryptdata = keyContext.decryptdata;
|
22280
|
-
if (decryptdata.pssh || !decryptdata.keyId) {
|
22281
|
-
continue;
|
22282
|
-
}
|
22283
|
-
const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
|
22284
|
-
if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
|
22285
|
-
keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
|
22286
|
-
delete keyIdToKeySessionPromise[oldKeyIdHex];
|
22287
|
-
decryptdata.pssh = new Uint8Array(initData);
|
22288
|
-
decryptdata.keyId = keyId;
|
22289
|
-
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
|
22290
|
-
return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
|
22291
|
-
});
|
22292
|
-
break;
|
22293
|
-
}
|
22294
|
-
}
|
22295
|
-
if (!keySessionContextPromise) {
|
22296
|
-
// Clear-lead key (not encountered in playlist)
|
22297
|
-
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
|
22298
|
-
keySystem,
|
22299
|
-
mediaKeys
|
22300
|
-
}) => {
|
22301
|
-
var _keySystemToKeySystem;
|
22302
|
-
this.throwIfDestroyed();
|
22303
|
-
const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
|
22304
|
-
decryptdata.pssh = new Uint8Array(initData);
|
22305
|
-
decryptdata.keyId = keyId;
|
22306
|
-
return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
|
22307
|
-
this.throwIfDestroyed();
|
22308
|
-
const keySessionContext = this.createMediaKeySessionContext({
|
22309
|
-
decryptdata,
|
22310
|
-
keySystem,
|
22311
|
-
mediaKeys
|
22312
|
-
});
|
22313
|
-
return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
|
22314
|
-
});
|
22315
|
-
});
|
22316
|
-
}
|
22317
|
-
keySessionContextPromise.catch(error => this.handleError(error));
|
22318
|
-
}
|
22319
|
-
_onWaitingForKey(event) {
|
22320
|
-
this.log(`"${event.type}" event`);
|
22321
|
-
}
|
22322
23282
|
attemptSetMediaKeys(keySystem, mediaKeys) {
|
22323
23283
|
const queue = this.setMediaKeysQueue.slice();
|
22324
23284
|
this.log(`Setting media-keys for "${keySystem}"`);
|
@@ -22911,20 +23871,6 @@ class SfItem {
|
|
22911
23871
|
}
|
22912
23872
|
}
|
22913
23873
|
|
22914
|
-
/**
|
22915
|
-
* A class to represent structured field tokens when `Symbol` is not available.
|
22916
|
-
*
|
22917
|
-
* @group Structured Field
|
22918
|
-
*
|
22919
|
-
* @beta
|
22920
|
-
*/
|
22921
|
-
class SfToken {
|
22922
|
-
constructor(description) {
|
22923
|
-
this.description = void 0;
|
22924
|
-
this.description = description;
|
22925
|
-
}
|
22926
|
-
}
|
22927
|
-
|
22928
23874
|
const DICT = 'Dict';
|
22929
23875
|
|
22930
23876
|
function format(value) {
|
@@ -22948,29 +23894,27 @@ function throwError(action, src, type, cause) {
|
|
22948
23894
|
});
|
22949
23895
|
}
|
22950
23896
|
|
22951
|
-
|
22952
|
-
|
22953
|
-
const BOOLEAN = 'Boolean';
|
22954
|
-
|
22955
|
-
const BYTES = 'Byte Sequence';
|
22956
|
-
|
22957
|
-
const DECIMAL = 'Decimal';
|
22958
|
-
|
22959
|
-
const INTEGER = 'Integer';
|
22960
|
-
|
22961
|
-
function isInvalidInt(value) {
|
22962
|
-
return value < -999999999999999 || 999999999999999 < value;
|
23897
|
+
function serializeError(src, type, cause) {
|
23898
|
+
return throwError('serialize', src, type, cause);
|
22963
23899
|
}
|
22964
23900
|
|
22965
|
-
|
22966
|
-
|
22967
|
-
|
23901
|
+
/**
|
23902
|
+
* A class to represent structured field tokens when `Symbol` is not available.
|
23903
|
+
*
|
23904
|
+
* @group Structured Field
|
23905
|
+
*
|
23906
|
+
* @beta
|
23907
|
+
*/
|
23908
|
+
class SfToken {
|
23909
|
+
constructor(description) {
|
23910
|
+
this.description = void 0;
|
23911
|
+
this.description = description;
|
23912
|
+
}
|
23913
|
+
}
|
22968
23914
|
|
22969
|
-
const
|
23915
|
+
const BARE_ITEM = 'Bare Item';
|
22970
23916
|
|
22971
|
-
|
22972
|
-
return throwError('serialize', src, type, cause);
|
22973
|
-
}
|
23917
|
+
const BOOLEAN = 'Boolean';
|
22974
23918
|
|
22975
23919
|
// 4.1.9. Serializing a Boolean
|
22976
23920
|
//
|
@@ -23009,6 +23953,8 @@ function base64encode(binary) {
|
|
23009
23953
|
return btoa(String.fromCharCode(...binary));
|
23010
23954
|
}
|
23011
23955
|
|
23956
|
+
const BYTES = 'Byte Sequence';
|
23957
|
+
|
23012
23958
|
// 4.1.8. Serializing a Byte Sequence
|
23013
23959
|
//
|
23014
23960
|
// Given a Byte Sequence as input_bytes, return an ASCII string suitable
|
@@ -23040,6 +23986,12 @@ function serializeByteSequence(value) {
|
|
23040
23986
|
return `:${base64encode(value)}:`;
|
23041
23987
|
}
|
23042
23988
|
|
23989
|
+
const INTEGER = 'Integer';
|
23990
|
+
|
23991
|
+
function isInvalidInt(value) {
|
23992
|
+
return value < -999999999999999 || 999999999999999 < value;
|
23993
|
+
}
|
23994
|
+
|
23043
23995
|
// 4.1.4. Serializing an Integer
|
23044
23996
|
//
|
23045
23997
|
// Given an Integer as input_integer, return an ASCII string suitable
|
@@ -23105,6 +24057,8 @@ function roundToEven(value, precision) {
|
|
23105
24057
|
}
|
23106
24058
|
}
|
23107
24059
|
|
24060
|
+
const DECIMAL = 'Decimal';
|
24061
|
+
|
23108
24062
|
// 4.1.5. Serializing a Decimal
|
23109
24063
|
//
|
23110
24064
|
// Given a decimal number as input_decimal, return an ASCII string
|
@@ -23150,6 +24104,8 @@ function serializeDecimal(value) {
|
|
23150
24104
|
|
23151
24105
|
const STRING = 'String';
|
23152
24106
|
|
24107
|
+
const STRING_REGEX = /[\x00-\x1f\x7f]+/; // eslint-disable-line no-control-regex
|
24108
|
+
|
23153
24109
|
// 4.1.6. Serializing a String
|
23154
24110
|
//
|
23155
24111
|
// Given a String as input_string, return an ASCII string suitable for
|
@@ -23185,6 +24141,8 @@ function symbolToStr(symbol) {
|
|
23185
24141
|
return symbol.description || symbol.toString().slice(7, -1);
|
23186
24142
|
}
|
23187
24143
|
|
24144
|
+
const TOKEN = 'Token';
|
24145
|
+
|
23188
24146
|
function serializeToken(token) {
|
23189
24147
|
const value = symbolToStr(token);
|
23190
24148
|
if (/^([a-zA-Z*])([!#$%&'*+\-.^_`|~\w:/]*)$/.test(value) === false) {
|
@@ -23252,6 +24210,8 @@ function serializeBareItem(value) {
|
|
23252
24210
|
}
|
23253
24211
|
}
|
23254
24212
|
|
24213
|
+
const KEY = 'Key';
|
24214
|
+
|
23255
24215
|
// 4.1.1.3. Serializing a Key
|
23256
24216
|
//
|
23257
24217
|
// Given a key as input_key, return an ASCII string suitable for use in
|
@@ -23493,36 +24453,6 @@ function urlToRelativePath(url, base) {
|
|
23493
24453
|
return toPath.join('/');
|
23494
24454
|
}
|
23495
24455
|
|
23496
|
-
/**
|
23497
|
-
* Generate a random v4 UUID
|
23498
|
-
*
|
23499
|
-
* @returns A random v4 UUID
|
23500
|
-
*
|
23501
|
-
* @group Utils
|
23502
|
-
*
|
23503
|
-
* @beta
|
23504
|
-
*/
|
23505
|
-
function uuid() {
|
23506
|
-
try {
|
23507
|
-
return crypto.randomUUID();
|
23508
|
-
} catch (error) {
|
23509
|
-
try {
|
23510
|
-
const url = URL.createObjectURL(new Blob());
|
23511
|
-
const uuid = url.toString();
|
23512
|
-
URL.revokeObjectURL(url);
|
23513
|
-
return uuid.slice(uuid.lastIndexOf('/') + 1);
|
23514
|
-
} catch (error) {
|
23515
|
-
let dt = new Date().getTime();
|
23516
|
-
const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
23517
|
-
const r = (dt + Math.random() * 16) % 16 | 0;
|
23518
|
-
dt = Math.floor(dt / 16);
|
23519
|
-
return (c == 'x' ? r : r & 0x3 | 0x8).toString(16);
|
23520
|
-
});
|
23521
|
-
return uuid;
|
23522
|
-
}
|
23523
|
-
}
|
23524
|
-
}
|
23525
|
-
|
23526
24456
|
const toRounded = value => Math.round(value);
|
23527
24457
|
const toUrlSafe = (value, options) => {
|
23528
24458
|
if (options != null && options.baseUrl) {
|
@@ -23748,6 +24678,36 @@ function appendCmcdQuery(url, cmcd, options) {
|
|
23748
24678
|
return `${url}${separator}${query}`;
|
23749
24679
|
}
|
23750
24680
|
|
24681
|
+
/**
|
24682
|
+
* Generate a random v4 UUID
|
24683
|
+
*
|
24684
|
+
* @returns A random v4 UUID
|
24685
|
+
*
|
24686
|
+
* @group Utils
|
24687
|
+
*
|
24688
|
+
* @beta
|
24689
|
+
*/
|
24690
|
+
function uuid() {
|
24691
|
+
try {
|
24692
|
+
return crypto.randomUUID();
|
24693
|
+
} catch (error) {
|
24694
|
+
try {
|
24695
|
+
const url = URL.createObjectURL(new Blob());
|
24696
|
+
const uuid = url.toString();
|
24697
|
+
URL.revokeObjectURL(url);
|
24698
|
+
return uuid.slice(uuid.lastIndexOf('/') + 1);
|
24699
|
+
} catch (error) {
|
24700
|
+
let dt = new Date().getTime();
|
24701
|
+
const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
24702
|
+
const r = (dt + Math.random() * 16) % 16 | 0;
|
24703
|
+
dt = Math.floor(dt / 16);
|
24704
|
+
return (c == 'x' ? r : r & 0x3 | 0x8).toString(16);
|
24705
|
+
});
|
24706
|
+
return uuid;
|
24707
|
+
}
|
24708
|
+
}
|
24709
|
+
}
|
24710
|
+
|
23751
24711
|
/**
|
23752
24712
|
* Controller to deal with Common Media Client Data (CMCD)
|
23753
24713
|
* @see https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf
|
@@ -23791,7 +24751,7 @@ class CMCDController {
|
|
23791
24751
|
su: !this.initialized
|
23792
24752
|
});
|
23793
24753
|
} catch (error) {
|
23794
|
-
logger.warn('Could not generate manifest CMCD data.', error);
|
24754
|
+
this.hls.logger.warn('Could not generate manifest CMCD data.', error);
|
23795
24755
|
}
|
23796
24756
|
};
|
23797
24757
|
/**
|
@@ -23811,9 +24771,15 @@ class CMCDController {
|
|
23811
24771
|
data.tb = this.getTopBandwidth(ot) / 1000;
|
23812
24772
|
data.bl = this.getBufferLength(ot);
|
23813
24773
|
}
|
24774
|
+
const next = this.getNextFrag(fragment);
|
24775
|
+
if (next) {
|
24776
|
+
if (next.url && next.url !== fragment.url) {
|
24777
|
+
data.nor = next.url;
|
24778
|
+
}
|
24779
|
+
}
|
23814
24780
|
this.apply(context, data);
|
23815
24781
|
} catch (error) {
|
23816
|
-
logger.warn('Could not generate segment CMCD data.', error);
|
24782
|
+
this.hls.logger.warn('Could not generate segment CMCD data.', error);
|
23817
24783
|
}
|
23818
24784
|
};
|
23819
24785
|
this.hls = hls;
|
@@ -23903,7 +24869,7 @@ class CMCDController {
|
|
23903
24869
|
data.su = this.buffering;
|
23904
24870
|
}
|
23905
24871
|
|
23906
|
-
// TODO: Implement rtp, nrr,
|
24872
|
+
// TODO: Implement rtp, nrr, dl
|
23907
24873
|
|
23908
24874
|
const {
|
23909
24875
|
includeKeys
|
@@ -23914,15 +24880,28 @@ class CMCDController {
|
|
23914
24880
|
return acc;
|
23915
24881
|
}, {});
|
23916
24882
|
}
|
24883
|
+
const options = {
|
24884
|
+
baseUrl: context.url
|
24885
|
+
};
|
23917
24886
|
if (this.useHeaders) {
|
23918
24887
|
if (!context.headers) {
|
23919
24888
|
context.headers = {};
|
23920
24889
|
}
|
23921
|
-
appendCmcdHeaders(context.headers, data);
|
24890
|
+
appendCmcdHeaders(context.headers, data, options);
|
23922
24891
|
} else {
|
23923
|
-
context.url = appendCmcdQuery(context.url, data);
|
24892
|
+
context.url = appendCmcdQuery(context.url, data, options);
|
23924
24893
|
}
|
23925
24894
|
}
|
24895
|
+
getNextFrag(fragment) {
|
24896
|
+
var _this$hls$levels$frag;
|
24897
|
+
const levelDetails = (_this$hls$levels$frag = this.hls.levels[fragment.level]) == null ? void 0 : _this$hls$levels$frag.details;
|
24898
|
+
if (levelDetails) {
|
24899
|
+
const index = fragment.sn - levelDetails.startSN;
|
24900
|
+
return levelDetails.fragments[index + 1];
|
24901
|
+
}
|
24902
|
+
return undefined;
|
24903
|
+
}
|
24904
|
+
|
23926
24905
|
/**
|
23927
24906
|
* The CMCD object type.
|
23928
24907
|
*/
|
@@ -24051,10 +25030,10 @@ class CMCDController {
|
|
24051
25030
|
}
|
24052
25031
|
|
24053
25032
|
const PATHWAY_PENALTY_DURATION_MS = 300000;
|
24054
|
-
class ContentSteeringController {
|
25033
|
+
class ContentSteeringController extends Logger {
|
24055
25034
|
constructor(hls) {
|
25035
|
+
super('content-steering', hls.logger);
|
24056
25036
|
this.hls = void 0;
|
24057
|
-
this.log = void 0;
|
24058
25037
|
this.loader = null;
|
24059
25038
|
this.uri = null;
|
24060
25039
|
this.pathwayId = '.';
|
@@ -24069,7 +25048,6 @@ class ContentSteeringController {
|
|
24069
25048
|
this.subtitleTracks = null;
|
24070
25049
|
this.penalizedPathways = {};
|
24071
25050
|
this.hls = hls;
|
24072
|
-
this.log = logger.log.bind(logger, `[content-steering]:`);
|
24073
25051
|
this.registerListeners();
|
24074
25052
|
}
|
24075
25053
|
registerListeners() {
|
@@ -24193,7 +25171,7 @@ class ContentSteeringController {
|
|
24193
25171
|
errorAction.resolved = this.pathwayId !== errorPathway;
|
24194
25172
|
}
|
24195
25173
|
if (!errorAction.resolved) {
|
24196
|
-
|
25174
|
+
this.warn(`Could not resolve ${data.details} ("${data.error.message}") with content-steering for Pathway: ${errorPathway} levels: ${levels ? levels.length : levels} priorities: ${JSON.stringify(pathwayPriority)} penalized: ${JSON.stringify(this.penalizedPathways)}`);
|
24197
25175
|
}
|
24198
25176
|
}
|
24199
25177
|
}
|
@@ -24364,7 +25342,7 @@ class ContentSteeringController {
|
|
24364
25342
|
onSuccess: (response, stats, context, networkDetails) => {
|
24365
25343
|
this.log(`Loaded steering manifest: "${url}"`);
|
24366
25344
|
const steeringData = response.data;
|
24367
|
-
if (steeringData.VERSION !== 1) {
|
25345
|
+
if ((steeringData == null ? void 0 : steeringData.VERSION) !== 1) {
|
24368
25346
|
this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
|
24369
25347
|
return;
|
24370
25348
|
}
|
@@ -25334,7 +26312,7 @@ function timelineConfig() {
|
|
25334
26312
|
/**
|
25335
26313
|
* @ignore
|
25336
26314
|
*/
|
25337
|
-
function mergeConfig(defaultConfig, userConfig) {
|
26315
|
+
function mergeConfig(defaultConfig, userConfig, logger) {
|
25338
26316
|
if ((userConfig.liveSyncDurationCount || userConfig.liveMaxLatencyDurationCount) && (userConfig.liveSyncDuration || userConfig.liveMaxLatencyDuration)) {
|
25339
26317
|
throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration");
|
25340
26318
|
}
|
@@ -25404,7 +26382,7 @@ function deepCpy(obj) {
|
|
25404
26382
|
/**
|
25405
26383
|
* @ignore
|
25406
26384
|
*/
|
25407
|
-
function enableStreamingMode(config) {
|
26385
|
+
function enableStreamingMode(config, logger) {
|
25408
26386
|
const currentLoader = config.loader;
|
25409
26387
|
if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
|
25410
26388
|
// If a developer has configured their own loader, respect that choice
|
@@ -25421,10 +26399,9 @@ function enableStreamingMode(config) {
|
|
25421
26399
|
}
|
25422
26400
|
}
|
25423
26401
|
|
25424
|
-
let chromeOrFirefox;
|
25425
26402
|
class LevelController extends BasePlaylistController {
|
25426
26403
|
constructor(hls, contentSteeringController) {
|
25427
|
-
super(hls, '
|
26404
|
+
super(hls, 'level-controller');
|
25428
26405
|
this._levels = [];
|
25429
26406
|
this._firstLevel = -1;
|
25430
26407
|
this._maxAutoLevel = -1;
|
@@ -25495,23 +26472,15 @@ class LevelController extends BasePlaylistController {
|
|
25495
26472
|
let videoCodecFound = false;
|
25496
26473
|
let audioCodecFound = false;
|
25497
26474
|
data.levels.forEach(levelParsed => {
|
25498
|
-
var
|
26475
|
+
var _videoCodec;
|
25499
26476
|
const attributes = levelParsed.attrs;
|
25500
|
-
|
25501
|
-
// erase audio codec info if browser does not support mp4a.40.34.
|
25502
|
-
// demuxer will autodetect codec and fallback to mpeg/audio
|
25503
26477
|
let {
|
25504
26478
|
audioCodec,
|
25505
26479
|
videoCodec
|
25506
26480
|
} = levelParsed;
|
25507
|
-
if (((_audioCodec = audioCodec) == null ? void 0 : _audioCodec.indexOf('mp4a.40.34')) !== -1) {
|
25508
|
-
chromeOrFirefox || (chromeOrFirefox = /chrome|firefox/i.test(navigator.userAgent));
|
25509
|
-
if (chromeOrFirefox) {
|
25510
|
-
levelParsed.audioCodec = audioCodec = undefined;
|
25511
|
-
}
|
25512
|
-
}
|
25513
26481
|
if (audioCodec) {
|
25514
|
-
|
26482
|
+
// Returns empty and set to undefined for 'mp4a.40.34' with fallback to 'audio/mpeg' SourceBuffer
|
26483
|
+
levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource) || undefined;
|
25515
26484
|
}
|
25516
26485
|
if (((_videoCodec = videoCodec) == null ? void 0 : _videoCodec.indexOf('avc1')) === 0) {
|
25517
26486
|
videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
|
@@ -25853,7 +26822,12 @@ class LevelController extends BasePlaylistController {
|
|
25853
26822
|
if (curLevel.fragmentError === 0) {
|
25854
26823
|
curLevel.loadError = 0;
|
25855
26824
|
}
|
25856
|
-
|
26825
|
+
// Ignore matching details populated by loading a Media Playlist directly
|
26826
|
+
let previousDetails = curLevel.details;
|
26827
|
+
if (previousDetails === data.details && previousDetails.advanced) {
|
26828
|
+
previousDetails = undefined;
|
26829
|
+
}
|
26830
|
+
this.playlistLoaded(level, data, previousDetails);
|
25857
26831
|
} else if ((_data$deliveryDirecti2 = data.deliveryDirectives) != null && _data$deliveryDirecti2.skip) {
|
25858
26832
|
// received a delta playlist update that cannot be merged
|
25859
26833
|
details.deltaUpdateFailed = true;
|
@@ -26097,6 +27071,8 @@ class KeyLoader {
|
|
26097
27071
|
}
|
26098
27072
|
return this.loadKeyEME(keyInfo, frag);
|
26099
27073
|
case 'AES-128':
|
27074
|
+
case 'AES-256':
|
27075
|
+
case 'AES-256-CTR':
|
26100
27076
|
return this.loadKeyHTTP(keyInfo, frag);
|
26101
27077
|
default:
|
26102
27078
|
return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`)));
|
@@ -26234,8 +27210,9 @@ const STALL_MINIMUM_DURATION_MS = 250;
|
|
26234
27210
|
const MAX_START_GAP_JUMP = 2.0;
|
26235
27211
|
const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
|
26236
27212
|
const SKIP_BUFFER_RANGE_START = 0.05;
|
26237
|
-
class GapController {
|
27213
|
+
class GapController extends Logger {
|
26238
27214
|
constructor(config, media, fragmentTracker, hls) {
|
27215
|
+
super('gap-controller', hls.logger);
|
26239
27216
|
this.config = void 0;
|
26240
27217
|
this.media = null;
|
26241
27218
|
this.fragmentTracker = void 0;
|
@@ -26245,6 +27222,7 @@ class GapController {
|
|
26245
27222
|
this.stalled = null;
|
26246
27223
|
this.moved = false;
|
26247
27224
|
this.seeking = false;
|
27225
|
+
this.ended = 0;
|
26248
27226
|
this.config = config;
|
26249
27227
|
this.media = media;
|
26250
27228
|
this.fragmentTracker = fragmentTracker;
|
@@ -26262,7 +27240,7 @@ class GapController {
|
|
26262
27240
|
*
|
26263
27241
|
* @param lastCurrentTime - Previously read playhead position
|
26264
27242
|
*/
|
26265
|
-
poll(lastCurrentTime, activeFrag) {
|
27243
|
+
poll(lastCurrentTime, activeFrag, levelDetails, state) {
|
26266
27244
|
const {
|
26267
27245
|
config,
|
26268
27246
|
media,
|
@@ -26281,6 +27259,7 @@ class GapController {
|
|
26281
27259
|
|
26282
27260
|
// The playhead is moving, no-op
|
26283
27261
|
if (currentTime !== lastCurrentTime) {
|
27262
|
+
this.ended = 0;
|
26284
27263
|
this.moved = true;
|
26285
27264
|
if (!seeking) {
|
26286
27265
|
this.nudgeRetry = 0;
|
@@ -26289,7 +27268,7 @@ class GapController {
|
|
26289
27268
|
// The playhead is now moving, but was previously stalled
|
26290
27269
|
if (this.stallReported) {
|
26291
27270
|
const _stalledDuration = self.performance.now() - stalled;
|
26292
|
-
|
27271
|
+
this.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
|
26293
27272
|
this.stallReported = false;
|
26294
27273
|
}
|
26295
27274
|
this.stalled = null;
|
@@ -26325,7 +27304,6 @@ class GapController {
|
|
26325
27304
|
// Skip start gaps if we haven't played, but the last poll detected the start of a stall
|
26326
27305
|
// The addition poll gives the browser a chance to jump the gap for us
|
26327
27306
|
if (!this.moved && this.stalled !== null) {
|
26328
|
-
var _level$details;
|
26329
27307
|
// There is no playable buffer (seeked, waiting for buffer)
|
26330
27308
|
const isBuffered = bufferInfo.len > 0;
|
26331
27309
|
if (!isBuffered && !nextStart) {
|
@@ -26337,9 +27315,8 @@ class GapController {
|
|
26337
27315
|
// When joining a live stream with audio tracks, account for live playlist window sliding by allowing
|
26338
27316
|
// a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
|
26339
27317
|
// that begins over 1 target duration after the video start position.
|
26340
|
-
const
|
26341
|
-
const
|
26342
|
-
const maxStartGapJump = isLive ? level.details.targetduration * 2 : MAX_START_GAP_JUMP;
|
27318
|
+
const isLive = !!(levelDetails != null && levelDetails.live);
|
27319
|
+
const maxStartGapJump = isLive ? levelDetails.targetduration * 2 : MAX_START_GAP_JUMP;
|
26343
27320
|
const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
|
26344
27321
|
if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
|
26345
27322
|
if (!media.paused) {
|
@@ -26357,6 +27334,17 @@ class GapController {
|
|
26357
27334
|
}
|
26358
27335
|
const stalledDuration = tnow - stalled;
|
26359
27336
|
if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) {
|
27337
|
+
// Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
|
27338
|
+
if (state === State.ENDED && !(levelDetails && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? void 0 : levelDetails.edge) || 0)) < 1) {
|
27339
|
+
if (stalledDuration < 1000 || this.ended) {
|
27340
|
+
return;
|
27341
|
+
}
|
27342
|
+
this.ended = currentTime;
|
27343
|
+
this.hls.trigger(Events.MEDIA_ENDED, {
|
27344
|
+
stalled: true
|
27345
|
+
});
|
27346
|
+
return;
|
27347
|
+
}
|
26360
27348
|
// Report stalling after trying to fix
|
26361
27349
|
this._reportStall(bufferInfo);
|
26362
27350
|
if (!this.media) {
|
@@ -26400,7 +27388,7 @@ class GapController {
|
|
26400
27388
|
// needs to cross some sort of threshold covering all source-buffers content
|
26401
27389
|
// to start playing properly.
|
26402
27390
|
if ((bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
|
26403
|
-
|
27391
|
+
this.warn('Trying to nudge playhead over buffer-hole');
|
26404
27392
|
// Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
|
26405
27393
|
// We only try to jump the hole if it's under the configured size
|
26406
27394
|
// Reset stalled so to rearm watchdog timer
|
@@ -26424,7 +27412,7 @@ class GapController {
|
|
26424
27412
|
// Report stalled error once
|
26425
27413
|
this.stallReported = true;
|
26426
27414
|
const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${JSON.stringify(bufferInfo)})`);
|
26427
|
-
|
27415
|
+
this.warn(error.message);
|
26428
27416
|
hls.trigger(Events.ERROR, {
|
26429
27417
|
type: ErrorTypes.MEDIA_ERROR,
|
26430
27418
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
@@ -26492,7 +27480,7 @@ class GapController {
|
|
26492
27480
|
}
|
26493
27481
|
}
|
26494
27482
|
const targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
|
26495
|
-
|
27483
|
+
this.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
|
26496
27484
|
this.moved = true;
|
26497
27485
|
this.stalled = null;
|
26498
27486
|
media.currentTime = targetTime;
|
@@ -26533,7 +27521,7 @@ class GapController {
|
|
26533
27521
|
const targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset;
|
26534
27522
|
// playback stalled in buffered area ... let's nudge currentTime to try to overcome this
|
26535
27523
|
const error = new Error(`Nudging 'currentTime' from ${currentTime} to ${targetTime}`);
|
26536
|
-
|
27524
|
+
this.warn(error.message);
|
26537
27525
|
media.currentTime = targetTime;
|
26538
27526
|
hls.trigger(Events.ERROR, {
|
26539
27527
|
type: ErrorTypes.MEDIA_ERROR,
|
@@ -26543,7 +27531,7 @@ class GapController {
|
|
26543
27531
|
});
|
26544
27532
|
} else {
|
26545
27533
|
const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`);
|
26546
|
-
|
27534
|
+
this.error(error.message);
|
26547
27535
|
hls.trigger(Events.ERROR, {
|
26548
27536
|
type: ErrorTypes.MEDIA_ERROR,
|
26549
27537
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
@@ -26558,7 +27546,7 @@ const TICK_INTERVAL = 100; // how often to tick in ms
|
|
26558
27546
|
|
26559
27547
|
class StreamController extends BaseStreamController {
|
26560
27548
|
constructor(hls, fragmentTracker, keyLoader) {
|
26561
|
-
super(hls, fragmentTracker, keyLoader, '
|
27549
|
+
super(hls, fragmentTracker, keyLoader, 'stream-controller', PlaylistLevelType.MAIN);
|
26562
27550
|
this.audioCodecSwap = false;
|
26563
27551
|
this.gapController = null;
|
26564
27552
|
this.level = -1;
|
@@ -26566,27 +27554,43 @@ class StreamController extends BaseStreamController {
|
|
26566
27554
|
this.altAudio = false;
|
26567
27555
|
this.audioOnly = false;
|
26568
27556
|
this.fragPlaying = null;
|
26569
|
-
this.onvplaying = null;
|
26570
|
-
this.onvseeked = null;
|
26571
27557
|
this.fragLastKbps = 0;
|
26572
27558
|
this.couldBacktrack = false;
|
26573
27559
|
this.backtrackFragment = null;
|
26574
27560
|
this.audioCodecSwitch = false;
|
26575
27561
|
this.videoBuffer = null;
|
26576
|
-
this.
|
27562
|
+
this.onMediaPlaying = () => {
|
27563
|
+
// tick to speed up FRAG_CHANGED triggering
|
27564
|
+
this.tick();
|
27565
|
+
};
|
27566
|
+
this.onMediaSeeked = () => {
|
27567
|
+
const media = this.media;
|
27568
|
+
const currentTime = media ? media.currentTime : null;
|
27569
|
+
if (isFiniteNumber(currentTime)) {
|
27570
|
+
this.log(`Media seeked to ${currentTime.toFixed(3)}`);
|
27571
|
+
}
|
27572
|
+
|
27573
|
+
// If seeked was issued before buffer was appended do not tick immediately
|
27574
|
+
const bufferInfo = this.getMainFwdBufferInfo();
|
27575
|
+
if (bufferInfo === null || bufferInfo.len === 0) {
|
27576
|
+
this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
|
27577
|
+
return;
|
27578
|
+
}
|
27579
|
+
|
27580
|
+
// tick to speed up FRAG_CHANGED triggering
|
27581
|
+
this.tick();
|
27582
|
+
};
|
27583
|
+
this.registerListeners();
|
26577
27584
|
}
|
26578
|
-
|
27585
|
+
registerListeners() {
|
27586
|
+
super.registerListeners();
|
26579
27587
|
const {
|
26580
27588
|
hls
|
26581
27589
|
} = this;
|
26582
|
-
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
26583
|
-
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
26584
|
-
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
26585
27590
|
hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
26586
27591
|
hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
|
26587
27592
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
26588
27593
|
hls.on(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
|
26589
|
-
hls.on(Events.ERROR, this.onError, this);
|
26590
27594
|
hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
26591
27595
|
hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
26592
27596
|
hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -26594,17 +27598,14 @@ class StreamController extends BaseStreamController {
|
|
26594
27598
|
hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
|
26595
27599
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
26596
27600
|
}
|
26597
|
-
|
27601
|
+
unregisterListeners() {
|
27602
|
+
super.unregisterListeners();
|
26598
27603
|
const {
|
26599
27604
|
hls
|
26600
27605
|
} = this;
|
26601
|
-
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
26602
|
-
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
26603
|
-
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
26604
27606
|
hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
26605
27607
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
26606
27608
|
hls.off(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
|
26607
|
-
hls.off(Events.ERROR, this.onError, this);
|
26608
27609
|
hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
26609
27610
|
hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
26610
27611
|
hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -26613,7 +27614,9 @@ class StreamController extends BaseStreamController {
|
|
26613
27614
|
hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
26614
27615
|
}
|
26615
27616
|
onHandlerDestroying() {
|
26616
|
-
|
27617
|
+
// @ts-ignore
|
27618
|
+
this.onMediaPlaying = this.onMediaSeeked = null;
|
27619
|
+
this.unregisterListeners();
|
26617
27620
|
super.onHandlerDestroying();
|
26618
27621
|
}
|
26619
27622
|
startLoad(startPosition) {
|
@@ -26711,6 +27714,9 @@ class StreamController extends BaseStreamController {
|
|
26711
27714
|
this.checkFragmentChanged();
|
26712
27715
|
}
|
26713
27716
|
doTickIdle() {
|
27717
|
+
if (!this.buffering) {
|
27718
|
+
return;
|
27719
|
+
}
|
26714
27720
|
const {
|
26715
27721
|
hls,
|
26716
27722
|
levelLastLoaded,
|
@@ -26938,20 +27944,17 @@ class StreamController extends BaseStreamController {
|
|
26938
27944
|
onMediaAttached(event, data) {
|
26939
27945
|
super.onMediaAttached(event, data);
|
26940
27946
|
const media = data.media;
|
26941
|
-
|
26942
|
-
|
26943
|
-
media.addEventListener('playing', this.onvplaying);
|
26944
|
-
media.addEventListener('seeked', this.onvseeked);
|
27947
|
+
media.addEventListener('playing', this.onMediaPlaying);
|
27948
|
+
media.addEventListener('seeked', this.onMediaSeeked);
|
26945
27949
|
this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
|
26946
27950
|
}
|
26947
27951
|
onMediaDetaching() {
|
26948
27952
|
const {
|
26949
27953
|
media
|
26950
27954
|
} = this;
|
26951
|
-
if (media
|
26952
|
-
media.removeEventListener('playing', this.
|
26953
|
-
media.removeEventListener('seeked', this.
|
26954
|
-
this.onvplaying = this.onvseeked = null;
|
27955
|
+
if (media) {
|
27956
|
+
media.removeEventListener('playing', this.onMediaPlaying);
|
27957
|
+
media.removeEventListener('seeked', this.onMediaSeeked);
|
26955
27958
|
this.videoBuffer = null;
|
26956
27959
|
}
|
26957
27960
|
this.fragPlaying = null;
|
@@ -26961,27 +27964,6 @@ class StreamController extends BaseStreamController {
|
|
26961
27964
|
}
|
26962
27965
|
super.onMediaDetaching();
|
26963
27966
|
}
|
26964
|
-
onMediaPlaying() {
|
26965
|
-
// tick to speed up FRAG_CHANGED triggering
|
26966
|
-
this.tick();
|
26967
|
-
}
|
26968
|
-
onMediaSeeked() {
|
26969
|
-
const media = this.media;
|
26970
|
-
const currentTime = media ? media.currentTime : null;
|
26971
|
-
if (isFiniteNumber(currentTime)) {
|
26972
|
-
this.log(`Media seeked to ${currentTime.toFixed(3)}`);
|
26973
|
-
}
|
26974
|
-
|
26975
|
-
// If seeked was issued before buffer was appended do not tick immediately
|
26976
|
-
const bufferInfo = this.getMainFwdBufferInfo();
|
26977
|
-
if (bufferInfo === null || bufferInfo.len === 0) {
|
26978
|
-
this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
|
26979
|
-
return;
|
26980
|
-
}
|
26981
|
-
|
26982
|
-
// tick to speed up FRAG_CHANGED triggering
|
26983
|
-
this.tick();
|
26984
|
-
}
|
26985
27967
|
onManifestLoading() {
|
26986
27968
|
// reset buffer on manifest loading
|
26987
27969
|
this.log('Trigger BUFFER_RESET');
|
@@ -27273,8 +28255,10 @@ class StreamController extends BaseStreamController {
|
|
27273
28255
|
}
|
27274
28256
|
if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) {
|
27275
28257
|
// Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
|
27276
|
-
const
|
27277
|
-
|
28258
|
+
const state = this.state;
|
28259
|
+
const activeFrag = state !== State.IDLE ? this.fragCurrent : null;
|
28260
|
+
const levelDetails = this.getLevelDetails();
|
28261
|
+
gapController.poll(this.lastCurrentTime, activeFrag, levelDetails, state);
|
27278
28262
|
}
|
27279
28263
|
this.lastCurrentTime = media.currentTime;
|
27280
28264
|
}
|
@@ -27607,6 +28591,17 @@ class StreamController extends BaseStreamController {
|
|
27607
28591
|
getMainFwdBufferInfo() {
|
27608
28592
|
return this.getFwdBufferInfo(this.mediaBuffer ? this.mediaBuffer : this.media, PlaylistLevelType.MAIN);
|
27609
28593
|
}
|
28594
|
+
get maxBufferLength() {
|
28595
|
+
const {
|
28596
|
+
levels,
|
28597
|
+
level
|
28598
|
+
} = this;
|
28599
|
+
const levelInfo = levels == null ? void 0 : levels[level];
|
28600
|
+
if (!levelInfo) {
|
28601
|
+
return this.config.maxBufferLength;
|
28602
|
+
}
|
28603
|
+
return this.getMaxBufferLength(levelInfo.maxBitrate);
|
28604
|
+
}
|
27610
28605
|
backtrack(frag) {
|
27611
28606
|
this.couldBacktrack = true;
|
27612
28607
|
// Causes findFragments to backtrack through fragments to find the keyframe
|
@@ -27712,7 +28707,7 @@ class Hls {
|
|
27712
28707
|
* Get the video-dev/hls.js package version.
|
27713
28708
|
*/
|
27714
28709
|
static get version() {
|
27715
|
-
return "1.5.
|
28710
|
+
return "1.5.8-0.canary.10046";
|
27716
28711
|
}
|
27717
28712
|
|
27718
28713
|
/**
|
@@ -27775,9 +28770,12 @@ class Hls {
|
|
27775
28770
|
* The configuration object provided on player instantiation.
|
27776
28771
|
*/
|
27777
28772
|
this.userConfig = void 0;
|
28773
|
+
/**
|
28774
|
+
* The logger functions used by this player instance, configured on player instantiation.
|
28775
|
+
*/
|
28776
|
+
this.logger = void 0;
|
27778
28777
|
this.coreComponents = void 0;
|
27779
28778
|
this.networkControllers = void 0;
|
27780
|
-
this.started = false;
|
27781
28779
|
this._emitter = new EventEmitter();
|
27782
28780
|
this._autoLevelCapping = -1;
|
27783
28781
|
this._maxHdcpLevel = null;
|
@@ -27794,11 +28792,11 @@ class Hls {
|
|
27794
28792
|
this._media = null;
|
27795
28793
|
this.url = null;
|
27796
28794
|
this.triggeringException = void 0;
|
27797
|
-
enableLogs(userConfig.debug || false, 'Hls instance');
|
27798
|
-
const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig);
|
28795
|
+
const logger = this.logger = enableLogs(userConfig.debug || false, 'Hls instance');
|
28796
|
+
const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig, logger);
|
27799
28797
|
this.userConfig = userConfig;
|
27800
28798
|
if (config.progressive) {
|
27801
|
-
enableStreamingMode(config);
|
28799
|
+
enableStreamingMode(config, logger);
|
27802
28800
|
}
|
27803
28801
|
|
27804
28802
|
// core controllers and network loaders
|
@@ -27811,7 +28809,9 @@ class Hls {
|
|
27811
28809
|
} = config;
|
27812
28810
|
const errorController = new ConfigErrorController(this);
|
27813
28811
|
const abrController = this.abrController = new ConfigAbrController(this);
|
27814
|
-
|
28812
|
+
// FragmentTracker must be defined before StreamController because the order of event handling is important
|
28813
|
+
const fragmentTracker = new FragmentTracker(this);
|
28814
|
+
const bufferController = this.bufferController = new ConfigBufferController(this, fragmentTracker);
|
27815
28815
|
const capLevelController = this.capLevelController = new ConfigCapLevelController(this);
|
27816
28816
|
const fpsController = new ConfigFpsController(this);
|
27817
28817
|
const playListLoader = new PlaylistLoader(this);
|
@@ -27820,8 +28820,6 @@ class Hls {
|
|
27820
28820
|
// ConentSteeringController is defined before LevelController to receive Multivariant Playlist events first
|
27821
28821
|
const contentSteering = ConfigContentSteeringController ? new ConfigContentSteeringController(this) : null;
|
27822
28822
|
const levelController = this.levelController = new LevelController(this, contentSteering);
|
27823
|
-
// FragmentTracker must be defined before StreamController because the order of event handling is important
|
27824
|
-
const fragmentTracker = new FragmentTracker(this);
|
27825
28823
|
const keyLoader = new KeyLoader(this.config);
|
27826
28824
|
const streamController = this.streamController = new StreamController(this, fragmentTracker, keyLoader);
|
27827
28825
|
|
@@ -27897,7 +28895,7 @@ class Hls {
|
|
27897
28895
|
try {
|
27898
28896
|
return this.emit(event, event, eventObject);
|
27899
28897
|
} catch (error) {
|
27900
|
-
logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
|
28898
|
+
this.logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
|
27901
28899
|
// Prevent recursion in error event handlers that throw #5497
|
27902
28900
|
if (!this.triggeringException) {
|
27903
28901
|
this.triggeringException = true;
|
@@ -27923,7 +28921,7 @@ class Hls {
|
|
27923
28921
|
* Dispose of the instance
|
27924
28922
|
*/
|
27925
28923
|
destroy() {
|
27926
|
-
logger.log('destroy');
|
28924
|
+
this.logger.log('destroy');
|
27927
28925
|
this.trigger(Events.DESTROYING, undefined);
|
27928
28926
|
this.detachMedia();
|
27929
28927
|
this.removeAllListeners();
|
@@ -27944,7 +28942,7 @@ class Hls {
|
|
27944
28942
|
* Attaches Hls.js to a media element
|
27945
28943
|
*/
|
27946
28944
|
attachMedia(media) {
|
27947
|
-
logger.log('attachMedia');
|
28945
|
+
this.logger.log('attachMedia');
|
27948
28946
|
this._media = media;
|
27949
28947
|
this.trigger(Events.MEDIA_ATTACHING, {
|
27950
28948
|
media: media
|
@@ -27955,7 +28953,7 @@ class Hls {
|
|
27955
28953
|
* Detach Hls.js from the media
|
27956
28954
|
*/
|
27957
28955
|
detachMedia() {
|
27958
|
-
logger.log('detachMedia');
|
28956
|
+
this.logger.log('detachMedia');
|
27959
28957
|
this.trigger(Events.MEDIA_DETACHING, undefined);
|
27960
28958
|
this._media = null;
|
27961
28959
|
}
|
@@ -27972,7 +28970,7 @@ class Hls {
|
|
27972
28970
|
});
|
27973
28971
|
this._autoLevelCapping = -1;
|
27974
28972
|
this._maxHdcpLevel = null;
|
27975
|
-
logger.log(`loadSource:${loadingSource}`);
|
28973
|
+
this.logger.log(`loadSource:${loadingSource}`);
|
27976
28974
|
if (media && loadedSource && (loadedSource !== loadingSource || this.bufferController.hasSourceTypes())) {
|
27977
28975
|
this.detachMedia();
|
27978
28976
|
this.attachMedia(media);
|
@@ -27991,8 +28989,7 @@ class Hls {
|
|
27991
28989
|
* Defaults to -1 (None: starts from earliest point)
|
27992
28990
|
*/
|
27993
28991
|
startLoad(startPosition = -1) {
|
27994
|
-
logger.log(`startLoad(${startPosition})`);
|
27995
|
-
this.started = true;
|
28992
|
+
this.logger.log(`startLoad(${startPosition})`);
|
27996
28993
|
this.networkControllers.forEach(controller => {
|
27997
28994
|
controller.startLoad(startPosition);
|
27998
28995
|
});
|
@@ -28002,34 +28999,31 @@ class Hls {
|
|
28002
28999
|
* Stop loading of any stream data.
|
28003
29000
|
*/
|
28004
29001
|
stopLoad() {
|
28005
|
-
logger.log('stopLoad');
|
28006
|
-
this.started = false;
|
29002
|
+
this.logger.log('stopLoad');
|
28007
29003
|
this.networkControllers.forEach(controller => {
|
28008
29004
|
controller.stopLoad();
|
28009
29005
|
});
|
28010
29006
|
}
|
28011
29007
|
|
28012
29008
|
/**
|
28013
|
-
* Resumes stream controller segment loading
|
29009
|
+
* Resumes stream controller segment loading after `pauseBuffering` has been called.
|
28014
29010
|
*/
|
28015
29011
|
resumeBuffering() {
|
28016
|
-
|
28017
|
-
|
28018
|
-
|
28019
|
-
|
28020
|
-
|
28021
|
-
});
|
28022
|
-
}
|
29012
|
+
this.networkControllers.forEach(controller => {
|
29013
|
+
if (controller.resumeBuffering) {
|
29014
|
+
controller.resumeBuffering();
|
29015
|
+
}
|
29016
|
+
});
|
28023
29017
|
}
|
28024
29018
|
|
28025
29019
|
/**
|
28026
|
-
*
|
29020
|
+
* Prevents stream controller from loading new segments until `resumeBuffering` is called.
|
28027
29021
|
* This allows for media buffering to be paused without interupting playlist loading.
|
28028
29022
|
*/
|
28029
29023
|
pauseBuffering() {
|
28030
29024
|
this.networkControllers.forEach(controller => {
|
28031
|
-
if (
|
28032
|
-
controller.
|
29025
|
+
if (controller.pauseBuffering) {
|
29026
|
+
controller.pauseBuffering();
|
28033
29027
|
}
|
28034
29028
|
});
|
28035
29029
|
}
|
@@ -28038,7 +29032,7 @@ class Hls {
|
|
28038
29032
|
* Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
|
28039
29033
|
*/
|
28040
29034
|
swapAudioCodec() {
|
28041
|
-
logger.log('swapAudioCodec');
|
29035
|
+
this.logger.log('swapAudioCodec');
|
28042
29036
|
this.streamController.swapAudioCodec();
|
28043
29037
|
}
|
28044
29038
|
|
@@ -28049,7 +29043,7 @@ class Hls {
|
|
28049
29043
|
* Automatic recovery of media-errors by this process is configurable.
|
28050
29044
|
*/
|
28051
29045
|
recoverMediaError() {
|
28052
|
-
logger.log('recoverMediaError');
|
29046
|
+
this.logger.log('recoverMediaError');
|
28053
29047
|
const media = this._media;
|
28054
29048
|
this.detachMedia();
|
28055
29049
|
if (media) {
|
@@ -28079,7 +29073,7 @@ class Hls {
|
|
28079
29073
|
* 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.
|
28080
29074
|
*/
|
28081
29075
|
set currentLevel(newLevel) {
|
28082
|
-
logger.log(`set currentLevel:${newLevel}`);
|
29076
|
+
this.logger.log(`set currentLevel:${newLevel}`);
|
28083
29077
|
this.levelController.manualLevel = newLevel;
|
28084
29078
|
this.streamController.immediateLevelSwitch();
|
28085
29079
|
}
|
@@ -28098,7 +29092,7 @@ class Hls {
|
|
28098
29092
|
* @param newLevel - Pass -1 for automatic level selection
|
28099
29093
|
*/
|
28100
29094
|
set nextLevel(newLevel) {
|
28101
|
-
logger.log(`set nextLevel:${newLevel}`);
|
29095
|
+
this.logger.log(`set nextLevel:${newLevel}`);
|
28102
29096
|
this.levelController.manualLevel = newLevel;
|
28103
29097
|
this.streamController.nextLevelSwitch();
|
28104
29098
|
}
|
@@ -28117,7 +29111,7 @@ class Hls {
|
|
28117
29111
|
* @param newLevel - Pass -1 for automatic level selection
|
28118
29112
|
*/
|
28119
29113
|
set loadLevel(newLevel) {
|
28120
|
-
logger.log(`set loadLevel:${newLevel}`);
|
29114
|
+
this.logger.log(`set loadLevel:${newLevel}`);
|
28121
29115
|
this.levelController.manualLevel = newLevel;
|
28122
29116
|
}
|
28123
29117
|
|
@@ -28148,7 +29142,7 @@ class Hls {
|
|
28148
29142
|
* Sets "first-level", see getter.
|
28149
29143
|
*/
|
28150
29144
|
set firstLevel(newLevel) {
|
28151
|
-
logger.log(`set firstLevel:${newLevel}`);
|
29145
|
+
this.logger.log(`set firstLevel:${newLevel}`);
|
28152
29146
|
this.levelController.firstLevel = newLevel;
|
28153
29147
|
}
|
28154
29148
|
|
@@ -28173,7 +29167,7 @@ class Hls {
|
|
28173
29167
|
* (determined from download of first segment)
|
28174
29168
|
*/
|
28175
29169
|
set startLevel(newLevel) {
|
28176
|
-
logger.log(`set startLevel:${newLevel}`);
|
29170
|
+
this.logger.log(`set startLevel:${newLevel}`);
|
28177
29171
|
// if not in automatic start level detection, ensure startLevel is greater than minAutoLevel
|
28178
29172
|
if (newLevel !== -1) {
|
28179
29173
|
newLevel = Math.max(newLevel, this.minAutoLevel);
|
@@ -28248,7 +29242,7 @@ class Hls {
|
|
28248
29242
|
*/
|
28249
29243
|
set autoLevelCapping(newLevel) {
|
28250
29244
|
if (this._autoLevelCapping !== newLevel) {
|
28251
|
-
logger.log(`set autoLevelCapping:${newLevel}`);
|
29245
|
+
this.logger.log(`set autoLevelCapping:${newLevel}`);
|
28252
29246
|
this._autoLevelCapping = newLevel;
|
28253
29247
|
this.levelController.checkMaxAutoUpdated();
|
28254
29248
|
}
|
@@ -28353,6 +29347,9 @@ class Hls {
|
|
28353
29347
|
get mainForwardBufferInfo() {
|
28354
29348
|
return this.streamController.getMainFwdBufferInfo();
|
28355
29349
|
}
|
29350
|
+
get maxBufferLength() {
|
29351
|
+
return this.streamController.maxBufferLength;
|
29352
|
+
}
|
28356
29353
|
|
28357
29354
|
/**
|
28358
29355
|
* Find and select the best matching audio track, making a level switch when a Group change is necessary.
|