hls.js 1.5.7-0.canary.10040 → 1.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -2
- package/dist/hls-demo.js +0 -10
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +1283 -2293
- package/dist/hls.js.d.ts +84 -97
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +1030 -1435
- 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 +809 -1209
- 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 +1039 -2030
- 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 +2 -3
- package/src/controller/abr-controller.ts +20 -24
- package/src/controller/audio-stream-controller.ts +74 -68
- package/src/controller/audio-track-controller.ts +1 -1
- package/src/controller/base-playlist-controller.ts +8 -20
- package/src/controller/base-stream-controller.ts +36 -157
- package/src/controller/buffer-controller.ts +99 -226
- package/src/controller/buffer-operation-queue.ts +19 -16
- package/src/controller/cap-level-controller.ts +2 -2
- package/src/controller/cmcd-controller.ts +6 -27
- package/src/controller/content-steering-controller.ts +6 -8
- package/src/controller/eme-controller.ts +22 -9
- package/src/controller/error-controller.ts +8 -6
- package/src/controller/fps-controller.ts +3 -2
- package/src/controller/fragment-tracker.ts +11 -15
- package/src/controller/gap-controller.ts +16 -43
- package/src/controller/latency-controller.ts +11 -9
- package/src/controller/level-controller.ts +18 -12
- package/src/controller/stream-controller.ts +31 -36
- package/src/controller/subtitle-stream-controller.ts +40 -28
- package/src/controller/subtitle-track-controller.ts +3 -5
- package/src/controller/timeline-controller.ts +30 -23
- package/src/crypt/aes-crypto.ts +2 -21
- package/src/crypt/decrypter.ts +18 -32
- package/src/crypt/fast-aes-key.ts +5 -24
- package/src/demux/audio/adts.ts +4 -9
- package/src/demux/sample-aes.ts +0 -2
- package/src/demux/transmuxer-interface.ts +12 -4
- package/src/demux/transmuxer-worker.ts +4 -4
- package/src/demux/transmuxer.ts +3 -16
- package/src/demux/tsdemuxer.ts +37 -71
- package/src/demux/video/avc-video-parser.ts +119 -208
- package/src/demux/video/base-video-parser.ts +2 -134
- package/src/demux/video/exp-golomb.ts +208 -0
- package/src/events.ts +0 -7
- package/src/hls.ts +37 -49
- package/src/loader/fragment-loader.ts +2 -9
- package/src/loader/key-loader.ts +0 -2
- package/src/loader/level-key.ts +9 -10
- package/src/loader/playlist-loader.ts +5 -4
- package/src/remux/mp4-generator.ts +1 -196
- package/src/remux/mp4-remuxer.ts +7 -23
- package/src/task-loop.ts +2 -5
- package/src/types/component-api.ts +0 -2
- package/src/types/demuxer.ts +0 -3
- package/src/types/events.ts +0 -4
- package/src/utils/buffer-helper.ts +31 -12
- package/src/utils/codecs.ts +5 -34
- package/src/utils/logger.ts +24 -54
- package/src/utils/mp4-tools.ts +2 -4
- package/src/crypt/decrypter-aes-mode.ts +0 -4
- package/src/demux/video/hevc-video-parser.ts +0 -746
- package/src/utils/encryption-methods-util.ts +0 -21
package/dist/hls.mjs
CHANGED
@@ -256,7 +256,6 @@ let Events = /*#__PURE__*/function (Events) {
|
|
256
256
|
Events["MEDIA_ATTACHED"] = "hlsMediaAttached";
|
257
257
|
Events["MEDIA_DETACHING"] = "hlsMediaDetaching";
|
258
258
|
Events["MEDIA_DETACHED"] = "hlsMediaDetached";
|
259
|
-
Events["MEDIA_ENDED"] = "hlsMediaEnded";
|
260
259
|
Events["BUFFER_RESET"] = "hlsBufferReset";
|
261
260
|
Events["BUFFER_CODECS"] = "hlsBufferCodecs";
|
262
261
|
Events["BUFFER_CREATED"] = "hlsBufferCreated";
|
@@ -370,6 +369,58 @@ let ErrorDetails = /*#__PURE__*/function (ErrorDetails) {
|
|
370
369
|
return ErrorDetails;
|
371
370
|
}({});
|
372
371
|
|
372
|
+
const noop = function noop() {};
|
373
|
+
const fakeLogger = {
|
374
|
+
trace: noop,
|
375
|
+
debug: noop,
|
376
|
+
log: noop,
|
377
|
+
warn: noop,
|
378
|
+
info: noop,
|
379
|
+
error: noop
|
380
|
+
};
|
381
|
+
let exportedLogger = fakeLogger;
|
382
|
+
|
383
|
+
// let lastCallTime;
|
384
|
+
// function formatMsgWithTimeInfo(type, msg) {
|
385
|
+
// const now = Date.now();
|
386
|
+
// const diff = lastCallTime ? '+' + (now - lastCallTime) : '0';
|
387
|
+
// lastCallTime = now;
|
388
|
+
// msg = (new Date(now)).toISOString() + ' | [' + type + '] > ' + msg + ' ( ' + diff + ' ms )';
|
389
|
+
// return msg;
|
390
|
+
// }
|
391
|
+
|
392
|
+
function consolePrintFn(type) {
|
393
|
+
const func = self.console[type];
|
394
|
+
if (func) {
|
395
|
+
return func.bind(self.console, `[${type}] >`);
|
396
|
+
}
|
397
|
+
return noop;
|
398
|
+
}
|
399
|
+
function exportLoggerFunctions(debugConfig, ...functions) {
|
400
|
+
functions.forEach(function (type) {
|
401
|
+
exportedLogger[type] = debugConfig[type] ? debugConfig[type].bind(debugConfig) : consolePrintFn(type);
|
402
|
+
});
|
403
|
+
}
|
404
|
+
function enableLogs(debugConfig, id) {
|
405
|
+
// check that console is available
|
406
|
+
if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
|
407
|
+
exportLoggerFunctions(debugConfig,
|
408
|
+
// Remove out from list here to hard-disable a log-level
|
409
|
+
// 'trace',
|
410
|
+
'debug', 'log', 'info', 'warn', 'error');
|
411
|
+
// Some browsers don't allow to use bind on console object anyway
|
412
|
+
// fallback to default if needed
|
413
|
+
try {
|
414
|
+
exportedLogger.log(`Debug logs enabled for "${id}" in hls.js version ${"1.5.7"}`);
|
415
|
+
} catch (e) {
|
416
|
+
exportedLogger = fakeLogger;
|
417
|
+
}
|
418
|
+
} else {
|
419
|
+
exportedLogger = fakeLogger;
|
420
|
+
}
|
421
|
+
}
|
422
|
+
const logger = exportedLogger;
|
423
|
+
|
373
424
|
const DECIMAL_RESOLUTION_REGEX = /^(\d+)x(\d+)$/;
|
374
425
|
const ATTR_LIST_REGEX = /(.+?)=(".*?"|.*?)(?:,|$)/g;
|
375
426
|
|
@@ -451,79 +502,6 @@ class AttrList {
|
|
451
502
|
}
|
452
503
|
}
|
453
504
|
|
454
|
-
class Logger {
|
455
|
-
constructor(label, logger) {
|
456
|
-
this.trace = void 0;
|
457
|
-
this.debug = void 0;
|
458
|
-
this.log = void 0;
|
459
|
-
this.warn = void 0;
|
460
|
-
this.info = void 0;
|
461
|
-
this.error = void 0;
|
462
|
-
const lb = `[${label}]:`;
|
463
|
-
this.trace = noop;
|
464
|
-
this.debug = logger.debug.bind(null, lb);
|
465
|
-
this.log = logger.log.bind(null, lb);
|
466
|
-
this.warn = logger.warn.bind(null, lb);
|
467
|
-
this.info = logger.info.bind(null, lb);
|
468
|
-
this.error = logger.error.bind(null, lb);
|
469
|
-
}
|
470
|
-
}
|
471
|
-
const noop = function noop() {};
|
472
|
-
const fakeLogger = {
|
473
|
-
trace: noop,
|
474
|
-
debug: noop,
|
475
|
-
log: noop,
|
476
|
-
warn: noop,
|
477
|
-
info: noop,
|
478
|
-
error: noop
|
479
|
-
};
|
480
|
-
function createLogger() {
|
481
|
-
return _extends({}, fakeLogger);
|
482
|
-
}
|
483
|
-
|
484
|
-
// let lastCallTime;
|
485
|
-
// function formatMsgWithTimeInfo(type, msg) {
|
486
|
-
// const now = Date.now();
|
487
|
-
// const diff = lastCallTime ? '+' + (now - lastCallTime) : '0';
|
488
|
-
// lastCallTime = now;
|
489
|
-
// msg = (new Date(now)).toISOString() + ' | [' + type + '] > ' + msg + ' ( ' + diff + ' ms )';
|
490
|
-
// return msg;
|
491
|
-
// }
|
492
|
-
|
493
|
-
function consolePrintFn(type, id) {
|
494
|
-
const func = self.console[type];
|
495
|
-
return func ? func.bind(self.console, `${id ? '[' + id + '] ' : ''}[${type}] >`) : noop;
|
496
|
-
}
|
497
|
-
function getLoggerFn(key, debugConfig, id) {
|
498
|
-
return debugConfig[key] ? debugConfig[key].bind(debugConfig) : consolePrintFn(key, id);
|
499
|
-
}
|
500
|
-
const exportedLogger = createLogger();
|
501
|
-
function enableLogs(debugConfig, context, id) {
|
502
|
-
// check that console is available
|
503
|
-
const newLogger = createLogger();
|
504
|
-
if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
|
505
|
-
const keys = [
|
506
|
-
// Remove out from list here to hard-disable a log-level
|
507
|
-
// 'trace',
|
508
|
-
'debug', 'log', 'info', 'warn', 'error'];
|
509
|
-
keys.forEach(key => {
|
510
|
-
newLogger[key] = getLoggerFn(key, debugConfig, id);
|
511
|
-
});
|
512
|
-
// Some browsers don't allow to use bind on console object anyway
|
513
|
-
// fallback to default if needed
|
514
|
-
try {
|
515
|
-
newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.5.7-0.canary.10040"}`);
|
516
|
-
} catch (e) {
|
517
|
-
/* log fn threw an exception. All logger methods are no-ops. */
|
518
|
-
return createLogger();
|
519
|
-
}
|
520
|
-
}
|
521
|
-
// global exported logger uses the log methods from last call to `enableLogs`
|
522
|
-
_extends(exportedLogger, newLogger);
|
523
|
-
return newLogger;
|
524
|
-
}
|
525
|
-
const logger = exportedLogger;
|
526
|
-
|
527
505
|
// Avoid exporting const enum so that these values can be inlined
|
528
506
|
|
529
507
|
function isDateRangeCueAttribute(attrName) {
|
@@ -1058,26 +1036,6 @@ function strToUtf8array(str) {
|
|
1058
1036
|
return Uint8Array.from(unescape(encodeURIComponent(str)), c => c.charCodeAt(0));
|
1059
1037
|
}
|
1060
1038
|
|
1061
|
-
var DecrypterAesMode = {
|
1062
|
-
cbc: 0,
|
1063
|
-
ctr: 1
|
1064
|
-
};
|
1065
|
-
|
1066
|
-
function isFullSegmentEncryption(method) {
|
1067
|
-
return method === 'AES-128' || method === 'AES-256' || method === 'AES-256-CTR';
|
1068
|
-
}
|
1069
|
-
function getAesModeFromFullSegmentMethod(method) {
|
1070
|
-
switch (method) {
|
1071
|
-
case 'AES-128':
|
1072
|
-
case 'AES-256':
|
1073
|
-
return DecrypterAesMode.cbc;
|
1074
|
-
case 'AES-256-CTR':
|
1075
|
-
return DecrypterAesMode.ctr;
|
1076
|
-
default:
|
1077
|
-
throw new Error(`invalid full segment method ${method}`);
|
1078
|
-
}
|
1079
|
-
}
|
1080
|
-
|
1081
1039
|
/** returns `undefined` is `self` is missing, e.g. in node */
|
1082
1040
|
const optionalSelf = typeof self !== 'undefined' ? self : undefined;
|
1083
1041
|
|
@@ -1826,7 +1784,7 @@ function parseStsd(stsd) {
|
|
1826
1784
|
{
|
1827
1785
|
const codecBox = findBox(sampleEntries, [fourCC])[0];
|
1828
1786
|
const esdsBox = findBox(codecBox.subarray(28), ['esds'])[0];
|
1829
|
-
if (esdsBox && esdsBox.length >
|
1787
|
+
if (esdsBox && esdsBox.length > 12) {
|
1830
1788
|
let i = 4;
|
1831
1789
|
// ES Descriptor tag
|
1832
1790
|
if (esdsBox[i++] !== 0x03) {
|
@@ -1941,9 +1899,7 @@ function parseStsd(stsd) {
|
|
1941
1899
|
}
|
1942
1900
|
function skipBERInteger(bytes, i) {
|
1943
1901
|
const limit = i + 5;
|
1944
|
-
while (bytes[i++] & 0x80 && i < limit) {
|
1945
|
-
/* do nothing */
|
1946
|
-
}
|
1902
|
+
while (bytes[i++] & 0x80 && i < limit) {}
|
1947
1903
|
return i;
|
1948
1904
|
}
|
1949
1905
|
function toHex(x) {
|
@@ -2732,12 +2688,12 @@ class LevelKey {
|
|
2732
2688
|
this.keyFormatVersions = formatversions;
|
2733
2689
|
this.iv = iv;
|
2734
2690
|
this.encrypted = method ? method !== 'NONE' : false;
|
2735
|
-
this.isCommonEncryption = this.encrypted &&
|
2691
|
+
this.isCommonEncryption = this.encrypted && method !== 'AES-128';
|
2736
2692
|
}
|
2737
2693
|
isSupported() {
|
2738
2694
|
// If it's Segment encryption or No encryption, just select that key system
|
2739
2695
|
if (this.method) {
|
2740
|
-
if (
|
2696
|
+
if (this.method === 'AES-128' || this.method === 'NONE') {
|
2741
2697
|
return true;
|
2742
2698
|
}
|
2743
2699
|
if (this.keyFormat === 'identity') {
|
@@ -2759,13 +2715,14 @@ class LevelKey {
|
|
2759
2715
|
if (!this.encrypted || !this.uri) {
|
2760
2716
|
return null;
|
2761
2717
|
}
|
2762
|
-
if (
|
2718
|
+
if (this.method === 'AES-128' && this.uri && !this.iv) {
|
2763
2719
|
if (typeof sn !== 'number') {
|
2764
2720
|
// We are fetching decryption data for a initialization segment
|
2765
|
-
// If the segment was encrypted with AES-128
|
2721
|
+
// If the segment was encrypted with AES-128
|
2766
2722
|
// It must have an IV defined. We cannot substitute the Segment Number in.
|
2767
|
-
|
2768
|
-
|
2723
|
+
if (this.method === 'AES-128' && !this.iv) {
|
2724
|
+
logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
|
2725
|
+
}
|
2769
2726
|
// Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
|
2770
2727
|
sn = 0;
|
2771
2728
|
}
|
@@ -3044,28 +3001,23 @@ function getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource =
|
|
3044
3001
|
if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) {
|
3045
3002
|
return CODEC_COMPATIBLE_NAMES[lowerCaseCodec];
|
3046
3003
|
}
|
3004
|
+
|
3005
|
+
// Idealy fLaC and Opus would be first (spec-compliant) but
|
3006
|
+
// some browsers will report that fLaC is supported then fail.
|
3007
|
+
// see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
|
3047
3008
|
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
|
3051
3009
|
flac: ['flac', 'fLaC', 'FLAC'],
|
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']
|
3010
|
+
opus: ['opus', 'Opus']
|
3056
3011
|
}[lowerCaseCodec];
|
3057
3012
|
for (let i = 0; i < codecsToCheck.length; i++) {
|
3058
|
-
var _getMediaSource;
|
3059
3013
|
if (isCodecMediaSourceSupported(codecsToCheck[i], 'audio', preferManagedMediaSource)) {
|
3060
3014
|
CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
|
3061
3015
|
return codecsToCheck[i];
|
3062
|
-
} else if (codecsToCheck[i] === 'mp3' && (_getMediaSource = getMediaSource(preferManagedMediaSource)) != null && _getMediaSource.isTypeSupported('audio/mpeg')) {
|
3063
|
-
return '';
|
3064
3016
|
}
|
3065
3017
|
}
|
3066
3018
|
return lowerCaseCodec;
|
3067
3019
|
}
|
3068
|
-
const AUDIO_CODEC_REGEXP = /flac|opus
|
3020
|
+
const AUDIO_CODEC_REGEXP = /flac|opus/i;
|
3069
3021
|
function getCodecCompatibleName(codec, preferManagedMediaSource = true) {
|
3070
3022
|
return codec.replace(AUDIO_CODEC_REGEXP, m => getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource));
|
3071
3023
|
}
|
@@ -3088,16 +3040,6 @@ function convertAVC1ToAVCOTI(codec) {
|
|
3088
3040
|
}
|
3089
3041
|
return codec;
|
3090
3042
|
}
|
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
|
-
}
|
3101
3043
|
|
3102
3044
|
const MASTER_PLAYLIST_REGEX = /#EXT-X-STREAM-INF:([^\r\n]*)(?:[\r\n](?:#[^\r\n]*)?)*([^\r\n]+)|#EXT-X-(SESSION-DATA|SESSION-KEY|DEFINE|CONTENT-STEERING|START):([^\r\n]*)[\r\n]+/g;
|
3103
3045
|
const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g;
|
@@ -3947,10 +3889,10 @@ class PlaylistLoader {
|
|
3947
3889
|
const loaderContext = loader.context;
|
3948
3890
|
if (loaderContext && loaderContext.url === context.url && loaderContext.level === context.level) {
|
3949
3891
|
// same URL can't overlap
|
3950
|
-
|
3892
|
+
logger.trace('[playlist-loader]: playlist request ongoing');
|
3951
3893
|
return;
|
3952
3894
|
}
|
3953
|
-
|
3895
|
+
logger.log(`[playlist-loader]: aborting previous loader for type: ${context.type}`);
|
3954
3896
|
loader.abort();
|
3955
3897
|
}
|
3956
3898
|
|
@@ -4060,7 +4002,7 @@ class PlaylistLoader {
|
|
4060
4002
|
// alt audio rendition in which quality levels (main)
|
4061
4003
|
// contains both audio+video. but with mixed audio track not signaled
|
4062
4004
|
if (!embeddedAudioFound && levels[0].audioCodec && !levels[0].attrs.AUDIO) {
|
4063
|
-
|
4005
|
+
logger.log('[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one');
|
4064
4006
|
audioTracks.unshift({
|
4065
4007
|
type: 'main',
|
4066
4008
|
name: 'main',
|
@@ -4159,7 +4101,7 @@ class PlaylistLoader {
|
|
4159
4101
|
message += ` id: ${context.id} group-id: "${context.groupId}"`;
|
4160
4102
|
}
|
4161
4103
|
const error = new Error(message);
|
4162
|
-
|
4104
|
+
logger.warn(`[playlist-loader]: ${message}`);
|
4163
4105
|
let details = ErrorDetails.UNKNOWN;
|
4164
4106
|
let fatal = false;
|
4165
4107
|
const loader = this.getInternalLoader(context);
|
@@ -4764,47 +4706,7 @@ class LatencyController {
|
|
4764
4706
|
this.currentTime = 0;
|
4765
4707
|
this.stallCount = 0;
|
4766
4708
|
this._latency = null;
|
4767
|
-
this.
|
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
|
-
};
|
4709
|
+
this.timeupdateHandler = () => this.timeupdate();
|
4808
4710
|
this.hls = hls;
|
4809
4711
|
this.config = hls.config;
|
4810
4712
|
this.registerListeners();
|
@@ -4896,7 +4798,7 @@ class LatencyController {
|
|
4896
4798
|
this.onMediaDetaching();
|
4897
4799
|
this.levelDetails = null;
|
4898
4800
|
// @ts-ignore
|
4899
|
-
this.hls = null;
|
4801
|
+
this.hls = this.timeupdateHandler = null;
|
4900
4802
|
}
|
4901
4803
|
registerListeners() {
|
4902
4804
|
this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
@@ -4914,11 +4816,11 @@ class LatencyController {
|
|
4914
4816
|
}
|
4915
4817
|
onMediaAttached(event, data) {
|
4916
4818
|
this.media = data.media;
|
4917
|
-
this.media.addEventListener('timeupdate', this.
|
4819
|
+
this.media.addEventListener('timeupdate', this.timeupdateHandler);
|
4918
4820
|
}
|
4919
4821
|
onMediaDetaching() {
|
4920
4822
|
if (this.media) {
|
4921
|
-
this.media.removeEventListener('timeupdate', this.
|
4823
|
+
this.media.removeEventListener('timeupdate', this.timeupdateHandler);
|
4922
4824
|
this.media = null;
|
4923
4825
|
}
|
4924
4826
|
}
|
@@ -4932,10 +4834,10 @@ class LatencyController {
|
|
4932
4834
|
}) {
|
4933
4835
|
this.levelDetails = details;
|
4934
4836
|
if (details.advanced) {
|
4935
|
-
this.
|
4837
|
+
this.timeupdate();
|
4936
4838
|
}
|
4937
4839
|
if (!details.live && this.media) {
|
4938
|
-
this.media.removeEventListener('timeupdate', this.
|
4840
|
+
this.media.removeEventListener('timeupdate', this.timeupdateHandler);
|
4939
4841
|
}
|
4940
4842
|
}
|
4941
4843
|
onError(event, data) {
|
@@ -4945,7 +4847,48 @@ class LatencyController {
|
|
4945
4847
|
}
|
4946
4848
|
this.stallCount++;
|
4947
4849
|
if ((_this$levelDetails = this.levelDetails) != null && _this$levelDetails.live) {
|
4948
|
-
|
4850
|
+
logger.warn('[playback-rate-controller]: Stall detected, adjusting target latency');
|
4851
|
+
}
|
4852
|
+
}
|
4853
|
+
timeupdate() {
|
4854
|
+
const {
|
4855
|
+
media,
|
4856
|
+
levelDetails
|
4857
|
+
} = this;
|
4858
|
+
if (!media || !levelDetails) {
|
4859
|
+
return;
|
4860
|
+
}
|
4861
|
+
this.currentTime = media.currentTime;
|
4862
|
+
const latency = this.computeLatency();
|
4863
|
+
if (latency === null) {
|
4864
|
+
return;
|
4865
|
+
}
|
4866
|
+
this._latency = latency;
|
4867
|
+
|
4868
|
+
// Adapt playbackRate to meet target latency in low-latency mode
|
4869
|
+
const {
|
4870
|
+
lowLatencyMode,
|
4871
|
+
maxLiveSyncPlaybackRate
|
4872
|
+
} = this.config;
|
4873
|
+
if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
|
4874
|
+
return;
|
4875
|
+
}
|
4876
|
+
const targetLatency = this.targetLatency;
|
4877
|
+
if (targetLatency === null) {
|
4878
|
+
return;
|
4879
|
+
}
|
4880
|
+
const distanceFromTarget = latency - targetLatency;
|
4881
|
+
// Only adjust playbackRate when within one target duration of targetLatency
|
4882
|
+
// and more than one second from under-buffering.
|
4883
|
+
// Playback further than one target duration from target can be considered DVR playback.
|
4884
|
+
const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
|
4885
|
+
const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
|
4886
|
+
if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
|
4887
|
+
const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
|
4888
|
+
const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
|
4889
|
+
media.playbackRate = Math.min(max, Math.max(1, rate));
|
4890
|
+
} else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
|
4891
|
+
media.playbackRate = 1;
|
4949
4892
|
}
|
4950
4893
|
}
|
4951
4894
|
estimateLiveEdge() {
|
@@ -5717,13 +5660,18 @@ var ErrorActionFlags = {
|
|
5717
5660
|
MoveAllAlternatesMatchingHDCP: 2,
|
5718
5661
|
SwitchToSDR: 4
|
5719
5662
|
}; // Reserved for future use
|
5720
|
-
class ErrorController
|
5663
|
+
class ErrorController {
|
5721
5664
|
constructor(hls) {
|
5722
|
-
super('error-controller', hls.logger);
|
5723
5665
|
this.hls = void 0;
|
5724
5666
|
this.playlistError = 0;
|
5725
5667
|
this.penalizedRenditions = {};
|
5668
|
+
this.log = void 0;
|
5669
|
+
this.warn = void 0;
|
5670
|
+
this.error = void 0;
|
5726
5671
|
this.hls = hls;
|
5672
|
+
this.log = logger.log.bind(logger, `[info]:`);
|
5673
|
+
this.warn = logger.warn.bind(logger, `[warning]:`);
|
5674
|
+
this.error = logger.error.bind(logger, `[error]:`);
|
5727
5675
|
this.registerListeners();
|
5728
5676
|
}
|
5729
5677
|
registerListeners() {
|
@@ -6075,13 +6023,16 @@ class ErrorController extends Logger {
|
|
6075
6023
|
}
|
6076
6024
|
}
|
6077
6025
|
|
6078
|
-
class BasePlaylistController
|
6026
|
+
class BasePlaylistController {
|
6079
6027
|
constructor(hls, logPrefix) {
|
6080
|
-
super(logPrefix, hls.logger);
|
6081
6028
|
this.hls = void 0;
|
6082
6029
|
this.timer = -1;
|
6083
6030
|
this.requestScheduled = -1;
|
6084
6031
|
this.canLoad = false;
|
6032
|
+
this.log = void 0;
|
6033
|
+
this.warn = void 0;
|
6034
|
+
this.log = logger.log.bind(logger, `${logPrefix}:`);
|
6035
|
+
this.warn = logger.warn.bind(logger, `${logPrefix}:`);
|
6085
6036
|
this.hls = hls;
|
6086
6037
|
}
|
6087
6038
|
destroy() {
|
@@ -6114,7 +6065,7 @@ class BasePlaylistController extends Logger {
|
|
6114
6065
|
try {
|
6115
6066
|
uri = new self.URL(attr.URI, previous.url).href;
|
6116
6067
|
} catch (error) {
|
6117
|
-
|
6068
|
+
logger.warn(`Could not construct new URL for Rendition Report: ${error}`);
|
6118
6069
|
uri = attr.URI || '';
|
6119
6070
|
}
|
6120
6071
|
// Use exact match. Otherwise, the last partial match, if any, will be used
|
@@ -6201,12 +6152,7 @@ class BasePlaylistController extends Logger {
|
|
6201
6152
|
const cdnAge = lastAdvanced + details.ageHeader;
|
6202
6153
|
let currentGoal = Math.min(cdnAge - details.partTarget, details.targetduration * 1.5);
|
6203
6154
|
if (currentGoal > 0) {
|
6204
|
-
if (
|
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) {
|
6155
|
+
if (previousDetails && currentGoal > previousDetails.tuneInGoal) {
|
6210
6156
|
// If we attempted to get the next or latest playlist update, but currentGoal increased,
|
6211
6157
|
// then we either can't catchup, or the "age" header cannot be trusted.
|
6212
6158
|
this.warn(`CDN Tune-in goal increased from: ${previousDetails.tuneInGoal} to: ${currentGoal} with playlist age: ${details.age}`);
|
@@ -6878,9 +6824,8 @@ function searchDownAndUpList(arr, searchIndex, predicate) {
|
|
6878
6824
|
return -1;
|
6879
6825
|
}
|
6880
6826
|
|
6881
|
-
class AbrController
|
6827
|
+
class AbrController {
|
6882
6828
|
constructor(_hls) {
|
6883
|
-
super('abr', _hls.logger);
|
6884
6829
|
this.hls = void 0;
|
6885
6830
|
this.lastLevelLoadSec = 0;
|
6886
6831
|
this.lastLoadedFragLevel = -1;
|
@@ -6994,7 +6939,7 @@ class AbrController extends Logger {
|
|
6994
6939
|
this.resetEstimator(nextLoadLevelBitrate);
|
6995
6940
|
}
|
6996
6941
|
this.clearTimer();
|
6997
|
-
|
6942
|
+
logger.warn(`[abr] Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
|
6998
6943
|
Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s
|
6999
6944
|
Estimated load time for current fragment: ${fragLoadedDelay.toFixed(3)} s
|
7000
6945
|
Estimated load time for down switch fragment: ${fragLevelNextLoadedDelay.toFixed(3)} s
|
@@ -7014,7 +6959,7 @@ class AbrController extends Logger {
|
|
7014
6959
|
}
|
7015
6960
|
resetEstimator(abrEwmaDefaultEstimate) {
|
7016
6961
|
if (abrEwmaDefaultEstimate) {
|
7017
|
-
|
6962
|
+
logger.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
|
7018
6963
|
this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate;
|
7019
6964
|
}
|
7020
6965
|
this.firstSelection = -1;
|
@@ -7246,7 +7191,7 @@ class AbrController extends Logger {
|
|
7246
7191
|
}
|
7247
7192
|
const firstLevel = this.hls.firstLevel;
|
7248
7193
|
const clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel);
|
7249
|
-
|
7194
|
+
logger.warn(`[abr] Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
|
7250
7195
|
return clamped;
|
7251
7196
|
}
|
7252
7197
|
get forcedAutoLevel() {
|
@@ -7292,9 +7237,6 @@ class AbrController extends Logger {
|
|
7292
7237
|
partCurrent,
|
7293
7238
|
hls
|
7294
7239
|
} = this;
|
7295
|
-
if (hls.levels.length <= 1) {
|
7296
|
-
return hls.loadLevel;
|
7297
|
-
}
|
7298
7240
|
const {
|
7299
7241
|
maxAutoLevel,
|
7300
7242
|
config,
|
@@ -7327,13 +7269,13 @@ class AbrController extends Logger {
|
|
7327
7269
|
// cap maxLoadingDelay and ensure it is not bigger 'than bitrate test' frag duration
|
7328
7270
|
const maxLoadingDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxLoadingDelay) : config.maxLoadingDelay;
|
7329
7271
|
maxStarvationDelay = maxLoadingDelay - bitrateTestDelay;
|
7330
|
-
|
7272
|
+
logger.info(`[abr] bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
|
7331
7273
|
// don't use conservative factor on bitrate test
|
7332
7274
|
bwFactor = bwUpFactor = 1;
|
7333
7275
|
}
|
7334
7276
|
}
|
7335
7277
|
const bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor);
|
7336
|
-
|
7278
|
+
logger.info(`[abr] ${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
|
7337
7279
|
if (bestLevel > -1) {
|
7338
7280
|
return bestLevel;
|
7339
7281
|
}
|
@@ -7407,7 +7349,7 @@ class AbrController extends Logger {
|
|
7407
7349
|
currentVideoRange = preferHDR ? videoRanges[videoRanges.length - 1] : videoRanges[0];
|
7408
7350
|
currentFrameRate = minFramerate;
|
7409
7351
|
currentBw = Math.max(currentBw, minBitrate);
|
7410
|
-
|
7352
|
+
logger.log(`[abr] picked start tier ${JSON.stringify(startTier)}`);
|
7411
7353
|
} else {
|
7412
7354
|
currentCodecSet = level == null ? void 0 : level.codecSet;
|
7413
7355
|
currentVideoRange = level == null ? void 0 : level.videoRange;
|
@@ -7434,11 +7376,11 @@ class AbrController extends Logger {
|
|
7434
7376
|
const levels = this.hls.levels;
|
7435
7377
|
const index = levels.indexOf(levelInfo);
|
7436
7378
|
if (decodingInfo.error) {
|
7437
|
-
|
7379
|
+
logger.warn(`[abr] MediaCapabilities decodingInfo error: "${decodingInfo.error}" for level ${index} ${JSON.stringify(decodingInfo)}`);
|
7438
7380
|
} else if (!decodingInfo.supported) {
|
7439
|
-
|
7381
|
+
logger.warn(`[abr] Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify(decodingInfo)}`);
|
7440
7382
|
if (index > -1 && levels.length > 1) {
|
7441
|
-
|
7383
|
+
logger.log(`[abr] Removing unsupported level ${index}`);
|
7442
7384
|
this.hls.removeLevel(index);
|
7443
7385
|
}
|
7444
7386
|
}
|
@@ -7485,9 +7427,9 @@ class AbrController extends Logger {
|
|
7485
7427
|
const forcedAutoLevel = this.forcedAutoLevel;
|
7486
7428
|
if (i !== loadLevel && (forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)) {
|
7487
7429
|
if (levelsSkipped.length) {
|
7488
|
-
|
7430
|
+
logger.trace(`[abr] Skipped level(s) ${levelsSkipped.join(',')} of ${maxAutoLevel} max with CODECS and VIDEO-RANGE:"${levels[levelsSkipped[0]].codecs}" ${levels[levelsSkipped[0]].videoRange}; not compatible with "${level.codecs}" ${currentVideoRange}`);
|
7489
7431
|
}
|
7490
|
-
|
7432
|
+
logger.info(`[abr] switch candidate:${selectionBaseLevel}->${i} adjustedbw(${Math.round(adjustedbw)})-bitrate=${Math.round(adjustedbw - bitrate)} ttfb:${ttfbEstimateSec.toFixed(1)} avgDuration:${avgDuration.toFixed(1)} maxFetchDuration:${maxFetchDuration.toFixed(1)} fetchDuration:${fetchDuration.toFixed(1)} firstSelection:${firstSelection} codecSet:${currentCodecSet} videoRange:${currentVideoRange} hls.loadLevel:${loadLevel}`);
|
7491
7433
|
}
|
7492
7434
|
if (firstSelection) {
|
7493
7435
|
this.firstSelection = i;
|
@@ -7541,9 +7483,8 @@ class AbrController extends Logger {
|
|
7541
7483
|
* we are limiting the task execution per call stack to exactly one, but scheduling/post-poning further
|
7542
7484
|
* task processing on the next main loop iteration (also known as "next tick" in the Node/JS runtime lingo).
|
7543
7485
|
*/
|
7544
|
-
class TaskLoop
|
7545
|
-
constructor(
|
7546
|
-
super(label, logger);
|
7486
|
+
class TaskLoop {
|
7487
|
+
constructor() {
|
7547
7488
|
this._boundTick = void 0;
|
7548
7489
|
this._tickTimer = null;
|
7549
7490
|
this._tickInterval = null;
|
@@ -7705,16 +7646,13 @@ class FragmentTracker {
|
|
7705
7646
|
* If not found any Fragment, return null
|
7706
7647
|
*/
|
7707
7648
|
getBufferedFrag(position, levelType) {
|
7708
|
-
return this.getFragAtPos(position, levelType, true);
|
7709
|
-
}
|
7710
|
-
getFragAtPos(position, levelType, buffered) {
|
7711
7649
|
const {
|
7712
7650
|
fragments
|
7713
7651
|
} = this;
|
7714
7652
|
const keys = Object.keys(fragments);
|
7715
7653
|
for (let i = keys.length; i--;) {
|
7716
7654
|
const fragmentEntity = fragments[keys[i]];
|
7717
|
-
if ((fragmentEntity == null ? void 0 : fragmentEntity.body.type) === levelType &&
|
7655
|
+
if ((fragmentEntity == null ? void 0 : fragmentEntity.body.type) === levelType && fragmentEntity.buffered) {
|
7718
7656
|
const frag = fragmentEntity.body;
|
7719
7657
|
if (frag.start <= position && position <= frag.end) {
|
7720
7658
|
return frag;
|
@@ -7969,8 +7907,7 @@ class FragmentTracker {
|
|
7969
7907
|
const {
|
7970
7908
|
frag,
|
7971
7909
|
part,
|
7972
|
-
timeRanges
|
7973
|
-
type
|
7910
|
+
timeRanges
|
7974
7911
|
} = data;
|
7975
7912
|
if (frag.sn === 'initSegment') {
|
7976
7913
|
return;
|
@@ -7985,8 +7922,10 @@ class FragmentTracker {
|
|
7985
7922
|
}
|
7986
7923
|
// Store the latest timeRanges loaded in the buffer
|
7987
7924
|
this.timeRanges = timeRanges;
|
7988
|
-
|
7989
|
-
|
7925
|
+
Object.keys(timeRanges).forEach(elementaryStream => {
|
7926
|
+
const timeRange = timeRanges[elementaryStream];
|
7927
|
+
this.detectEvictedFragments(elementaryStream, timeRange, playlistType, part);
|
7928
|
+
});
|
7990
7929
|
}
|
7991
7930
|
onFragBuffered(event, data) {
|
7992
7931
|
this.detectPartialFragments(data);
|
@@ -8064,29 +8003,40 @@ class BufferHelper {
|
|
8064
8003
|
* Return true if `media`'s buffered include `position`
|
8065
8004
|
*/
|
8066
8005
|
static isBuffered(media, position) {
|
8067
|
-
|
8068
|
-
|
8069
|
-
|
8070
|
-
|
8071
|
-
|
8006
|
+
try {
|
8007
|
+
if (media) {
|
8008
|
+
const buffered = BufferHelper.getBuffered(media);
|
8009
|
+
for (let i = 0; i < buffered.length; i++) {
|
8010
|
+
if (position >= buffered.start(i) && position <= buffered.end(i)) {
|
8011
|
+
return true;
|
8012
|
+
}
|
8072
8013
|
}
|
8073
8014
|
}
|
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
|
8074
8019
|
}
|
8075
8020
|
return false;
|
8076
8021
|
}
|
8077
8022
|
static bufferInfo(media, pos, maxHoleDuration) {
|
8078
|
-
|
8079
|
-
|
8080
|
-
|
8023
|
+
try {
|
8024
|
+
if (media) {
|
8025
|
+
const vbuffered = BufferHelper.getBuffered(media);
|
8081
8026
|
const buffered = [];
|
8082
|
-
|
8027
|
+
let i;
|
8028
|
+
for (i = 0; i < vbuffered.length; i++) {
|
8083
8029
|
buffered.push({
|
8084
8030
|
start: vbuffered.start(i),
|
8085
8031
|
end: vbuffered.end(i)
|
8086
8032
|
});
|
8087
8033
|
}
|
8088
|
-
return
|
8034
|
+
return this.bufferedInfo(buffered, pos, maxHoleDuration);
|
8089
8035
|
}
|
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
|
8090
8040
|
}
|
8091
8041
|
return {
|
8092
8042
|
len: 0,
|
@@ -8098,7 +8048,14 @@ class BufferHelper {
|
|
8098
8048
|
static bufferedInfo(buffered, pos, maxHoleDuration) {
|
8099
8049
|
pos = Math.max(0, pos);
|
8100
8050
|
// sort on buffer.start/smaller end (IE does not always return sorted buffered range)
|
8101
|
-
buffered.sort((a, b)
|
8051
|
+
buffered.sort(function (a, b) {
|
8052
|
+
const diff = a.start - b.start;
|
8053
|
+
if (diff) {
|
8054
|
+
return diff;
|
8055
|
+
} else {
|
8056
|
+
return b.end - a.end;
|
8057
|
+
}
|
8058
|
+
});
|
8102
8059
|
let buffered2 = [];
|
8103
8060
|
if (maxHoleDuration) {
|
8104
8061
|
// there might be some small holes between buffer time range
|
@@ -8165,7 +8122,7 @@ class BufferHelper {
|
|
8165
8122
|
*/
|
8166
8123
|
static getBuffered(media) {
|
8167
8124
|
try {
|
8168
|
-
return media.buffered
|
8125
|
+
return media.buffered;
|
8169
8126
|
} catch (e) {
|
8170
8127
|
logger.log('failed to get media.buffered', e);
|
8171
8128
|
return noopBuffered;
|
@@ -8618,8 +8575,8 @@ function createLoaderContext(frag, part = null) {
|
|
8618
8575
|
var _frag$decryptdata;
|
8619
8576
|
let byteRangeStart = start;
|
8620
8577
|
let byteRangeEnd = end;
|
8621
|
-
if (frag.sn === 'initSegment' &&
|
8622
|
-
// MAP segment encrypted with method 'AES-128'
|
8578
|
+
if (frag.sn === 'initSegment' && ((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method) === 'AES-128') {
|
8579
|
+
// MAP segment encrypted with method 'AES-128', when served with HTTP Range,
|
8623
8580
|
// has the unencrypted size specified in the range.
|
8624
8581
|
// Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
|
8625
8582
|
const fragmentLen = end - start;
|
@@ -8652,9 +8609,6 @@ function createGapLoadError(frag, part) {
|
|
8652
8609
|
(part ? part : frag).stats.aborted = true;
|
8653
8610
|
return new LoadError(errorData);
|
8654
8611
|
}
|
8655
|
-
function isMethodFullSegmentAesCbc(method) {
|
8656
|
-
return method === 'AES-128' || method === 'AES-256';
|
8657
|
-
}
|
8658
8612
|
class LoadError extends Error {
|
8659
8613
|
constructor(data) {
|
8660
8614
|
super(data.error.message);
|
@@ -8664,61 +8618,33 @@ class LoadError extends Error {
|
|
8664
8618
|
}
|
8665
8619
|
|
8666
8620
|
class AESCrypto {
|
8667
|
-
constructor(subtle, iv
|
8621
|
+
constructor(subtle, iv) {
|
8668
8622
|
this.subtle = void 0;
|
8669
8623
|
this.aesIV = void 0;
|
8670
|
-
this.aesMode = void 0;
|
8671
8624
|
this.subtle = subtle;
|
8672
8625
|
this.aesIV = iv;
|
8673
|
-
this.aesMode = aesMode;
|
8674
8626
|
}
|
8675
8627
|
decrypt(data, key) {
|
8676
|
-
|
8677
|
-
|
8678
|
-
|
8679
|
-
|
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
|
-
}
|
8628
|
+
return this.subtle.decrypt({
|
8629
|
+
name: 'AES-CBC',
|
8630
|
+
iv: this.aesIV
|
8631
|
+
}, key, data);
|
8693
8632
|
}
|
8694
8633
|
}
|
8695
8634
|
|
8696
8635
|
class FastAESKey {
|
8697
|
-
constructor(subtle, key
|
8636
|
+
constructor(subtle, key) {
|
8698
8637
|
this.subtle = void 0;
|
8699
8638
|
this.key = void 0;
|
8700
|
-
this.aesMode = void 0;
|
8701
8639
|
this.subtle = subtle;
|
8702
8640
|
this.key = key;
|
8703
|
-
this.aesMode = aesMode;
|
8704
8641
|
}
|
8705
8642
|
expandKey() {
|
8706
|
-
const subtleAlgoName = getSubtleAlgoName(this.aesMode);
|
8707
8643
|
return this.subtle.importKey('raw', this.key, {
|
8708
|
-
name:
|
8644
|
+
name: 'AES-CBC'
|
8709
8645
|
}, false, ['encrypt', 'decrypt']);
|
8710
8646
|
}
|
8711
8647
|
}
|
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
|
-
}
|
8722
8648
|
|
8723
8649
|
// PKCS7
|
8724
8650
|
function removePadding(array) {
|
@@ -8968,8 +8894,7 @@ class Decrypter {
|
|
8968
8894
|
this.currentIV = null;
|
8969
8895
|
this.currentResult = null;
|
8970
8896
|
this.useSoftware = void 0;
|
8971
|
-
this.
|
8972
|
-
this.enableSoftwareAES = config.enableSoftwareAES;
|
8897
|
+
this.useSoftware = config.enableSoftwareAES;
|
8973
8898
|
this.removePKCS7Padding = removePKCS7Padding;
|
8974
8899
|
// built in decryptor expects PKCS7 padding
|
8975
8900
|
if (removePKCS7Padding) {
|
@@ -8982,7 +8907,9 @@ class Decrypter {
|
|
8982
8907
|
/* no-op */
|
8983
8908
|
}
|
8984
8909
|
}
|
8985
|
-
|
8910
|
+
if (this.subtle === null) {
|
8911
|
+
this.useSoftware = true;
|
8912
|
+
}
|
8986
8913
|
}
|
8987
8914
|
destroy() {
|
8988
8915
|
this.subtle = null;
|
@@ -9020,10 +8947,10 @@ class Decrypter {
|
|
9020
8947
|
this.softwareDecrypter = null;
|
9021
8948
|
}
|
9022
8949
|
}
|
9023
|
-
decrypt(data, key, iv
|
8950
|
+
decrypt(data, key, iv) {
|
9024
8951
|
if (this.useSoftware) {
|
9025
8952
|
return new Promise((resolve, reject) => {
|
9026
|
-
this.softwareDecrypt(new Uint8Array(data), key, iv
|
8953
|
+
this.softwareDecrypt(new Uint8Array(data), key, iv);
|
9027
8954
|
const decryptResult = this.flush();
|
9028
8955
|
if (decryptResult) {
|
9029
8956
|
resolve(decryptResult.buffer);
|
@@ -9032,21 +8959,17 @@ class Decrypter {
|
|
9032
8959
|
}
|
9033
8960
|
});
|
9034
8961
|
}
|
9035
|
-
return this.webCryptoDecrypt(new Uint8Array(data), key, iv
|
8962
|
+
return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
|
9036
8963
|
}
|
9037
8964
|
|
9038
8965
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
9039
8966
|
// data is handled in the flush() call
|
9040
|
-
softwareDecrypt(data, key, iv
|
8967
|
+
softwareDecrypt(data, key, iv) {
|
9041
8968
|
const {
|
9042
8969
|
currentIV,
|
9043
8970
|
currentResult,
|
9044
8971
|
remainderData
|
9045
8972
|
} = this;
|
9046
|
-
if (aesMode !== DecrypterAesMode.cbc || key.byteLength !== 16) {
|
9047
|
-
logger.warn('SoftwareDecrypt: can only handle AES-128-CBC');
|
9048
|
-
return null;
|
9049
|
-
}
|
9050
8973
|
this.logOnce('JS AES decrypt');
|
9051
8974
|
// The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
|
9052
8975
|
// This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached
|
@@ -9079,11 +9002,11 @@ class Decrypter {
|
|
9079
9002
|
}
|
9080
9003
|
return result;
|
9081
9004
|
}
|
9082
|
-
webCryptoDecrypt(data, key, iv
|
9005
|
+
webCryptoDecrypt(data, key, iv) {
|
9083
9006
|
const subtle = this.subtle;
|
9084
9007
|
if (this.key !== key || !this.fastAesKey) {
|
9085
9008
|
this.key = key;
|
9086
|
-
this.fastAesKey = new FastAESKey(subtle, key
|
9009
|
+
this.fastAesKey = new FastAESKey(subtle, key);
|
9087
9010
|
}
|
9088
9011
|
return this.fastAesKey.expandKey().then(aesKey => {
|
9089
9012
|
// decrypt using web crypto
|
@@ -9091,25 +9014,22 @@ class Decrypter {
|
|
9091
9014
|
return Promise.reject(new Error('web crypto not initialized'));
|
9092
9015
|
}
|
9093
9016
|
this.logOnce('WebCrypto AES decrypt');
|
9094
|
-
const crypto = new AESCrypto(subtle, new Uint8Array(iv)
|
9017
|
+
const crypto = new AESCrypto(subtle, new Uint8Array(iv));
|
9095
9018
|
return crypto.decrypt(data.buffer, aesKey);
|
9096
9019
|
}).catch(err => {
|
9097
9020
|
logger.warn(`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`);
|
9098
|
-
return this.onWebCryptoError(data, key, iv
|
9021
|
+
return this.onWebCryptoError(data, key, iv);
|
9099
9022
|
});
|
9100
9023
|
}
|
9101
|
-
onWebCryptoError(data, key, iv
|
9102
|
-
|
9103
|
-
|
9104
|
-
|
9105
|
-
|
9106
|
-
|
9107
|
-
|
9108
|
-
if (decryptResult) {
|
9109
|
-
return decryptResult.buffer;
|
9110
|
-
}
|
9024
|
+
onWebCryptoError(data, key, iv) {
|
9025
|
+
this.useSoftware = true;
|
9026
|
+
this.logEnabled = true;
|
9027
|
+
this.softwareDecrypt(data, key, iv);
|
9028
|
+
const decryptResult = this.flush();
|
9029
|
+
if (decryptResult) {
|
9030
|
+
return decryptResult.buffer;
|
9111
9031
|
}
|
9112
|
-
throw new Error('WebCrypto
|
9032
|
+
throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data');
|
9113
9033
|
}
|
9114
9034
|
getValidChunk(data) {
|
9115
9035
|
let currentChunk = data;
|
@@ -9160,7 +9080,7 @@ const State = {
|
|
9160
9080
|
};
|
9161
9081
|
class BaseStreamController extends TaskLoop {
|
9162
9082
|
constructor(hls, fragmentTracker, keyLoader, logPrefix, playlistType) {
|
9163
|
-
super(
|
9083
|
+
super();
|
9164
9084
|
this.hls = void 0;
|
9165
9085
|
this.fragPrevious = null;
|
9166
9086
|
this.fragCurrent = null;
|
@@ -9185,98 +9105,22 @@ class BaseStreamController extends TaskLoop {
|
|
9185
9105
|
this.startFragRequested = false;
|
9186
9106
|
this.decrypter = void 0;
|
9187
9107
|
this.initPTS = [];
|
9188
|
-
this.
|
9189
|
-
this.
|
9190
|
-
this.
|
9191
|
-
|
9192
|
-
|
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
|
-
};
|
9108
|
+
this.onvseeking = null;
|
9109
|
+
this.onvended = null;
|
9110
|
+
this.logPrefix = '';
|
9111
|
+
this.log = void 0;
|
9112
|
+
this.warn = void 0;
|
9253
9113
|
this.playlistType = playlistType;
|
9114
|
+
this.logPrefix = logPrefix;
|
9115
|
+
this.log = logger.log.bind(logger, `${logPrefix}:`);
|
9116
|
+
this.warn = logger.warn.bind(logger, `${logPrefix}:`);
|
9254
9117
|
this.hls = hls;
|
9255
9118
|
this.fragmentLoader = new FragmentLoader(hls.config);
|
9256
9119
|
this.keyLoader = keyLoader;
|
9257
9120
|
this.fragmentTracker = fragmentTracker;
|
9258
9121
|
this.config = hls.config;
|
9259
9122
|
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);
|
9268
9123
|
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);
|
9280
9124
|
}
|
9281
9125
|
doTick() {
|
9282
9126
|
this.onTickEnd();
|
@@ -9300,12 +9144,6 @@ class BaseStreamController extends TaskLoop {
|
|
9300
9144
|
this.clearNextTick();
|
9301
9145
|
this.state = State.STOPPED;
|
9302
9146
|
}
|
9303
|
-
pauseBuffering() {
|
9304
|
-
this.buffering = false;
|
9305
|
-
}
|
9306
|
-
resumeBuffering() {
|
9307
|
-
this.buffering = true;
|
9308
|
-
}
|
9309
9147
|
_streamEnded(bufferInfo, levelDetails) {
|
9310
9148
|
// If playlist is live, there is another buffered range after the current range, nothing buffered, media is detached,
|
9311
9149
|
// of nothing loading/loaded return false
|
@@ -9336,8 +9174,10 @@ class BaseStreamController extends TaskLoop {
|
|
9336
9174
|
}
|
9337
9175
|
onMediaAttached(event, data) {
|
9338
9176
|
const media = this.media = this.mediaBuffer = data.media;
|
9339
|
-
|
9340
|
-
|
9177
|
+
this.onvseeking = this.onMediaSeeking.bind(this);
|
9178
|
+
this.onvended = this.onMediaEnded.bind(this);
|
9179
|
+
media.addEventListener('seeking', this.onvseeking);
|
9180
|
+
media.addEventListener('ended', this.onvended);
|
9341
9181
|
const config = this.config;
|
9342
9182
|
if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
|
9343
9183
|
this.startLoad(config.startPosition);
|
@@ -9351,9 +9191,10 @@ class BaseStreamController extends TaskLoop {
|
|
9351
9191
|
}
|
9352
9192
|
|
9353
9193
|
// remove video listeners
|
9354
|
-
if (media) {
|
9355
|
-
media.removeEventListener('seeking', this.
|
9356
|
-
media.removeEventListener('ended', this.
|
9194
|
+
if (media && this.onvseeking && this.onvended) {
|
9195
|
+
media.removeEventListener('seeking', this.onvseeking);
|
9196
|
+
media.removeEventListener('ended', this.onvended);
|
9197
|
+
this.onvseeking = this.onvended = null;
|
9357
9198
|
}
|
9358
9199
|
if (this.keyLoader) {
|
9359
9200
|
this.keyLoader.detach();
|
@@ -9363,8 +9204,56 @@ class BaseStreamController extends TaskLoop {
|
|
9363
9204
|
this.fragmentTracker.removeAllFragments();
|
9364
9205
|
this.stopLoad();
|
9365
9206
|
}
|
9366
|
-
|
9367
|
-
|
9207
|
+
onMediaSeeking() {
|
9208
|
+
const {
|
9209
|
+
config,
|
9210
|
+
fragCurrent,
|
9211
|
+
media,
|
9212
|
+
mediaBuffer,
|
9213
|
+
state
|
9214
|
+
} = this;
|
9215
|
+
const currentTime = media ? media.currentTime : 0;
|
9216
|
+
const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
|
9217
|
+
this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
|
9218
|
+
if (this.state === State.ENDED) {
|
9219
|
+
this.resetLoadingState();
|
9220
|
+
} else if (fragCurrent) {
|
9221
|
+
// Seeking while frag load is in progress
|
9222
|
+
const tolerance = config.maxFragLookUpTolerance;
|
9223
|
+
const fragStartOffset = fragCurrent.start - tolerance;
|
9224
|
+
const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
|
9225
|
+
// if seeking out of buffered range or into new one
|
9226
|
+
if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
|
9227
|
+
const pastFragment = currentTime > fragEndOffset;
|
9228
|
+
// if the seek position is outside the current fragment range
|
9229
|
+
if (currentTime < fragStartOffset || pastFragment) {
|
9230
|
+
if (pastFragment && fragCurrent.loader) {
|
9231
|
+
this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
|
9232
|
+
fragCurrent.abortRequests();
|
9233
|
+
this.resetLoadingState();
|
9234
|
+
}
|
9235
|
+
this.fragPrevious = null;
|
9236
|
+
}
|
9237
|
+
}
|
9238
|
+
}
|
9239
|
+
if (media) {
|
9240
|
+
// Remove gap fragments
|
9241
|
+
this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
|
9242
|
+
this.lastCurrentTime = currentTime;
|
9243
|
+
}
|
9244
|
+
|
9245
|
+
// in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
|
9246
|
+
if (!this.loadedmetadata && !bufferInfo.len) {
|
9247
|
+
this.nextLoadPosition = this.startPosition = currentTime;
|
9248
|
+
}
|
9249
|
+
|
9250
|
+
// Async tick to speed up processing
|
9251
|
+
this.tickImmediate();
|
9252
|
+
}
|
9253
|
+
onMediaEnded() {
|
9254
|
+
// reset startPosition and lastCurrentTime to restart playback @ stream beginning
|
9255
|
+
this.startPosition = this.lastCurrentTime = 0;
|
9256
|
+
}
|
9368
9257
|
onManifestLoaded(event, data) {
|
9369
9258
|
this.startTimeOffset = data.startTimeOffset;
|
9370
9259
|
this.initPTS = [];
|
@@ -9374,7 +9263,7 @@ class BaseStreamController extends TaskLoop {
|
|
9374
9263
|
this.stopLoad();
|
9375
9264
|
super.onHandlerDestroying();
|
9376
9265
|
// @ts-ignore
|
9377
|
-
this.hls =
|
9266
|
+
this.hls = null;
|
9378
9267
|
}
|
9379
9268
|
onHandlerDestroyed() {
|
9380
9269
|
this.state = State.STOPPED;
|
@@ -9505,10 +9394,10 @@ class BaseStreamController extends TaskLoop {
|
|
9505
9394
|
const decryptData = frag.decryptdata;
|
9506
9395
|
|
9507
9396
|
// check to see if the payload needs to be decrypted
|
9508
|
-
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv &&
|
9397
|
+
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
|
9509
9398
|
const startTime = self.performance.now();
|
9510
9399
|
// decrypt init segment data
|
9511
|
-
return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer
|
9400
|
+
return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
|
9512
9401
|
hls.trigger(Events.ERROR, {
|
9513
9402
|
type: ErrorTypes.MEDIA_ERROR,
|
9514
9403
|
details: ErrorDetails.FRAG_DECRYPT_ERROR,
|
@@ -9620,7 +9509,7 @@ class BaseStreamController extends TaskLoop {
|
|
9620
9509
|
}
|
9621
9510
|
let keyLoadingPromise = null;
|
9622
9511
|
if (frag.encrypted && !((_frag$decryptdata = frag.decryptdata) != null && _frag$decryptdata.key)) {
|
9623
|
-
this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.
|
9512
|
+
this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.logPrefix === '[stream-controller]' ? 'level' : 'track'} ${frag.level}`);
|
9624
9513
|
this.state = State.KEY_LOADING;
|
9625
9514
|
this.fragCurrent = frag;
|
9626
9515
|
keyLoadingPromise = this.keyLoader.load(frag).then(keyLoadedData => {
|
@@ -9641,16 +9530,8 @@ class BaseStreamController extends TaskLoop {
|
|
9641
9530
|
} else if (!frag.encrypted && details.encryptedFragments.length) {
|
9642
9531
|
this.keyLoader.loadClear(frag, details.encryptedFragments);
|
9643
9532
|
}
|
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
|
-
}
|
9652
9533
|
targetBufferTime = Math.max(frag.start, targetBufferTime || 0);
|
9653
|
-
if (this.
|
9534
|
+
if (this.config.lowLatencyMode && frag.sn !== 'initSegment') {
|
9654
9535
|
const partList = details.partList;
|
9655
9536
|
if (partList && progressCallback) {
|
9656
9537
|
if (targetBufferTime > frag.end && details.fragmentHint) {
|
@@ -9659,7 +9540,7 @@ class BaseStreamController extends TaskLoop {
|
|
9659
9540
|
const partIndex = this.getNextPart(partList, frag, targetBufferTime);
|
9660
9541
|
if (partIndex > -1) {
|
9661
9542
|
const part = partList[partIndex];
|
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.
|
9543
|
+
this.log(`Loading part sn: ${frag.sn} p: ${part.index} cc: ${frag.cc} of playlist [${details.startSN}-${details.endSN}] parts [0-${partIndex}-${partList.length - 1}] ${this.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
|
9663
9544
|
this.nextLoadPosition = part.start + part.duration;
|
9664
9545
|
this.state = State.FRAG_LOADING;
|
9665
9546
|
let _result;
|
@@ -9688,14 +9569,7 @@ class BaseStreamController extends TaskLoop {
|
|
9688
9569
|
}
|
9689
9570
|
}
|
9690
9571
|
}
|
9691
|
-
|
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))}`);
|
9572
|
+
this.log(`Loading fragment ${frag.sn} cc: ${frag.cc} ${details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''}${this.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
|
9699
9573
|
// Don't update nextLoadPosition for fragments which are not buffered
|
9700
9574
|
if (isFiniteNumber(frag.sn) && !this.bitrateTest) {
|
9701
9575
|
this.nextLoadPosition = frag.start + frag.duration;
|
@@ -9793,36 +9667,8 @@ class BaseStreamController extends TaskLoop {
|
|
9793
9667
|
if (part) {
|
9794
9668
|
part.stats.parsing.end = now;
|
9795
9669
|
}
|
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
|
-
}
|
9806
9670
|
this.updateLevelTiming(frag, part, level, chunkMeta.partial);
|
9807
9671
|
}
|
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
9672
|
getCurrentContext(chunkMeta) {
|
9827
9673
|
const {
|
9828
9674
|
levels,
|
@@ -9923,7 +9769,7 @@ class BaseStreamController extends TaskLoop {
|
|
9923
9769
|
// Workaround flaw in getting forward buffer when maxBufferHole is smaller than gap at current pos
|
9924
9770
|
if (bufferInfo.len === 0 && bufferInfo.nextStart !== undefined) {
|
9925
9771
|
const bufferedFragAtPos = this.fragmentTracker.getBufferedFrag(pos, type);
|
9926
|
-
if (bufferedFragAtPos &&
|
9772
|
+
if (bufferedFragAtPos && bufferInfo.nextStart < bufferedFragAtPos.end) {
|
9927
9773
|
return BufferHelper.bufferInfo(bufferable, pos, Math.max(bufferInfo.nextStart, maxBufferHole));
|
9928
9774
|
}
|
9929
9775
|
}
|
@@ -9971,8 +9817,7 @@ class BaseStreamController extends TaskLoop {
|
|
9971
9817
|
config
|
9972
9818
|
} = this;
|
9973
9819
|
const start = fragments[0].start;
|
9974
|
-
|
9975
|
-
let frag = null;
|
9820
|
+
let frag;
|
9976
9821
|
if (levelDetails.live) {
|
9977
9822
|
const initialLiveManifestSize = config.initialLiveManifestSize;
|
9978
9823
|
if (fragLen < initialLiveManifestSize) {
|
@@ -9984,10 +9829,6 @@ class BaseStreamController extends TaskLoop {
|
|
9984
9829
|
// Do not load using live logic if the starting frag is requested - we want to use getFragmentAtPosition() so that
|
9985
9830
|
// we get the fragment matching that start time
|
9986
9831
|
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
|
-
}
|
9991
9832
|
frag = this.getInitialLiveFragment(levelDetails, fragments);
|
9992
9833
|
this.startPosition = this.nextLoadPosition = frag ? this.hls.liveSyncPosition || frag.start : pos;
|
9993
9834
|
}
|
@@ -9998,7 +9839,7 @@ class BaseStreamController extends TaskLoop {
|
|
9998
9839
|
|
9999
9840
|
// If we haven't run into any special cases already, just load the fragment most closely matching the requested position
|
10000
9841
|
if (!frag) {
|
10001
|
-
const end =
|
9842
|
+
const end = config.lowLatencyMode ? levelDetails.partEnd : levelDetails.fragmentEnd;
|
10002
9843
|
frag = this.getFragmentAtPosition(pos, end, levelDetails);
|
10003
9844
|
}
|
10004
9845
|
return this.mapToInitFragWhenRequired(frag);
|
@@ -10120,7 +9961,7 @@ class BaseStreamController extends TaskLoop {
|
|
10120
9961
|
} = levelDetails;
|
10121
9962
|
const tolerance = config.maxFragLookUpTolerance;
|
10122
9963
|
const partList = levelDetails.partList;
|
10123
|
-
const loadingParts = !!(
|
9964
|
+
const loadingParts = !!(config.lowLatencyMode && partList != null && partList.length && fragmentHint);
|
10124
9965
|
if (loadingParts && fragmentHint && !this.bitrateTest) {
|
10125
9966
|
// Include incomplete fragment with parts at end
|
10126
9967
|
fragments = fragments.concat(fragmentHint);
|
@@ -10313,7 +10154,7 @@ class BaseStreamController extends TaskLoop {
|
|
10313
10154
|
errorAction.resolved = true;
|
10314
10155
|
}
|
10315
10156
|
} else {
|
10316
|
-
|
10157
|
+
logger.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
|
10317
10158
|
return;
|
10318
10159
|
}
|
10319
10160
|
} else if ((errorAction == null ? void 0 : errorAction.action) === NetworkErrorAction.SendAlternateToPenaltyBox) {
|
@@ -10381,9 +10222,7 @@ class BaseStreamController extends TaskLoop {
|
|
10381
10222
|
this.log('Reset loading state');
|
10382
10223
|
this.fragCurrent = null;
|
10383
10224
|
this.fragPrevious = null;
|
10384
|
-
|
10385
|
-
this.state = State.IDLE;
|
10386
|
-
}
|
10225
|
+
this.state = State.IDLE;
|
10387
10226
|
}
|
10388
10227
|
resetStartWhenNotLoaded(level) {
|
10389
10228
|
// if loadedmetadata is not set, it means that first frag request failed
|
@@ -10724,7 +10563,6 @@ const initPTSFn = (timestamp, timeOffset, initPTS) => {
|
|
10724
10563
|
*/
|
10725
10564
|
function getAudioConfig(observer, data, offset, audioCodec) {
|
10726
10565
|
let adtsObjectType;
|
10727
|
-
let originalAdtsObjectType;
|
10728
10566
|
let adtsExtensionSamplingIndex;
|
10729
10567
|
let adtsChannelConfig;
|
10730
10568
|
let config;
|
@@ -10732,7 +10570,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
10732
10570
|
const manifestCodec = audioCodec;
|
10733
10571
|
const adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
|
10734
10572
|
// byte 2
|
10735
|
-
adtsObjectType =
|
10573
|
+
adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
|
10736
10574
|
const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
|
10737
10575
|
if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
|
10738
10576
|
const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
|
@@ -10749,8 +10587,8 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
10749
10587
|
// byte 3
|
10750
10588
|
adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6;
|
10751
10589
|
logger.log(`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`);
|
10752
|
-
//
|
10753
|
-
if (/firefox
|
10590
|
+
// firefox: freq less than 24kHz = AAC SBR (HE-AAC)
|
10591
|
+
if (/firefox/i.test(userAgent)) {
|
10754
10592
|
if (adtsSamplingIndex >= 6) {
|
10755
10593
|
adtsObjectType = 5;
|
10756
10594
|
config = new Array(4);
|
@@ -10844,7 +10682,6 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
10844
10682
|
samplerate: adtsSamplingRates[adtsSamplingIndex],
|
10845
10683
|
channelCount: adtsChannelConfig,
|
10846
10684
|
codec: 'mp4a.40.' + adtsObjectType,
|
10847
|
-
parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
|
10848
10685
|
manifestCodec
|
10849
10686
|
};
|
10850
10687
|
}
|
@@ -10899,8 +10736,7 @@ function initTrackConfig(track, observer, data, offset, audioCodec) {
|
|
10899
10736
|
track.channelCount = config.channelCount;
|
10900
10737
|
track.codec = config.codec;
|
10901
10738
|
track.manifestCodec = config.manifestCodec;
|
10902
|
-
track.
|
10903
|
-
logger.log(`parsed codec:${track.parsedCodec}, codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
|
10739
|
+
logger.log(`parsed codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
|
10904
10740
|
}
|
10905
10741
|
}
|
10906
10742
|
function getFrameDuration(samplerate) {
|
@@ -11491,110 +11327,6 @@ class BaseVideoParser {
|
|
11491
11327
|
logger.log(VideoSample.pts + '/' + VideoSample.dts + ':' + VideoSample.debug);
|
11492
11328
|
}
|
11493
11329
|
}
|
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
|
-
}
|
11598
11330
|
}
|
11599
11331
|
|
11600
11332
|
/**
|
@@ -11737,171 +11469,21 @@ class ExpGolomb {
|
|
11737
11469
|
readUInt() {
|
11738
11470
|
return this.readBits(32);
|
11739
11471
|
}
|
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
|
-
}
|
11891
11472
|
|
11892
11473
|
/**
|
11893
|
-
*
|
11474
|
+
* Advance the ExpGolomb decoder past a scaling list. The scaling
|
11475
|
+
* list is optionally transmitted as part of a sequence parameter
|
11894
11476
|
* set and is not relevant to transmuxing.
|
11895
11477
|
* @param count the number of entries in this scaling list
|
11896
11478
|
* @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
|
11897
11479
|
*/
|
11898
|
-
skipScalingList(count
|
11480
|
+
skipScalingList(count) {
|
11899
11481
|
let lastScale = 8;
|
11900
11482
|
let nextScale = 8;
|
11901
11483
|
let deltaScale;
|
11902
11484
|
for (let j = 0; j < count; j++) {
|
11903
11485
|
if (nextScale !== 0) {
|
11904
|
-
deltaScale =
|
11486
|
+
deltaScale = this.readEG();
|
11905
11487
|
nextScale = (lastScale + deltaScale + 256) % 256;
|
11906
11488
|
}
|
11907
11489
|
lastScale = nextScale === 0 ? lastScale : nextScale;
|
@@ -11916,8 +11498,7 @@ class AvcVideoParser extends BaseVideoParser {
|
|
11916
11498
|
* sequence parameter set, including the dimensions of the
|
11917
11499
|
* associated video frames.
|
11918
11500
|
*/
|
11919
|
-
readSPS(
|
11920
|
-
const eg = new ExpGolomb(sps);
|
11501
|
+
readSPS() {
|
11921
11502
|
let frameCropLeftOffset = 0;
|
11922
11503
|
let frameCropRightOffset = 0;
|
11923
11504
|
let frameCropTopOffset = 0;
|
@@ -11925,13 +11506,13 @@ class AvcVideoParser extends BaseVideoParser {
|
|
11925
11506
|
let numRefFramesInPicOrderCntCycle;
|
11926
11507
|
let scalingListCount;
|
11927
11508
|
let i;
|
11928
|
-
const readUByte =
|
11929
|
-
const readBits =
|
11930
|
-
const readUEG =
|
11931
|
-
const readBoolean =
|
11932
|
-
const skipBits =
|
11933
|
-
const skipEG =
|
11934
|
-
const skipUEG =
|
11509
|
+
const readUByte = this.readUByte.bind(this);
|
11510
|
+
const readBits = this.readBits.bind(this);
|
11511
|
+
const readUEG = this.readUEG.bind(this);
|
11512
|
+
const readBoolean = this.readBoolean.bind(this);
|
11513
|
+
const skipBits = this.skipBits.bind(this);
|
11514
|
+
const skipEG = this.skipEG.bind(this);
|
11515
|
+
const skipUEG = this.skipUEG.bind(this);
|
11935
11516
|
const skipScalingList = this.skipScalingList.bind(this);
|
11936
11517
|
readUByte();
|
11937
11518
|
const profileIdc = readUByte(); // profile_idc
|
@@ -11956,9 +11537,9 @@ class AvcVideoParser extends BaseVideoParser {
|
|
11956
11537
|
if (readBoolean()) {
|
11957
11538
|
// seq_scaling_list_present_flag[ i ]
|
11958
11539
|
if (i < 6) {
|
11959
|
-
skipScalingList(16
|
11540
|
+
skipScalingList(16);
|
11960
11541
|
} else {
|
11961
|
-
skipScalingList(64
|
11542
|
+
skipScalingList(64);
|
11962
11543
|
}
|
11963
11544
|
}
|
11964
11545
|
}
|
@@ -12063,15 +11644,19 @@ class AvcVideoParser extends BaseVideoParser {
|
|
12063
11644
|
pixelRatio: pixelRatio
|
12064
11645
|
};
|
12065
11646
|
}
|
11647
|
+
readSliceType() {
|
11648
|
+
// skip NALu type
|
11649
|
+
this.readUByte();
|
11650
|
+
// discard first_mb_in_slice
|
11651
|
+
this.readUEG();
|
11652
|
+
// return slice_type
|
11653
|
+
return this.readUEG();
|
11654
|
+
}
|
12066
11655
|
}
|
12067
11656
|
|
12068
|
-
class
|
12069
|
-
|
12070
|
-
|
12071
|
-
this.initVPS = null;
|
12072
|
-
}
|
12073
|
-
parsePES(track, textTrack, pes, last, duration) {
|
12074
|
-
const units = this.parseNALu(track, pes.data);
|
11657
|
+
class AvcVideoParser extends BaseVideoParser {
|
11658
|
+
parseAVCPES(track, textTrack, pes, last, duration) {
|
11659
|
+
const units = this.parseAVCNALu(track, pes.data);
|
12075
11660
|
let VideoSample = this.VideoSample;
|
12076
11661
|
let push;
|
12077
11662
|
let spsfound = false;
|
@@ -12087,49 +11672,42 @@ class HevcVideoParser extends BaseVideoParser {
|
|
12087
11672
|
units.forEach(unit => {
|
12088
11673
|
var _VideoSample2;
|
12089
11674
|
switch (unit.type) {
|
12090
|
-
//
|
12091
|
-
case 0:
|
11675
|
+
// NDR
|
12092
11676
|
case 1:
|
12093
|
-
|
12094
|
-
|
12095
|
-
|
12096
|
-
|
12097
|
-
|
12098
|
-
|
12099
|
-
|
12100
|
-
|
12101
|
-
|
12102
|
-
|
12103
|
-
|
12104
|
-
|
12105
|
-
|
12106
|
-
|
12107
|
-
|
12108
|
-
|
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;
|
11677
|
+
{
|
11678
|
+
let iskey = false;
|
11679
|
+
push = true;
|
11680
|
+
const data = unit.data;
|
11681
|
+
// only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
|
11682
|
+
if (spsfound && data.length > 4) {
|
11683
|
+
// retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
|
11684
|
+
const sliceType = new ExpGolomb(data).readSliceType();
|
11685
|
+
// 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
|
11686
|
+
// SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
|
11687
|
+
// An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
|
11688
|
+
// I slice: A slice that is not an SI slice that is decoded using intra prediction only.
|
11689
|
+
// if (sliceType === 2 || sliceType === 7) {
|
11690
|
+
if (sliceType === 2 || sliceType === 4 || sliceType === 7 || sliceType === 9) {
|
11691
|
+
iskey = true;
|
11692
|
+
}
|
12121
11693
|
}
|
11694
|
+
if (iskey) {
|
11695
|
+
var _VideoSample;
|
11696
|
+
// if we have non-keyframe data already, that cannot belong to the same frame as a keyframe, so force a push
|
11697
|
+
if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
|
11698
|
+
this.pushAccessUnit(VideoSample, track);
|
11699
|
+
VideoSample = this.VideoSample = null;
|
11700
|
+
}
|
11701
|
+
}
|
11702
|
+
if (!VideoSample) {
|
11703
|
+
VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
|
11704
|
+
}
|
11705
|
+
VideoSample.frame = true;
|
11706
|
+
VideoSample.key = iskey;
|
11707
|
+
break;
|
11708
|
+
// IDR
|
12122
11709
|
}
|
12123
|
-
|
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:
|
11710
|
+
case 5:
|
12133
11711
|
push = true;
|
12134
11712
|
// handle PES not starting with AUD
|
12135
11713
|
// if we have frame data already, that cannot belong to the same frame, so force a push
|
@@ -12143,76 +11721,48 @@ class HevcVideoParser extends BaseVideoParser {
|
|
12143
11721
|
VideoSample.key = true;
|
12144
11722
|
VideoSample.frame = true;
|
12145
11723
|
break;
|
12146
|
-
|
12147
11724
|
// SEI
|
12148
|
-
case
|
12149
|
-
|
12150
|
-
|
12151
|
-
|
12152
|
-
|
12153
|
-
|
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;
|
11725
|
+
case 6:
|
11726
|
+
{
|
11727
|
+
push = true;
|
11728
|
+
parseSEIMessageFromNALu(unit.data, 1, pes.pts, textTrack.samples);
|
11729
|
+
break;
|
11730
|
+
// SPS
|
12162
11731
|
}
|
12163
|
-
|
12164
|
-
|
12165
|
-
|
12166
|
-
|
12167
|
-
|
12168
|
-
|
12169
|
-
|
12170
|
-
|
12171
|
-
if (track.
|
12172
|
-
this.initVPS = track.vps[0];
|
12173
|
-
track.sps = track.pps = undefined;
|
12174
|
-
}
|
12175
|
-
if (!track.sps) {
|
12176
|
-
const config = this.readSPS(unit.data);
|
11732
|
+
case 7:
|
11733
|
+
{
|
11734
|
+
var _track$pixelRatio, _track$pixelRatio2;
|
11735
|
+
push = true;
|
11736
|
+
spsfound = true;
|
11737
|
+
const sps = unit.data;
|
11738
|
+
const expGolombDecoder = new ExpGolomb(sps);
|
11739
|
+
const config = expGolombDecoder.readSPS();
|
11740
|
+
if (!track.sps || track.width !== config.width || track.height !== config.height || ((_track$pixelRatio = track.pixelRatio) == null ? void 0 : _track$pixelRatio[0]) !== config.pixelRatio[0] || ((_track$pixelRatio2 = track.pixelRatio) == null ? void 0 : _track$pixelRatio2[1]) !== config.pixelRatio[1]) {
|
12177
11741
|
track.width = config.width;
|
12178
11742
|
track.height = config.height;
|
12179
11743
|
track.pixelRatio = config.pixelRatio;
|
11744
|
+
track.sps = [sps];
|
12180
11745
|
track.duration = duration;
|
12181
|
-
|
12182
|
-
|
12183
|
-
for (
|
12184
|
-
|
11746
|
+
const codecarray = sps.subarray(1, 4);
|
11747
|
+
let codecstring = 'avc1.';
|
11748
|
+
for (let i = 0; i < 3; i++) {
|
11749
|
+
let h = codecarray[i].toString(16);
|
11750
|
+
if (h.length < 2) {
|
11751
|
+
h = '0' + h;
|
11752
|
+
}
|
11753
|
+
codecstring += h;
|
12185
11754
|
}
|
11755
|
+
track.codec = codecstring;
|
12186
11756
|
}
|
12187
|
-
|
12188
|
-
track.sps.push(unit.data);
|
12189
|
-
}
|
12190
|
-
}
|
12191
|
-
if (!VideoSample) {
|
12192
|
-
VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
|
11757
|
+
break;
|
12193
11758
|
}
|
12194
|
-
VideoSample.key = true;
|
12195
|
-
break;
|
12196
|
-
|
12197
11759
|
// PPS
|
12198
|
-
case
|
11760
|
+
case 8:
|
12199
11761
|
push = true;
|
12200
|
-
|
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
|
-
}
|
11762
|
+
track.pps = [unit.data];
|
12212
11763
|
break;
|
12213
|
-
|
12214
|
-
|
12215
|
-
case 35:
|
11764
|
+
// AUD
|
11765
|
+
case 9:
|
12216
11766
|
push = true;
|
12217
11767
|
track.audFound = true;
|
12218
11768
|
if (VideoSample) {
|
@@ -12220,10 +11770,14 @@ class HevcVideoParser extends BaseVideoParser {
|
|
12220
11770
|
}
|
12221
11771
|
VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
|
12222
11772
|
break;
|
11773
|
+
// Filler Data
|
11774
|
+
case 12:
|
11775
|
+
push = true;
|
11776
|
+
break;
|
12223
11777
|
default:
|
12224
11778
|
push = false;
|
12225
11779
|
if (VideoSample) {
|
12226
|
-
VideoSample.debug += 'unknown
|
11780
|
+
VideoSample.debug += 'unknown NAL ' + unit.type + ' ';
|
12227
11781
|
}
|
12228
11782
|
break;
|
12229
11783
|
}
|
@@ -12238,423 +11792,109 @@ class HevcVideoParser extends BaseVideoParser {
|
|
12238
11792
|
this.VideoSample = null;
|
12239
11793
|
}
|
12240
11794
|
}
|
12241
|
-
|
12242
|
-
|
12243
|
-
|
12244
|
-
|
12245
|
-
const
|
12246
|
-
let
|
12247
|
-
|
12248
|
-
|
12249
|
-
|
12250
|
-
|
12251
|
-
|
12252
|
-
|
12253
|
-
}
|
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
|
11795
|
+
parseAVCNALu(track, array) {
|
11796
|
+
const len = array.byteLength;
|
11797
|
+
let state = track.naluState || 0;
|
11798
|
+
const lastState = state;
|
11799
|
+
const units = [];
|
11800
|
+
let i = 0;
|
11801
|
+
let value;
|
11802
|
+
let overflow;
|
11803
|
+
let unitType;
|
11804
|
+
let lastUnitStart = -1;
|
11805
|
+
let lastUnitType = 0;
|
11806
|
+
// logger.log('PES:' + Hex.hexDump(array));
|
12270
11807
|
|
12271
|
-
|
12272
|
-
|
12273
|
-
|
12274
|
-
|
12275
|
-
|
12276
|
-
|
12277
|
-
|
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
|
-
}
|
12378
|
-
}
|
12379
|
-
}
|
12380
|
-
}
|
12381
|
-
}
|
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;
|
12382
11815
|
}
|
12383
|
-
|
12384
|
-
|
12385
|
-
|
12386
|
-
|
12387
|
-
|
12388
|
-
|
12389
|
-
|
12390
|
-
|
12391
|
-
|
12392
|
-
|
12393
|
-
|
12394
|
-
|
12395
|
-
|
12396
|
-
|
12397
|
-
|
12398
|
-
|
12399
|
-
|
12400
|
-
|
12401
|
-
|
12402
|
-
|
12403
|
-
|
12404
|
-
|
12405
|
-
|
12406
|
-
|
12407
|
-
|
12408
|
-
|
12409
|
-
|
12410
|
-
|
12411
|
-
|
12412
|
-
if (
|
12413
|
-
|
12414
|
-
|
12415
|
-
|
12416
|
-
|
12417
|
-
|
12418
|
-
|
12419
|
-
|
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);
|
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;
|
11826
|
+
}
|
11827
|
+
// here we have state either equal to 2 or 3
|
11828
|
+
if (!value) {
|
11829
|
+
state = 3;
|
11830
|
+
} else if (value === 1) {
|
11831
|
+
overflow = i - state - 1;
|
11832
|
+
if (lastUnitStart >= 0) {
|
11833
|
+
const unit = {
|
11834
|
+
data: array.subarray(lastUnitStart, overflow),
|
11835
|
+
type: lastUnitType
|
11836
|
+
};
|
11837
|
+
// logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
|
11838
|
+
units.push(unit);
|
11839
|
+
} else {
|
11840
|
+
// lastUnitStart is undefined => this is the first start code found in this PES packet
|
11841
|
+
// first check if start code delimiter is overlapping between 2 PES packets,
|
11842
|
+
// ie it started in last packet (lastState not zero)
|
11843
|
+
// and ended at the beginning of this PES packet (i <= 4 - lastState)
|
11844
|
+
const lastUnit = this.getLastNalUnit(track.samples);
|
11845
|
+
if (lastUnit) {
|
11846
|
+
if (lastState && i <= 4 - lastState) {
|
11847
|
+
// start delimiter overlapping between PES packets
|
11848
|
+
// strip start delimiter bytes from the end of last NAL unit
|
11849
|
+
// check if lastUnit had a state different from zero
|
11850
|
+
if (lastUnit.state) {
|
11851
|
+
// strip last bytes
|
11852
|
+
lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
|
12547
11853
|
}
|
12548
11854
|
}
|
12549
|
-
|
12550
|
-
|
12551
|
-
|
12552
|
-
|
12553
|
-
|
12554
|
-
|
12555
|
-
eg.readUEG();
|
12556
|
-
}
|
12557
|
-
eg.skipBits(1);
|
12558
|
-
}
|
11855
|
+
// If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
|
11856
|
+
|
11857
|
+
if (overflow > 0) {
|
11858
|
+
// logger.log('first NALU found with overflow:' + overflow);
|
11859
|
+
lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
|
11860
|
+
lastUnit.state = 0;
|
12559
11861
|
}
|
12560
11862
|
}
|
12561
11863
|
}
|
12562
|
-
|
12563
|
-
|
12564
|
-
|
12565
|
-
|
12566
|
-
|
12567
|
-
|
12568
|
-
|
12569
|
-
|
12570
|
-
|
12571
|
-
|
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
|
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;
|
12614
11874
|
}
|
12615
|
-
}
|
12616
|
-
|
12617
|
-
|
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
|
11875
|
+
} else {
|
11876
|
+
state = 0;
|
11877
|
+
}
|
12650
11878
|
}
|
12651
|
-
|
12652
|
-
|
12653
|
-
|
12654
|
-
|
12655
|
-
|
12656
|
-
|
12657
|
-
|
11879
|
+
if (lastUnitStart >= 0 && state >= 0) {
|
11880
|
+
const unit = {
|
11881
|
+
data: array.subarray(lastUnitStart, len),
|
11882
|
+
type: lastUnitType,
|
11883
|
+
state: state
|
11884
|
+
};
|
11885
|
+
units.push(unit);
|
11886
|
+
// logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
|
11887
|
+
}
|
11888
|
+
// no NALu found
|
11889
|
+
if (units.length === 0) {
|
11890
|
+
// append pes.data to previous NAL unit
|
11891
|
+
const lastUnit = this.getLastNalUnit(track.samples);
|
11892
|
+
if (lastUnit) {
|
11893
|
+
lastUnit.data = appendUint8Array(lastUnit.data, array);
|
11894
|
+
}
|
11895
|
+
}
|
11896
|
+
track.naluState = state;
|
11897
|
+
return units;
|
12658
11898
|
}
|
12659
11899
|
}
|
12660
11900
|
|
@@ -12672,7 +11912,7 @@ class SampleAesDecrypter {
|
|
12672
11912
|
});
|
12673
11913
|
}
|
12674
11914
|
decryptBuffer(encryptedData) {
|
12675
|
-
return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer
|
11915
|
+
return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer);
|
12676
11916
|
}
|
12677
11917
|
|
12678
11918
|
// AAC - encrypt all full 16 bytes blocks starting from offset 16
|
@@ -12786,7 +12026,7 @@ class TSDemuxer {
|
|
12786
12026
|
this.observer = observer;
|
12787
12027
|
this.config = config;
|
12788
12028
|
this.typeSupported = typeSupported;
|
12789
|
-
this.videoParser =
|
12029
|
+
this.videoParser = new AvcVideoParser();
|
12790
12030
|
}
|
12791
12031
|
static probe(data) {
|
12792
12032
|
const syncOffset = TSDemuxer.syncOffset(data);
|
@@ -12951,21 +12191,7 @@ class TSDemuxer {
|
|
12951
12191
|
case videoPid:
|
12952
12192
|
if (stt) {
|
12953
12193
|
if (videoData && (pes = parsePES(videoData))) {
|
12954
|
-
|
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
|
-
}
|
12194
|
+
this.videoParser.parseAVCPES(videoTrack, textTrack, pes, false, this._duration);
|
12969
12195
|
}
|
12970
12196
|
videoData = {
|
12971
12197
|
data: [],
|
@@ -13132,22 +12358,8 @@ class TSDemuxer {
|
|
13132
12358
|
// try to parse last PES packets
|
13133
12359
|
let pes;
|
13134
12360
|
if (videoData && (pes = parsePES(videoData))) {
|
13135
|
-
|
13136
|
-
|
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
|
-
}
|
12361
|
+
this.videoParser.parseAVCPES(videoTrack, textTrack, pes, true, this._duration);
|
12362
|
+
videoTrack.pesData = null;
|
13151
12363
|
} else {
|
13152
12364
|
// either avcData null or PES truncated, keep it for next frag parsing
|
13153
12365
|
videoTrack.pesData = videoData;
|
@@ -13480,14 +12692,7 @@ function parsePMT(data, offset, typeSupported, isSampleAes) {
|
|
13480
12692
|
logger.warn('Unsupported EC-3 in M2TS found');
|
13481
12693
|
break;
|
13482
12694
|
case 0x24:
|
13483
|
-
|
13484
|
-
{
|
13485
|
-
if (result.videoPid === -1) {
|
13486
|
-
result.videoPid = pid;
|
13487
|
-
result.segmentVideoCodec = 'hevc';
|
13488
|
-
logger.log('HEVC in M2TS found');
|
13489
|
-
}
|
13490
|
-
}
|
12695
|
+
logger.warn('Unsupported HEVC in M2TS found');
|
13491
12696
|
break;
|
13492
12697
|
}
|
13493
12698
|
// move to the next table entry
|
@@ -13710,8 +12915,6 @@ class MP4 {
|
|
13710
12915
|
avc1: [],
|
13711
12916
|
// codingname
|
13712
12917
|
avcC: [],
|
13713
|
-
hvc1: [],
|
13714
|
-
hvcC: [],
|
13715
12918
|
btrt: [],
|
13716
12919
|
dinf: [],
|
13717
12920
|
dref: [],
|
@@ -14136,10 +13339,8 @@ class MP4 {
|
|
14136
13339
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.ac3(track));
|
14137
13340
|
}
|
14138
13341
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track));
|
14139
|
-
} else if (track.segmentCodec === 'avc') {
|
14140
|
-
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
|
14141
13342
|
} else {
|
14142
|
-
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.
|
13343
|
+
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
|
14143
13344
|
}
|
14144
13345
|
}
|
14145
13346
|
static tkhd(track) {
|
@@ -14238,122 +13439,44 @@ class MP4 {
|
|
14238
13439
|
const arraylen = 12 + 16 * len;
|
14239
13440
|
const array = new Uint8Array(arraylen);
|
14240
13441
|
let i;
|
14241
|
-
let sample;
|
14242
|
-
let duration;
|
14243
|
-
let size;
|
14244
|
-
let flags;
|
14245
|
-
let cts;
|
14246
|
-
offset += 8 + arraylen;
|
14247
|
-
array.set([track.type === 'video' ? 0x01 : 0x00,
|
14248
|
-
// version 1 for video with signed-int sample_composition_time_offset
|
14249
|
-
0x00, 0x0f, 0x01,
|
14250
|
-
// flags
|
14251
|
-
len >>> 24 & 0xff, len >>> 16 & 0xff, len >>> 8 & 0xff, len & 0xff,
|
14252
|
-
// sample_count
|
14253
|
-
offset >>> 24 & 0xff, offset >>> 16 & 0xff, offset >>> 8 & 0xff, offset & 0xff // data_offset
|
14254
|
-
], 0);
|
14255
|
-
for (i = 0; i < len; i++) {
|
14256
|
-
sample = samples[i];
|
14257
|
-
duration = sample.duration;
|
14258
|
-
size = sample.size;
|
14259
|
-
flags = sample.flags;
|
14260
|
-
cts = sample.cts;
|
14261
|
-
array.set([duration >>> 24 & 0xff, duration >>> 16 & 0xff, duration >>> 8 & 0xff, duration & 0xff,
|
14262
|
-
// sample_duration
|
14263
|
-
size >>> 24 & 0xff, size >>> 16 & 0xff, size >>> 8 & 0xff, size & 0xff,
|
14264
|
-
// sample_size
|
14265
|
-
flags.isLeading << 2 | flags.dependsOn, flags.isDependedOn << 6 | flags.hasRedundancy << 4 | flags.paddingValue << 1 | flags.isNonSync, flags.degradPrio & 0xf0 << 8, flags.degradPrio & 0x0f,
|
14266
|
-
// sample_flags
|
14267
|
-
cts >>> 24 & 0xff, cts >>> 16 & 0xff, cts >>> 8 & 0xff, cts & 0xff // sample_composition_time_offset
|
14268
|
-
], 12 + 16 * i);
|
14269
|
-
}
|
14270
|
-
return MP4.box(MP4.types.trun, array);
|
14271
|
-
}
|
14272
|
-
static initSegment(tracks) {
|
14273
|
-
if (!MP4.types) {
|
14274
|
-
MP4.init();
|
14275
|
-
}
|
14276
|
-
const movie = MP4.moov(tracks);
|
14277
|
-
const result = appendUint8Array(MP4.FTYP, movie);
|
14278
|
-
return result;
|
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])));
|
13442
|
+
let sample;
|
13443
|
+
let duration;
|
13444
|
+
let size;
|
13445
|
+
let flags;
|
13446
|
+
let cts;
|
13447
|
+
offset += 8 + arraylen;
|
13448
|
+
array.set([track.type === 'video' ? 0x01 : 0x00,
|
13449
|
+
// version 1 for video with signed-int sample_composition_time_offset
|
13450
|
+
0x00, 0x0f, 0x01,
|
13451
|
+
// flags
|
13452
|
+
len >>> 24 & 0xff, len >>> 16 & 0xff, len >>> 8 & 0xff, len & 0xff,
|
13453
|
+
// sample_count
|
13454
|
+
offset >>> 24 & 0xff, offset >>> 16 & 0xff, offset >>> 8 & 0xff, offset & 0xff // data_offset
|
13455
|
+
], 0);
|
13456
|
+
for (i = 0; i < len; i++) {
|
13457
|
+
sample = samples[i];
|
13458
|
+
duration = sample.duration;
|
13459
|
+
size = sample.size;
|
13460
|
+
flags = sample.flags;
|
13461
|
+
cts = sample.cts;
|
13462
|
+
array.set([duration >>> 24 & 0xff, duration >>> 16 & 0xff, duration >>> 8 & 0xff, duration & 0xff,
|
13463
|
+
// sample_duration
|
13464
|
+
size >>> 24 & 0xff, size >>> 16 & 0xff, size >>> 8 & 0xff, size & 0xff,
|
13465
|
+
// sample_size
|
13466
|
+
flags.isLeading << 2 | flags.dependsOn, flags.isDependedOn << 6 | flags.hasRedundancy << 4 | flags.paddingValue << 1 | flags.isNonSync, flags.degradPrio & 0xf0 << 8, flags.degradPrio & 0x0f,
|
13467
|
+
// sample_flags
|
13468
|
+
cts >>> 24 & 0xff, cts >>> 16 & 0xff, cts >>> 8 & 0xff, cts & 0xff // sample_composition_time_offset
|
13469
|
+
], 12 + 16 * i);
|
13470
|
+
}
|
13471
|
+
return MP4.box(MP4.types.trun, array);
|
13472
|
+
}
|
13473
|
+
static initSegment(tracks) {
|
13474
|
+
if (!MP4.types) {
|
13475
|
+
MP4.init();
|
13476
|
+
}
|
13477
|
+
const movie = MP4.moov(tracks);
|
13478
|
+
const result = appendUint8Array(MP4.FTYP, movie);
|
13479
|
+
return result;
|
14357
13480
|
}
|
14358
13481
|
}
|
14359
13482
|
MP4.types = void 0;
|
@@ -14736,9 +13859,9 @@ class MP4Remuxer {
|
|
14736
13859
|
const foundOverlap = delta < -1;
|
14737
13860
|
if (foundHole || foundOverlap) {
|
14738
13861
|
if (foundHole) {
|
14739
|
-
logger.warn(
|
13862
|
+
logger.warn(`AVC: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
|
14740
13863
|
} else {
|
14741
|
-
logger.warn(
|
13864
|
+
logger.warn(`AVC: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
|
14742
13865
|
}
|
14743
13866
|
if (!foundOverlap || nextAvcDts >= inputSamples[0].pts || chromeVersion) {
|
14744
13867
|
firstDTS = nextAvcDts;
|
@@ -14747,24 +13870,12 @@ class MP4Remuxer {
|
|
14747
13870
|
inputSamples[0].dts = firstDTS;
|
14748
13871
|
inputSamples[0].pts = firstPTS;
|
14749
13872
|
} else {
|
14750
|
-
let isPTSOrderRetained = true;
|
14751
13873
|
for (let i = 0; i < inputSamples.length; i++) {
|
14752
|
-
if (inputSamples[i].dts > firstPTS
|
13874
|
+
if (inputSamples[i].dts > firstPTS) {
|
14753
13875
|
break;
|
14754
13876
|
}
|
14755
|
-
const prevPTS = inputSamples[i].pts;
|
14756
13877
|
inputSamples[i].dts -= delta;
|
14757
13878
|
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
|
-
}
|
14768
13879
|
}
|
14769
13880
|
}
|
14770
13881
|
logger.log(`Video: Initial PTS/DTS adjusted: ${toMsFromMpegTsClock(firstPTS, true)}/${toMsFromMpegTsClock(firstDTS, true)}, delta: ${toMsFromMpegTsClock(delta, true)} ms`);
|
@@ -14912,7 +14023,7 @@ class MP4Remuxer {
|
|
14912
14023
|
}
|
14913
14024
|
}
|
14914
14025
|
}
|
14915
|
-
// next AVC
|
14026
|
+
// next AVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
|
14916
14027
|
mp4SampleDuration = stretchedLastFrame || !mp4SampleDuration ? averageSampleDuration : mp4SampleDuration;
|
14917
14028
|
this.nextAvcDts = nextAvcDts = lastDTS + mp4SampleDuration;
|
14918
14029
|
this.videoSampleDuration = mp4SampleDuration;
|
@@ -15045,7 +14156,7 @@ class MP4Remuxer {
|
|
15045
14156
|
logger.warn(`[mp4-remuxer]: Injecting ${missing} audio frame @ ${(nextPts / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`);
|
15046
14157
|
for (let j = 0; j < missing; j++) {
|
15047
14158
|
const newStamp = Math.max(nextPts, 0);
|
15048
|
-
let fillFrame = AAC.getSilentFrame(track.
|
14159
|
+
let fillFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
|
15049
14160
|
if (!fillFrame) {
|
15050
14161
|
logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.');
|
15051
14162
|
fillFrame = sample.unit.subarray();
|
@@ -15173,7 +14284,7 @@ class MP4Remuxer {
|
|
15173
14284
|
// samples count of this segment's duration
|
15174
14285
|
const nbSamples = Math.ceil((endDTS - startDTS) / frameDuration);
|
15175
14286
|
// silent frame
|
15176
|
-
const silentFrame = AAC.getSilentFrame(track.
|
14287
|
+
const silentFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
|
15177
14288
|
logger.warn('[mp4-remuxer]: remux empty Audio');
|
15178
14289
|
// Can't remux if we can't generate a silent frame...
|
15179
14290
|
if (!silentFrame) {
|
@@ -15567,15 +14678,13 @@ class Transmuxer {
|
|
15567
14678
|
initSegmentData
|
15568
14679
|
} = transmuxConfig;
|
15569
14680
|
const keyData = getEncryptionType(uintData, decryptdata);
|
15570
|
-
if (keyData &&
|
14681
|
+
if (keyData && keyData.method === 'AES-128') {
|
15571
14682
|
const decrypter = this.getDecrypter();
|
15572
|
-
const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
|
15573
|
-
|
15574
14683
|
// Software decryption is synchronous; webCrypto is not
|
15575
14684
|
if (decrypter.isSync()) {
|
15576
14685
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
15577
14686
|
// data is handled in the flush() call
|
15578
|
-
let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer
|
14687
|
+
let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer);
|
15579
14688
|
// For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
|
15580
14689
|
const loadingParts = chunkMeta.part > -1;
|
15581
14690
|
if (loadingParts) {
|
@@ -15587,7 +14696,7 @@ class Transmuxer {
|
|
15587
14696
|
}
|
15588
14697
|
uintData = new Uint8Array(decryptedData);
|
15589
14698
|
} else {
|
15590
|
-
this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer
|
14699
|
+
this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer).then(decryptedData => {
|
15591
14700
|
// Calling push here is important; if flush() is called while this is still resolving, this ensures that
|
15592
14701
|
// the decrypted data has been transmuxed
|
15593
14702
|
const result = this.push(decryptedData, null, chunkMeta);
|
@@ -16241,7 +15350,14 @@ class TransmuxerInterface {
|
|
16241
15350
|
this.observer = new EventEmitter();
|
16242
15351
|
this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
|
16243
15352
|
this.observer.on(Events.ERROR, forwardMessage);
|
16244
|
-
const
|
15353
|
+
const MediaSource = getMediaSource(config.preferManagedMediaSource) || {
|
15354
|
+
isTypeSupported: () => false
|
15355
|
+
};
|
15356
|
+
const m2tsTypeSupported = {
|
15357
|
+
mpeg: MediaSource.isTypeSupported('audio/mpeg'),
|
15358
|
+
mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
|
15359
|
+
ac3: MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
|
15360
|
+
};
|
16245
15361
|
|
16246
15362
|
// navigator.vendor is not always available in Web Worker
|
16247
15363
|
// refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
|
@@ -16529,7 +15645,7 @@ const TICK_INTERVAL$2 = 100; // how often to tick in ms
|
|
16529
15645
|
|
16530
15646
|
class AudioStreamController extends BaseStreamController {
|
16531
15647
|
constructor(hls, fragmentTracker, keyLoader) {
|
16532
|
-
super(hls, fragmentTracker, keyLoader, 'audio-stream-controller', PlaylistLevelType.AUDIO);
|
15648
|
+
super(hls, fragmentTracker, keyLoader, '[audio-stream-controller]', PlaylistLevelType.AUDIO);
|
16533
15649
|
this.videoBuffer = null;
|
16534
15650
|
this.videoTrackCC = -1;
|
16535
15651
|
this.waitingVideoCC = -1;
|
@@ -16541,24 +15657,27 @@ class AudioStreamController extends BaseStreamController {
|
|
16541
15657
|
this.flushing = false;
|
16542
15658
|
this.bufferFlushed = false;
|
16543
15659
|
this.cachedTrackLoadedData = null;
|
16544
|
-
this.
|
15660
|
+
this._registerListeners();
|
16545
15661
|
}
|
16546
15662
|
onHandlerDestroying() {
|
16547
|
-
this.
|
15663
|
+
this._unregisterListeners();
|
16548
15664
|
super.onHandlerDestroying();
|
16549
15665
|
this.mainDetails = null;
|
16550
15666
|
this.bufferedTrack = null;
|
16551
15667
|
this.switchingTrack = null;
|
16552
15668
|
}
|
16553
|
-
|
16554
|
-
super.registerListeners();
|
15669
|
+
_registerListeners() {
|
16555
15670
|
const {
|
16556
15671
|
hls
|
16557
15672
|
} = this;
|
15673
|
+
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
15674
|
+
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
15675
|
+
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
16558
15676
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
16559
15677
|
hls.on(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
|
16560
15678
|
hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
16561
15679
|
hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
|
15680
|
+
hls.on(Events.ERROR, this.onError, this);
|
16562
15681
|
hls.on(Events.BUFFER_RESET, this.onBufferReset, this);
|
16563
15682
|
hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
16564
15683
|
hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
@@ -16566,18 +15685,18 @@ class AudioStreamController extends BaseStreamController {
|
|
16566
15685
|
hls.on(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);
|
16567
15686
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
16568
15687
|
}
|
16569
|
-
|
15688
|
+
_unregisterListeners() {
|
16570
15689
|
const {
|
16571
15690
|
hls
|
16572
15691
|
} = this;
|
16573
|
-
|
16574
|
-
|
16575
|
-
|
16576
|
-
super.unregisterListeners();
|
15692
|
+
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
15693
|
+
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
15694
|
+
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
16577
15695
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
16578
15696
|
hls.off(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
|
16579
15697
|
hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
16580
15698
|
hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
|
15699
|
+
hls.off(Events.ERROR, this.onError, this);
|
16581
15700
|
hls.off(Events.BUFFER_RESET, this.onBufferReset, this);
|
16582
15701
|
hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
16583
15702
|
hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
@@ -16720,9 +15839,7 @@ class AudioStreamController extends BaseStreamController {
|
|
16720
15839
|
this.fragmentTracker.removeFragment(waitingData.frag);
|
16721
15840
|
this.waitingData = null;
|
16722
15841
|
this.waitingVideoCC = -1;
|
16723
|
-
|
16724
|
-
this.state = State.IDLE;
|
16725
|
-
}
|
15842
|
+
this.state = State.IDLE;
|
16726
15843
|
}
|
16727
15844
|
}
|
16728
15845
|
resetLoadingState() {
|
@@ -16748,13 +15865,12 @@ class AudioStreamController extends BaseStreamController {
|
|
16748
15865
|
} = this;
|
16749
15866
|
const config = hls.config;
|
16750
15867
|
|
16751
|
-
// 1. if
|
16752
|
-
// 2. if video not attached AND
|
15868
|
+
// 1. if video not attached AND
|
16753
15869
|
// start fragment already requested OR start frag prefetch not enabled
|
16754
|
-
//
|
15870
|
+
// 2. if tracks or track not loaded and selected
|
16755
15871
|
// then exit loop
|
16756
15872
|
// => if media not attached but start frag prefetch is enabled and start frag not requested yet, we will not exit loop
|
16757
|
-
if (!
|
15873
|
+
if (!media && (this.startFragRequested || !config.startFragPrefetch) || !(levels != null && levels[trackId])) {
|
16758
15874
|
return;
|
16759
15875
|
}
|
16760
15876
|
const levelInfo = levels[trackId];
|
@@ -16783,8 +15899,9 @@ class AudioStreamController extends BaseStreamController {
|
|
16783
15899
|
this.state = State.ENDED;
|
16784
15900
|
return;
|
16785
15901
|
}
|
15902
|
+
const mainBufferInfo = this.getFwdBufferInfo(this.videoBuffer ? this.videoBuffer : this.media, PlaylistLevelType.MAIN);
|
16786
15903
|
const bufferLen = bufferInfo.len;
|
16787
|
-
const maxBufLen =
|
15904
|
+
const maxBufLen = this.getMaxBufferLength(mainBufferInfo == null ? void 0 : mainBufferInfo.len);
|
16788
15905
|
const fragments = trackDetails.fragments;
|
16789
15906
|
const start = fragments[0].start;
|
16790
15907
|
let targetBufferTime = this.flushing ? this.getLoadPosition() : bufferInfo.end;
|
@@ -16819,25 +15936,32 @@ class AudioStreamController extends BaseStreamController {
|
|
16819
15936
|
this.bufferFlushed = true;
|
16820
15937
|
return;
|
16821
15938
|
}
|
16822
|
-
|
16823
|
-
|
16824
|
-
|
16825
|
-
|
16826
|
-
|
16827
|
-
|
16828
|
-
|
16829
|
-
|
16830
|
-
|
16831
|
-
|
16832
|
-
|
16833
|
-
|
16834
|
-
|
16835
|
-
|
16836
|
-
|
15939
|
+
|
15940
|
+
// Buffer audio up to one target duration ahead of main buffer
|
15941
|
+
const atBufferSyncLimit = mainBufferInfo && frag.start > mainBufferInfo.end + trackDetails.targetduration;
|
15942
|
+
if (atBufferSyncLimit ||
|
15943
|
+
// Or wait for main buffer after buffing some audio
|
15944
|
+
!(mainBufferInfo != null && mainBufferInfo.len) && bufferInfo.len) {
|
15945
|
+
// Check fragment-tracker for main fragments since GAP segments do not show up in bufferInfo
|
15946
|
+
const mainFrag = this.getAppendedFrag(frag.start, PlaylistLevelType.MAIN);
|
15947
|
+
if (mainFrag === null) {
|
15948
|
+
return;
|
15949
|
+
}
|
15950
|
+
// Bridge gaps in main buffer
|
15951
|
+
atGap || (atGap = !!mainFrag.gap || !!atBufferSyncLimit && mainBufferInfo.len === 0);
|
15952
|
+
if (atBufferSyncLimit && !atGap || atGap && bufferInfo.nextStart && bufferInfo.nextStart < mainFrag.end) {
|
15953
|
+
return;
|
16837
15954
|
}
|
16838
15955
|
}
|
16839
15956
|
this.loadFragment(frag, levelInfo, targetBufferTime);
|
16840
15957
|
}
|
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
|
+
}
|
16841
15965
|
onMediaDetaching() {
|
16842
15966
|
this.videoBuffer = null;
|
16843
15967
|
this.bufferFlushed = this.flushing = false;
|
@@ -16862,25 +15986,26 @@ class AudioStreamController extends BaseStreamController {
|
|
16862
15986
|
this.removeUnbufferedFrags(fragCurrent.start);
|
16863
15987
|
}
|
16864
15988
|
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
|
+
}
|
16865
15996
|
|
16866
15997
|
// should we switch tracks ?
|
16867
15998
|
if (altAudio) {
|
16868
15999
|
this.switchingTrack = data;
|
16869
16000
|
// main audio track are handled by stream-controller, just do something if switching to alt audio track
|
16001
|
+
this.state = State.IDLE;
|
16870
16002
|
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
|
-
}
|
16877
16003
|
} else {
|
16878
|
-
// destroy useless transmuxer when switching audio to main
|
16879
|
-
this.resetTransmuxer();
|
16880
16004
|
this.switchingTrack = null;
|
16881
16005
|
this.bufferedTrack = data;
|
16882
|
-
this.
|
16006
|
+
this.state = State.STOPPED;
|
16883
16007
|
}
|
16008
|
+
this.tick();
|
16884
16009
|
}
|
16885
16010
|
onManifestLoading() {
|
16886
16011
|
this.fragmentTracker.removeAllFragments();
|
@@ -17303,7 +16428,7 @@ class AudioStreamController extends BaseStreamController {
|
|
17303
16428
|
|
17304
16429
|
class AudioTrackController extends BasePlaylistController {
|
17305
16430
|
constructor(hls) {
|
17306
|
-
super(hls, 'audio-track-controller');
|
16431
|
+
super(hls, '[audio-track-controller]');
|
17307
16432
|
this.tracks = [];
|
17308
16433
|
this.groupIds = null;
|
17309
16434
|
this.tracksInGroup = [];
|
@@ -17622,23 +16747,26 @@ const TICK_INTERVAL$1 = 500; // how often to tick in ms
|
|
17622
16747
|
|
17623
16748
|
class SubtitleStreamController extends BaseStreamController {
|
17624
16749
|
constructor(hls, fragmentTracker, keyLoader) {
|
17625
|
-
super(hls, fragmentTracker, keyLoader, 'subtitle-stream-controller', PlaylistLevelType.SUBTITLE);
|
16750
|
+
super(hls, fragmentTracker, keyLoader, '[subtitle-stream-controller]', PlaylistLevelType.SUBTITLE);
|
17626
16751
|
this.currentTrackId = -1;
|
17627
16752
|
this.tracksBuffered = [];
|
17628
16753
|
this.mainDetails = null;
|
17629
|
-
this.
|
16754
|
+
this._registerListeners();
|
17630
16755
|
}
|
17631
16756
|
onHandlerDestroying() {
|
17632
|
-
this.
|
16757
|
+
this._unregisterListeners();
|
17633
16758
|
super.onHandlerDestroying();
|
17634
16759
|
this.mainDetails = null;
|
17635
16760
|
}
|
17636
|
-
|
17637
|
-
super.registerListeners();
|
16761
|
+
_registerListeners() {
|
17638
16762
|
const {
|
17639
16763
|
hls
|
17640
16764
|
} = this;
|
16765
|
+
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
16766
|
+
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
16767
|
+
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
17641
16768
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
16769
|
+
hls.on(Events.ERROR, this.onError, this);
|
17642
16770
|
hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
|
17643
16771
|
hls.on(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
|
17644
16772
|
hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
|
@@ -17646,12 +16774,15 @@ class SubtitleStreamController extends BaseStreamController {
|
|
17646
16774
|
hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
17647
16775
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
17648
16776
|
}
|
17649
|
-
|
17650
|
-
super.unregisterListeners();
|
16777
|
+
_unregisterListeners() {
|
17651
16778
|
const {
|
17652
16779
|
hls
|
17653
16780
|
} = this;
|
16781
|
+
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
16782
|
+
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
16783
|
+
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
17654
16784
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
16785
|
+
hls.off(Events.ERROR, this.onError, this);
|
17655
16786
|
hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
|
17656
16787
|
hls.off(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
|
17657
16788
|
hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
|
@@ -17797,7 +16928,7 @@ class SubtitleStreamController extends BaseStreamController {
|
|
17797
16928
|
} else {
|
17798
16929
|
this.mediaBuffer = null;
|
17799
16930
|
}
|
17800
|
-
if (currentTrack
|
16931
|
+
if (currentTrack) {
|
17801
16932
|
this.setInterval(TICK_INTERVAL$1);
|
17802
16933
|
}
|
17803
16934
|
}
|
@@ -17878,10 +17009,10 @@ class SubtitleStreamController extends BaseStreamController {
|
|
17878
17009
|
return;
|
17879
17010
|
}
|
17880
17011
|
// check to see if the payload needs to be decrypted
|
17881
|
-
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv &&
|
17012
|
+
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
|
17882
17013
|
const startTime = performance.now();
|
17883
17014
|
// decrypt the subtitles
|
17884
|
-
this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer
|
17015
|
+
this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
|
17885
17016
|
hls.trigger(Events.ERROR, {
|
17886
17017
|
type: ErrorTypes.MEDIA_ERROR,
|
17887
17018
|
details: ErrorDetails.FRAG_DECRYPT_ERROR,
|
@@ -17930,8 +17061,9 @@ class SubtitleStreamController extends BaseStreamController {
|
|
17930
17061
|
end: targetBufferTime,
|
17931
17062
|
len: bufferLen
|
17932
17063
|
} = bufferedInfo;
|
17064
|
+
const mainBufferInfo = this.getFwdBufferInfo(this.media, PlaylistLevelType.MAIN);
|
17933
17065
|
const trackDetails = track.details;
|
17934
|
-
const maxBufLen = this.
|
17066
|
+
const maxBufLen = this.getMaxBufferLength(mainBufferInfo == null ? void 0 : mainBufferInfo.len) + trackDetails.levelTargetDuration;
|
17935
17067
|
if (bufferLen > maxBufLen) {
|
17936
17068
|
return;
|
17937
17069
|
}
|
@@ -17968,6 +17100,13 @@ class SubtitleStreamController extends BaseStreamController {
|
|
17968
17100
|
}
|
17969
17101
|
}
|
17970
17102
|
}
|
17103
|
+
getMaxBufferLength(mainBufferLength) {
|
17104
|
+
const maxConfigBuffer = super.getMaxBufferLength();
|
17105
|
+
if (!mainBufferLength) {
|
17106
|
+
return maxConfigBuffer;
|
17107
|
+
}
|
17108
|
+
return Math.max(maxConfigBuffer, mainBufferLength);
|
17109
|
+
}
|
17971
17110
|
loadFragment(frag, level, targetBufferTime) {
|
17972
17111
|
this.fragCurrent = frag;
|
17973
17112
|
if (frag.sn === 'initSegment') {
|
@@ -18007,7 +17146,7 @@ class BufferableInstance {
|
|
18007
17146
|
|
18008
17147
|
class SubtitleTrackController extends BasePlaylistController {
|
18009
17148
|
constructor(hls) {
|
18010
|
-
super(hls, 'subtitle-track-controller');
|
17149
|
+
super(hls, '[subtitle-track-controller]');
|
18011
17150
|
this.media = null;
|
18012
17151
|
this.tracks = [];
|
18013
17152
|
this.groupIds = null;
|
@@ -18016,10 +17155,10 @@ class SubtitleTrackController extends BasePlaylistController {
|
|
18016
17155
|
this.currentTrack = null;
|
18017
17156
|
this.selectDefaultTrack = true;
|
18018
17157
|
this.queuedDefaultTrack = -1;
|
17158
|
+
this.asyncPollTrackChange = () => this.pollTrackChange(0);
|
18019
17159
|
this.useTextTrackPolling = false;
|
18020
17160
|
this.subtitlePollingInterval = -1;
|
18021
17161
|
this._subtitleDisplay = true;
|
18022
|
-
this.asyncPollTrackChange = () => this.pollTrackChange(0);
|
18023
17162
|
this.onTextTracksChanged = () => {
|
18024
17163
|
if (!this.useTextTrackPolling) {
|
18025
17164
|
self.clearInterval(this.subtitlePollingInterval);
|
@@ -18053,7 +17192,6 @@ class SubtitleTrackController extends BasePlaylistController {
|
|
18053
17192
|
this.tracks.length = 0;
|
18054
17193
|
this.tracksInGroup.length = 0;
|
18055
17194
|
this.currentTrack = null;
|
18056
|
-
// @ts-ignore
|
18057
17195
|
this.onTextTracksChanged = this.asyncPollTrackChange = null;
|
18058
17196
|
super.destroy();
|
18059
17197
|
}
|
@@ -18465,22 +17603,24 @@ class BufferOperationQueue {
|
|
18465
17603
|
this.executeNext(type);
|
18466
17604
|
}
|
18467
17605
|
}
|
17606
|
+
insertAbort(operation, type) {
|
17607
|
+
const queue = this.queues[type];
|
17608
|
+
queue.unshift(operation);
|
17609
|
+
this.executeNext(type);
|
17610
|
+
}
|
18468
17611
|
appendBlocker(type) {
|
18469
|
-
|
18470
|
-
|
18471
|
-
|
18472
|
-
onStart: () => {},
|
18473
|
-
onComplete: () => {},
|
18474
|
-
onError: () => {}
|
18475
|
-
};
|
18476
|
-
this.append(operation, type);
|
17612
|
+
let execute;
|
17613
|
+
const promise = new Promise(resolve => {
|
17614
|
+
execute = resolve;
|
18477
17615
|
});
|
18478
|
-
|
18479
|
-
|
18480
|
-
|
18481
|
-
|
18482
|
-
|
18483
|
-
}
|
17616
|
+
const operation = {
|
17617
|
+
execute,
|
17618
|
+
onStart: () => {},
|
17619
|
+
onComplete: () => {},
|
17620
|
+
onError: () => {}
|
17621
|
+
};
|
17622
|
+
this.append(operation, type);
|
17623
|
+
return promise;
|
18484
17624
|
}
|
18485
17625
|
executeNext(type) {
|
18486
17626
|
const queue = this.queues[type];
|
@@ -18512,9 +17652,8 @@ class BufferOperationQueue {
|
|
18512
17652
|
}
|
18513
17653
|
|
18514
17654
|
const VIDEO_CODEC_PROFILE_REPLACE = /(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\.[^.,]+)+/;
|
18515
|
-
class BufferController
|
18516
|
-
constructor(hls
|
18517
|
-
super('buffer-controller', hls.logger);
|
17655
|
+
class BufferController {
|
17656
|
+
constructor(hls) {
|
18518
17657
|
// The level details used to determine duration, target-duration and live
|
18519
17658
|
this.details = null;
|
18520
17659
|
// cache the self generated object url to detect hijack of video tag
|
@@ -18524,7 +17663,6 @@ class BufferController extends Logger {
|
|
18524
17663
|
// References to event listeners for each SourceBuffer, so that they can be referenced for event removal
|
18525
17664
|
this.listeners = void 0;
|
18526
17665
|
this.hls = void 0;
|
18527
|
-
this.fragmentTracker = void 0;
|
18528
17666
|
// The number of BUFFER_CODEC events received before any sourceBuffers are created
|
18529
17667
|
this.bufferCodecEventsExpected = 0;
|
18530
17668
|
// The total number of BUFFER_CODEC events received
|
@@ -18535,10 +17673,6 @@ class BufferController extends Logger {
|
|
18535
17673
|
this.mediaSource = null;
|
18536
17674
|
// Last MP3 audio chunk appended
|
18537
17675
|
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;
|
18542
17676
|
this.appendSource = void 0;
|
18543
17677
|
// counters
|
18544
17678
|
this.appendErrors = {
|
@@ -18549,6 +17683,9 @@ class BufferController extends Logger {
|
|
18549
17683
|
this.tracks = {};
|
18550
17684
|
this.pendingTracks = {};
|
18551
17685
|
this.sourceBuffer = void 0;
|
17686
|
+
this.log = void 0;
|
17687
|
+
this.warn = void 0;
|
17688
|
+
this.error = void 0;
|
18552
17689
|
this._onEndStreaming = event => {
|
18553
17690
|
if (!this.hls) {
|
18554
17691
|
return;
|
@@ -18570,10 +17707,7 @@ class BufferController extends Logger {
|
|
18570
17707
|
this.log('Media source opened');
|
18571
17708
|
if (media) {
|
18572
17709
|
media.removeEventListener('emptied', this._onMediaEmptied);
|
18573
|
-
|
18574
|
-
if (durationAndRange) {
|
18575
|
-
this.updateMediaSource(durationAndRange);
|
18576
|
-
}
|
17710
|
+
this.updateMediaElementDuration();
|
18577
17711
|
this.hls.trigger(Events.MEDIA_ATTACHED, {
|
18578
17712
|
media,
|
18579
17713
|
mediaSource: mediaSource
|
@@ -18597,12 +17731,15 @@ class BufferController extends Logger {
|
|
18597
17731
|
_objectUrl
|
18598
17732
|
} = this;
|
18599
17733
|
if (mediaSrc !== _objectUrl) {
|
18600
|
-
|
17734
|
+
logger.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
|
18601
17735
|
}
|
18602
17736
|
};
|
18603
17737
|
this.hls = hls;
|
18604
|
-
|
18605
|
-
this.appendSource = hls.config.preferManagedMediaSource;
|
17738
|
+
const logPrefix = '[buffer-controller]';
|
17739
|
+
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);
|
18606
17743
|
this._initSourceBuffer();
|
18607
17744
|
this.registerListeners();
|
18608
17745
|
}
|
@@ -18614,13 +17751,7 @@ class BufferController extends Logger {
|
|
18614
17751
|
this.details = null;
|
18615
17752
|
this.lastMpegAudioChunk = null;
|
18616
17753
|
// @ts-ignore
|
18617
|
-
this.hls =
|
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;
|
17754
|
+
this.hls = null;
|
18624
17755
|
}
|
18625
17756
|
registerListeners() {
|
18626
17757
|
const {
|
@@ -18670,8 +17801,6 @@ class BufferController extends Logger {
|
|
18670
17801
|
audiovideo: 0
|
18671
17802
|
};
|
18672
17803
|
this.lastMpegAudioChunk = null;
|
18673
|
-
this.blockedAudioAppend = null;
|
18674
|
-
this.lastVideoAppendEnd = 0;
|
18675
17804
|
}
|
18676
17805
|
onManifestLoading() {
|
18677
17806
|
this.bufferCodecEventsExpected = this._bufferCodecEventsTotal = 0;
|
@@ -18700,8 +17829,10 @@ class BufferController extends Logger {
|
|
18700
17829
|
ms.addEventListener('sourceopen', this._onMediaSourceOpen);
|
18701
17830
|
ms.addEventListener('sourceended', this._onMediaSourceEnded);
|
18702
17831
|
ms.addEventListener('sourceclose', this._onMediaSourceClose);
|
18703
|
-
|
18704
|
-
|
17832
|
+
if (this.appendSource) {
|
17833
|
+
ms.addEventListener('startstreaming', this._onStartStreaming);
|
17834
|
+
ms.addEventListener('endstreaming', this._onEndStreaming);
|
17835
|
+
}
|
18705
17836
|
|
18706
17837
|
// cache the locally generated object url
|
18707
17838
|
const objectUrl = this._objectUrl = self.URL.createObjectURL(ms);
|
@@ -18748,8 +17879,10 @@ class BufferController extends Logger {
|
|
18748
17879
|
mediaSource.removeEventListener('sourceopen', this._onMediaSourceOpen);
|
18749
17880
|
mediaSource.removeEventListener('sourceended', this._onMediaSourceEnded);
|
18750
17881
|
mediaSource.removeEventListener('sourceclose', this._onMediaSourceClose);
|
18751
|
-
|
18752
|
-
|
17882
|
+
if (this.appendSource) {
|
17883
|
+
mediaSource.removeEventListener('startstreaming', this._onStartStreaming);
|
17884
|
+
mediaSource.removeEventListener('endstreaming', this._onEndStreaming);
|
17885
|
+
}
|
18753
17886
|
|
18754
17887
|
// Detach properly the MediaSource from the HTMLMediaElement as
|
18755
17888
|
// suggested in https://github.com/w3c/media-source/issues/53.
|
@@ -18785,7 +17918,6 @@ class BufferController extends Logger {
|
|
18785
17918
|
this.resetBuffer(type);
|
18786
17919
|
});
|
18787
17920
|
this._initSourceBuffer();
|
18788
|
-
this.hls.resumeBuffering();
|
18789
17921
|
}
|
18790
17922
|
resetBuffer(type) {
|
18791
17923
|
const sb = this.sourceBuffer[type];
|
@@ -18809,10 +17941,9 @@ class BufferController extends Logger {
|
|
18809
17941
|
const trackNames = Object.keys(data);
|
18810
17942
|
trackNames.forEach(trackName => {
|
18811
17943
|
if (sourceBufferCount) {
|
18812
|
-
var _track$buffer;
|
18813
17944
|
// check if SourceBuffer codec needs to change
|
18814
17945
|
const track = this.tracks[trackName];
|
18815
|
-
if (track && typeof
|
17946
|
+
if (track && typeof track.buffer.changeType === 'function') {
|
18816
17947
|
var _trackCodec;
|
18817
17948
|
const {
|
18818
17949
|
id,
|
@@ -18827,7 +17958,7 @@ class BufferController extends Logger {
|
|
18827
17958
|
const nextCodec = (_trackCodec = trackCodec) == null ? void 0 : _trackCodec.replace(VIDEO_CODEC_PROFILE_REPLACE, '$1');
|
18828
17959
|
if (trackCodec && currentCodec !== nextCodec) {
|
18829
17960
|
if (trackName.slice(0, 5) === 'audio') {
|
18830
|
-
trackCodec = getCodecCompatibleName(trackCodec, this.
|
17961
|
+
trackCodec = getCodecCompatibleName(trackCodec, this.appendSource);
|
18831
17962
|
}
|
18832
17963
|
const mimeType = `${container};codecs=${trackCodec}`;
|
18833
17964
|
this.appendChangeType(trackName, mimeType);
|
@@ -18882,54 +18013,20 @@ class BufferController extends Logger {
|
|
18882
18013
|
};
|
18883
18014
|
operationQueue.append(operation, type, !!this.pendingTracks[type]);
|
18884
18015
|
}
|
18885
|
-
blockAudio(partOrFrag) {
|
18886
|
-
var _this$fragmentTracker;
|
18887
|
-
const pStart = partOrFrag.start;
|
18888
|
-
const pTime = pStart + partOrFrag.duration * 0.05;
|
18889
|
-
const atGap = ((_this$fragmentTracker = this.fragmentTracker.getAppendedFrag(pStart, PlaylistLevelType.MAIN)) == null ? void 0 : _this$fragmentTracker.gap) === true;
|
18890
|
-
if (atGap) {
|
18891
|
-
return;
|
18892
|
-
}
|
18893
|
-
const op = {
|
18894
|
-
execute: () => {
|
18895
|
-
var _this$fragmentTracker2;
|
18896
|
-
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) {
|
18897
|
-
this.blockedAudioAppend = null;
|
18898
|
-
this.operationQueue.shiftAndExecuteNext('audio');
|
18899
|
-
}
|
18900
|
-
},
|
18901
|
-
onStart: () => {},
|
18902
|
-
onComplete: () => {},
|
18903
|
-
onError: () => {}
|
18904
|
-
};
|
18905
|
-
this.blockedAudioAppend = {
|
18906
|
-
op,
|
18907
|
-
frag: partOrFrag
|
18908
|
-
};
|
18909
|
-
this.operationQueue.append(op, 'audio', true);
|
18910
|
-
}
|
18911
|
-
unblockAudio() {
|
18912
|
-
const blockedAudioAppend = this.blockedAudioAppend;
|
18913
|
-
if (blockedAudioAppend) {
|
18914
|
-
this.blockedAudioAppend = null;
|
18915
|
-
this.operationQueue.unblockAudio(blockedAudioAppend.op);
|
18916
|
-
}
|
18917
|
-
}
|
18918
18016
|
onBufferAppending(event, eventData) {
|
18919
18017
|
const {
|
18018
|
+
hls,
|
18920
18019
|
operationQueue,
|
18921
18020
|
tracks
|
18922
18021
|
} = this;
|
18923
18022
|
const {
|
18924
18023
|
data,
|
18925
18024
|
type,
|
18926
|
-
parent,
|
18927
18025
|
frag,
|
18928
18026
|
part,
|
18929
18027
|
chunkMeta
|
18930
18028
|
} = eventData;
|
18931
18029
|
const chunkStats = chunkMeta.buffering[type];
|
18932
|
-
const sn = frag.sn;
|
18933
18030
|
const bufferAppendingStart = self.performance.now();
|
18934
18031
|
chunkStats.start = bufferAppendingStart;
|
18935
18032
|
const fragBuffering = frag.stats.buffering;
|
@@ -18952,36 +18049,7 @@ class BufferController extends Logger {
|
|
18952
18049
|
checkTimestampOffset = !this.lastMpegAudioChunk || chunkMeta.id === 1 || this.lastMpegAudioChunk.sn !== chunkMeta.sn;
|
18953
18050
|
this.lastMpegAudioChunk = chunkMeta;
|
18954
18051
|
}
|
18955
|
-
|
18956
|
-
// Block audio append until overlapping video append
|
18957
|
-
const videoSb = this.sourceBuffer.video;
|
18958
|
-
if (videoSb && sn !== 'initSegment') {
|
18959
|
-
const partOrFrag = part || frag;
|
18960
|
-
const blockedAudioAppend = this.blockedAudioAppend;
|
18961
|
-
if (type === 'audio' && parent !== 'main' && !this.blockedAudioAppend) {
|
18962
|
-
const pStart = partOrFrag.start;
|
18963
|
-
const pTime = pStart + partOrFrag.duration * 0.05;
|
18964
|
-
const vbuffered = videoSb.buffered;
|
18965
|
-
const vappending = this.operationQueue.current('video');
|
18966
|
-
if (!vbuffered.length && !vappending) {
|
18967
|
-
// wait for video before appending audio
|
18968
|
-
this.blockAudio(partOrFrag);
|
18969
|
-
} else if (!vappending && !BufferHelper.isBuffered(videoSb, pTime) && this.lastVideoAppendEnd < pTime) {
|
18970
|
-
// audio is ahead of video
|
18971
|
-
this.blockAudio(partOrFrag);
|
18972
|
-
}
|
18973
|
-
} else if (type === 'video') {
|
18974
|
-
const videoAppendEnd = partOrFrag.end;
|
18975
|
-
if (blockedAudioAppend) {
|
18976
|
-
const audioStart = blockedAudioAppend.frag.start;
|
18977
|
-
if (videoAppendEnd > audioStart || videoAppendEnd < this.lastVideoAppendEnd || BufferHelper.isBuffered(videoSb, audioStart)) {
|
18978
|
-
this.unblockAudio();
|
18979
|
-
}
|
18980
|
-
}
|
18981
|
-
this.lastVideoAppendEnd = videoAppendEnd;
|
18982
|
-
}
|
18983
|
-
}
|
18984
|
-
const fragStart = (part || frag).start;
|
18052
|
+
const fragStart = frag.start;
|
18985
18053
|
const operation = {
|
18986
18054
|
execute: () => {
|
18987
18055
|
chunkStats.executeStart = self.performance.now();
|
@@ -18990,7 +18058,7 @@ class BufferController extends Logger {
|
|
18990
18058
|
if (sb) {
|
18991
18059
|
const delta = fragStart - sb.timestampOffset;
|
18992
18060
|
if (Math.abs(delta) >= 0.1) {
|
18993
|
-
this.log(`Updating audio SourceBuffer timestampOffset to ${fragStart} (delta: ${delta}) sn: ${sn})`);
|
18061
|
+
this.log(`Updating audio SourceBuffer timestampOffset to ${fragStart} (delta: ${delta}) sn: ${frag.sn})`);
|
18994
18062
|
sb.timestampOffset = fragStart;
|
18995
18063
|
}
|
18996
18064
|
}
|
@@ -19057,21 +18125,22 @@ class BufferController extends Logger {
|
|
19057
18125
|
/* with UHD content, we could get loop of quota exceeded error until
|
19058
18126
|
browser is able to evict some data from sourcebuffer. Retrying can help recover.
|
19059
18127
|
*/
|
19060
|
-
this.warn(`Failed ${appendErrorCount}/${
|
19061
|
-
if (appendErrorCount >=
|
18128
|
+
this.warn(`Failed ${appendErrorCount}/${hls.config.appendErrorMaxRetry} times to append segment in "${type}" sourceBuffer`);
|
18129
|
+
if (appendErrorCount >= hls.config.appendErrorMaxRetry) {
|
19062
18130
|
event.fatal = true;
|
19063
18131
|
}
|
19064
18132
|
}
|
19065
|
-
|
18133
|
+
hls.trigger(Events.ERROR, event);
|
19066
18134
|
}
|
19067
18135
|
};
|
19068
18136
|
operationQueue.append(operation, type, !!this.pendingTracks[type]);
|
19069
18137
|
}
|
19070
|
-
|
19071
|
-
|
19072
|
-
|
19073
|
-
|
19074
|
-
|
18138
|
+
onBufferFlushing(event, data) {
|
18139
|
+
const {
|
18140
|
+
operationQueue
|
18141
|
+
} = this;
|
18142
|
+
const flushOperation = type => ({
|
18143
|
+
execute: this.removeExecutor.bind(this, type, data.startOffset, data.endOffset),
|
19075
18144
|
onStart: () => {
|
19076
18145
|
// logger.debug(`[buffer-controller]: Started flushing ${data.startOffset} -> ${data.endOffset} for ${type} Source Buffer`);
|
19077
18146
|
},
|
@@ -19084,22 +18153,12 @@ class BufferController extends Logger {
|
|
19084
18153
|
onError: error => {
|
19085
18154
|
this.warn(`Failed to remove from ${type} SourceBuffer`, error);
|
19086
18155
|
}
|
19087
|
-
};
|
19088
|
-
|
19089
|
-
|
19090
|
-
const {
|
19091
|
-
operationQueue
|
19092
|
-
} = this;
|
19093
|
-
const {
|
19094
|
-
type,
|
19095
|
-
startOffset,
|
19096
|
-
endOffset
|
19097
|
-
} = data;
|
19098
|
-
if (type) {
|
19099
|
-
operationQueue.append(this.getFlushOp(type, startOffset, endOffset), type);
|
18156
|
+
});
|
18157
|
+
if (data.type) {
|
18158
|
+
operationQueue.append(flushOperation(data.type), data.type);
|
19100
18159
|
} else {
|
19101
|
-
this.getSourceBufferTypes().forEach(
|
19102
|
-
operationQueue.append(
|
18160
|
+
this.getSourceBufferTypes().forEach(type => {
|
18161
|
+
operationQueue.append(flushOperation(type), type);
|
19103
18162
|
});
|
19104
18163
|
}
|
19105
18164
|
}
|
@@ -19146,9 +18205,6 @@ class BufferController extends Logger {
|
|
19146
18205
|
// on BUFFER_EOS mark matching sourcebuffer(s) as ended and trigger checkEos()
|
19147
18206
|
// an undefined data.type will mark all buffers as EOS.
|
19148
18207
|
onBufferEos(event, data) {
|
19149
|
-
if (data.type === 'video') {
|
19150
|
-
this.unblockAudio();
|
19151
|
-
}
|
19152
18208
|
const ended = this.getSourceBufferTypes().reduce((acc, type) => {
|
19153
18209
|
const sb = this.sourceBuffer[type];
|
19154
18210
|
if (sb && (!data.type || data.type === type)) {
|
@@ -19191,14 +18247,10 @@ class BufferController extends Logger {
|
|
19191
18247
|
return;
|
19192
18248
|
}
|
19193
18249
|
this.details = details;
|
19194
|
-
const durationAndRange = this.getDurationAndRange();
|
19195
|
-
if (!durationAndRange) {
|
19196
|
-
return;
|
19197
|
-
}
|
19198
18250
|
if (this.getSourceBufferTypes().length) {
|
19199
|
-
this.blockBuffers(
|
18251
|
+
this.blockBuffers(this.updateMediaElementDuration.bind(this));
|
19200
18252
|
} else {
|
19201
|
-
this.
|
18253
|
+
this.updateMediaElementDuration();
|
19202
18254
|
}
|
19203
18255
|
}
|
19204
18256
|
trimBuffers() {
|
@@ -19303,9 +18355,9 @@ class BufferController extends Logger {
|
|
19303
18355
|
* 'liveDurationInfinity` is set to `true`
|
19304
18356
|
* More details: https://github.com/video-dev/hls.js/issues/355
|
19305
18357
|
*/
|
19306
|
-
|
18358
|
+
updateMediaElementDuration() {
|
19307
18359
|
if (!this.details || !this.media || !this.mediaSource || this.mediaSource.readyState !== 'open') {
|
19308
|
-
return
|
18360
|
+
return;
|
19309
18361
|
}
|
19310
18362
|
const {
|
19311
18363
|
details,
|
@@ -19319,41 +18371,25 @@ class BufferController extends Logger {
|
|
19319
18371
|
if (details.live && hls.config.liveDurationInfinity) {
|
19320
18372
|
// Override duration to Infinity
|
19321
18373
|
mediaSource.duration = Infinity;
|
19322
|
-
|
19323
|
-
if (len && details.live && !!mediaSource.setLiveSeekableRange) {
|
19324
|
-
const start = Math.max(0, details.fragments[0].start);
|
19325
|
-
const end = Math.max(start, start + details.totalduration);
|
19326
|
-
return {
|
19327
|
-
duration: Infinity,
|
19328
|
-
start,
|
19329
|
-
end
|
19330
|
-
};
|
19331
|
-
}
|
19332
|
-
return {
|
19333
|
-
duration: Infinity
|
19334
|
-
};
|
18374
|
+
this.updateSeekableRange(details);
|
19335
18375
|
} else if (levelDuration > msDuration && levelDuration > mediaDuration || !isFiniteNumber(mediaDuration)) {
|
19336
|
-
|
19337
|
-
|
19338
|
-
|
18376
|
+
// levelDuration was the last value we set.
|
18377
|
+
// not using mediaSource.duration as the browser may tweak this value
|
18378
|
+
// only update Media Source duration if its value increase, this is to avoid
|
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;
|
19339
18382
|
}
|
19340
|
-
return null;
|
19341
18383
|
}
|
19342
|
-
|
19343
|
-
|
19344
|
-
|
19345
|
-
|
19346
|
-
|
19347
|
-
|
19348
|
-
|
19349
|
-
|
19350
|
-
|
19351
|
-
this.log(`Updating Media Source duration to ${duration.toFixed(3)}`);
|
19352
|
-
}
|
19353
|
-
this.mediaSource.duration = duration;
|
19354
|
-
if (start !== undefined && end !== undefined) {
|
19355
|
-
this.log(`Media Source duration is set to ${this.mediaSource.duration}. Setting seekable range to ${start}-${end}.`);
|
19356
|
-
this.mediaSource.setLiveSeekableRange(start, end);
|
18384
|
+
updateSeekableRange(levelDetails) {
|
18385
|
+
const mediaSource = this.mediaSource;
|
18386
|
+
const fragments = levelDetails.fragments;
|
18387
|
+
const len = fragments.length;
|
18388
|
+
if (len && levelDetails.live && mediaSource != null && mediaSource.setLiveSeekableRange) {
|
18389
|
+
const start = Math.max(0, fragments[0].start);
|
18390
|
+
const end = Math.max(start, start + levelDetails.totalduration);
|
18391
|
+
this.log(`Media Source duration is set to ${mediaSource.duration}. Setting seekable range to ${start}-${end}.`);
|
18392
|
+
mediaSource.setLiveSeekableRange(start, end);
|
19357
18393
|
}
|
19358
18394
|
}
|
19359
18395
|
checkPendingTracks() {
|
@@ -19411,7 +18447,7 @@ class BufferController extends Logger {
|
|
19411
18447
|
let codec = track.levelCodec || track.codec;
|
19412
18448
|
if (codec) {
|
19413
18449
|
if (trackName.slice(0, 5) === 'audio') {
|
19414
|
-
codec = getCodecCompatibleName(codec, this.
|
18450
|
+
codec = getCodecCompatibleName(codec, this.appendSource);
|
19415
18451
|
}
|
19416
18452
|
}
|
19417
18453
|
const mimeType = `${track.container};codecs=${codec}`;
|
@@ -19423,15 +18459,17 @@ class BufferController extends Logger {
|
|
19423
18459
|
this.addBufferListener(sbName, 'updateend', this._onSBUpdateEnd);
|
19424
18460
|
this.addBufferListener(sbName, 'error', this._onSBUpdateError);
|
19425
18461
|
// ManagedSourceBuffer bufferedchange event
|
19426
|
-
this.
|
19427
|
-
|
19428
|
-
|
19429
|
-
|
19430
|
-
|
19431
|
-
|
19432
|
-
|
19433
|
-
|
19434
|
-
|
18462
|
+
if (this.appendSource) {
|
18463
|
+
this.addBufferListener(sbName, 'bufferedchange', (type, event) => {
|
18464
|
+
// If media was ejected check for a change. Added ranges are redundant with changes on 'updateend' event.
|
18465
|
+
const removedRanges = event.removedRanges;
|
18466
|
+
if (removedRanges != null && removedRanges.length) {
|
18467
|
+
this.hls.trigger(Events.BUFFER_FLUSHED, {
|
18468
|
+
type: trackName
|
18469
|
+
});
|
18470
|
+
}
|
18471
|
+
});
|
18472
|
+
}
|
19435
18473
|
this.tracks[trackName] = {
|
19436
18474
|
buffer: sb,
|
19437
18475
|
codec: codec,
|
@@ -19536,7 +18574,6 @@ class BufferController extends Logger {
|
|
19536
18574
|
}
|
19537
18575
|
return;
|
19538
18576
|
}
|
19539
|
-
sb.ending = false;
|
19540
18577
|
sb.ended = false;
|
19541
18578
|
sb.appendBuffer(data);
|
19542
18579
|
}
|
@@ -19556,14 +18593,10 @@ class BufferController extends Logger {
|
|
19556
18593
|
|
19557
18594
|
// logger.debug(`[buffer-controller]: Blocking ${buffers} SourceBuffer`);
|
19558
18595
|
const blockingOperations = buffers.map(type => operationQueue.appendBlocker(type));
|
19559
|
-
|
19560
|
-
if (audioBlocked) {
|
19561
|
-
this.unblockAudio();
|
19562
|
-
}
|
19563
|
-
Promise.all(blockingOperations).then(result => {
|
18596
|
+
Promise.all(blockingOperations).then(() => {
|
19564
18597
|
// logger.debug(`[buffer-controller]: Blocking operation resolved; unblocking ${buffers} SourceBuffer`);
|
19565
18598
|
onUnblocked();
|
19566
|
-
buffers.forEach(
|
18599
|
+
buffers.forEach(type => {
|
19567
18600
|
const sb = this.sourceBuffer[type];
|
19568
18601
|
// Only cycle the queue if the SB is not updating. There's a bug in Chrome which sets the SB updating flag to
|
19569
18602
|
// true when changing the MediaSource duration (https://bugs.chromium.org/p/chromium/issues/detail?id=959359&can=2&q=mediasource%20duration)
|
@@ -21989,12 +21022,14 @@ class TimelineController {
|
|
21989
21022
|
this.cea608Parser1 = this.cea608Parser2 = undefined;
|
21990
21023
|
}
|
21991
21024
|
initCea608Parsers() {
|
21992
|
-
|
21993
|
-
|
21994
|
-
|
21995
|
-
|
21996
|
-
|
21997
|
-
|
21025
|
+
if (this.config.enableCEA708Captions && (!this.cea608Parser1 || !this.cea608Parser2)) {
|
21026
|
+
const channel1 = new OutputFilter(this, 'textTrack1');
|
21027
|
+
const channel2 = new OutputFilter(this, 'textTrack2');
|
21028
|
+
const channel3 = new OutputFilter(this, 'textTrack3');
|
21029
|
+
const channel4 = new OutputFilter(this, 'textTrack4');
|
21030
|
+
this.cea608Parser1 = new Cea608Parser(1, channel1, channel2);
|
21031
|
+
this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
|
21032
|
+
}
|
21998
21033
|
}
|
21999
21034
|
addCues(trackName, startTime, endTime, screen, cueRanges) {
|
22000
21035
|
// skip cues which overlap more than 50% with previously parsed time ranges
|
@@ -22232,7 +21267,7 @@ class TimelineController {
|
|
22232
21267
|
if (inUseTracks != null && inUseTracks.length) {
|
22233
21268
|
const unusedTextTracks = inUseTracks.filter(t => t !== null).map(t => t.label);
|
22234
21269
|
if (unusedTextTracks.length) {
|
22235
|
-
|
21270
|
+
logger.warn(`Media element contains unused subtitle tracks: ${unusedTextTracks.join(', ')}. Replace media element for each source to clear TextTracks and captions menu.`);
|
22236
21271
|
}
|
22237
21272
|
}
|
22238
21273
|
} else if (this.tracks.length) {
|
@@ -22277,23 +21312,26 @@ class TimelineController {
|
|
22277
21312
|
return level == null ? void 0 : level.attrs['CLOSED-CAPTIONS'];
|
22278
21313
|
}
|
22279
21314
|
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
|
+
}
|
22280
21326
|
// if this frag isn't contiguous, clear the parser so cues with bad start/end times aren't added to the textTrack
|
22281
|
-
if (
|
21327
|
+
if (data.frag.type === PlaylistLevelType.MAIN) {
|
22282
21328
|
var _data$part$index, _data$part;
|
22283
|
-
const {
|
22284
|
-
cea608Parser1,
|
22285
|
-
cea608Parser2,
|
22286
|
-
lastSn
|
22287
|
-
} = this;
|
22288
|
-
if (!cea608Parser1 || !cea608Parser2) {
|
22289
|
-
return;
|
22290
|
-
}
|
22291
21329
|
const {
|
22292
21330
|
cc,
|
22293
21331
|
sn
|
22294
21332
|
} = data.frag;
|
22295
|
-
const partIndex = (_data$part$index = (_data$part = data.part) == null ? void 0 : _data$part.index) != null ? _data$part$index : -1;
|
22296
|
-
if (!(sn === lastSn + 1 || sn === lastSn && partIndex ===
|
21333
|
+
const partIndex = (_data$part$index = data == null ? void 0 : (_data$part = data.part) == null ? void 0 : _data$part.index) != null ? _data$part$index : -1;
|
21334
|
+
if (!(sn === lastSn + 1 || sn === lastSn && partIndex === lastPartIndex + 1 || cc === lastCc)) {
|
22297
21335
|
cea608Parser1.reset();
|
22298
21336
|
cea608Parser2.reset();
|
22299
21337
|
}
|
@@ -22350,7 +21388,7 @@ class TimelineController {
|
|
22350
21388
|
frag: frag
|
22351
21389
|
});
|
22352
21390
|
}, error => {
|
22353
|
-
|
21391
|
+
logger.log(`Failed to parse IMSC1: ${error}`);
|
22354
21392
|
hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
|
22355
21393
|
success: false,
|
22356
21394
|
frag: frag,
|
@@ -22391,7 +21429,7 @@ class TimelineController {
|
|
22391
21429
|
this._fallbackToIMSC1(frag, payload);
|
22392
21430
|
}
|
22393
21431
|
// Something went wrong while parsing. Trigger event with success false.
|
22394
|
-
|
21432
|
+
logger.log(`Failed to parse VTT cue: ${error}`);
|
22395
21433
|
if (missingInitPTS && maxAvCC > frag.cc) {
|
22396
21434
|
return;
|
22397
21435
|
}
|
@@ -22452,7 +21490,12 @@ class TimelineController {
|
|
22452
21490
|
this.captionsTracks = {};
|
22453
21491
|
}
|
22454
21492
|
onFragParsingUserdata(event, data) {
|
22455
|
-
|
21493
|
+
this.initCea608Parsers();
|
21494
|
+
const {
|
21495
|
+
cea608Parser1,
|
21496
|
+
cea608Parser2
|
21497
|
+
} = this;
|
21498
|
+
if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
|
22456
21499
|
return;
|
22457
21500
|
}
|
22458
21501
|
const {
|
@@ -22467,12 +21510,9 @@ class TimelineController {
|
|
22467
21510
|
for (let i = 0; i < samples.length; i++) {
|
22468
21511
|
const ccBytes = samples[i].bytes;
|
22469
21512
|
if (ccBytes) {
|
22470
|
-
if (!this.cea608Parser1) {
|
22471
|
-
this.initCea608Parsers();
|
22472
|
-
}
|
22473
21513
|
const ccdatas = this.extractCea608Data(ccBytes);
|
22474
|
-
|
22475
|
-
|
21514
|
+
cea608Parser1.addData(samples[i].pts, ccdatas[0]);
|
21515
|
+
cea608Parser2.addData(samples[i].pts, ccdatas[1]);
|
22476
21516
|
}
|
22477
21517
|
}
|
22478
21518
|
}
|
@@ -22668,10 +21708,10 @@ class CapLevelController {
|
|
22668
21708
|
const hls = this.hls;
|
22669
21709
|
const maxLevel = this.getMaxLevel(levels.length - 1);
|
22670
21710
|
if (maxLevel !== this.autoLevelCapping) {
|
22671
|
-
|
21711
|
+
logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
|
22672
21712
|
}
|
22673
21713
|
hls.autoLevelCapping = maxLevel;
|
22674
|
-
if (hls.
|
21714
|
+
if (hls.autoLevelCapping > this.autoLevelCapping && this.streamController) {
|
22675
21715
|
// if auto level capping has a higher value for the previous one, flush the buffer using nextLevelSwitch
|
22676
21716
|
// usually happen when the user go to the fullscreen mode.
|
22677
21717
|
this.streamController.nextLevelSwitch();
|
@@ -22846,10 +21886,10 @@ class FPSController {
|
|
22846
21886
|
totalDroppedFrames: droppedFrames
|
22847
21887
|
});
|
22848
21888
|
if (droppedFPS > 0) {
|
22849
|
-
//
|
21889
|
+
// logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
|
22850
21890
|
if (currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
|
22851
21891
|
let currentLevel = hls.currentLevel;
|
22852
|
-
|
21892
|
+
logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
|
22853
21893
|
if (currentLevel > 0 && (hls.autoLevelCapping === -1 || hls.autoLevelCapping >= currentLevel)) {
|
22854
21894
|
currentLevel = currentLevel - 1;
|
22855
21895
|
hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, {
|
@@ -22881,6 +21921,7 @@ class FPSController {
|
|
22881
21921
|
}
|
22882
21922
|
}
|
22883
21923
|
|
21924
|
+
const LOGGER_PREFIX = '[eme]';
|
22884
21925
|
/**
|
22885
21926
|
* Controller to deal with encrypted media extensions (EME)
|
22886
21927
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/Encrypted_Media_Extensions_API
|
@@ -22888,9 +21929,8 @@ class FPSController {
|
|
22888
21929
|
* @class
|
22889
21930
|
* @constructor
|
22890
21931
|
*/
|
22891
|
-
class EMEController
|
21932
|
+
class EMEController {
|
22892
21933
|
constructor(hls) {
|
22893
|
-
super('eme', hls.logger);
|
22894
21934
|
this.hls = void 0;
|
22895
21935
|
this.config = void 0;
|
22896
21936
|
this.media = null;
|
@@ -22899,101 +21939,13 @@ class EMEController extends Logger {
|
|
22899
21939
|
this._requestLicenseFailureCount = 0;
|
22900
21940
|
this.mediaKeySessions = [];
|
22901
21941
|
this.keyIdToKeySessionPromise = {};
|
22902
|
-
this.setMediaKeysQueue = EMEController.CDMCleanupPromise ? [EMEController.CDMCleanupPromise] : [];
|
22903
|
-
this.onMediaEncrypted =
|
22904
|
-
|
22905
|
-
|
22906
|
-
|
22907
|
-
|
22908
|
-
|
22909
|
-
|
22910
|
-
// Ignore event when initData is null
|
22911
|
-
if (initData === null) {
|
22912
|
-
return;
|
22913
|
-
}
|
22914
|
-
let keyId;
|
22915
|
-
let keySystemDomain;
|
22916
|
-
if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) {
|
22917
|
-
// Match sinf keyId to playlist skd://keyId=
|
22918
|
-
const json = bin2str(new Uint8Array(initData));
|
22919
|
-
try {
|
22920
|
-
const sinf = base64Decode(JSON.parse(json).sinf);
|
22921
|
-
const tenc = parseSinf(new Uint8Array(sinf));
|
22922
|
-
if (!tenc) {
|
22923
|
-
return;
|
22924
|
-
}
|
22925
|
-
keyId = tenc.subarray(8, 24);
|
22926
|
-
keySystemDomain = KeySystems.FAIRPLAY;
|
22927
|
-
} catch (error) {
|
22928
|
-
this.warn('Failed to parse sinf "encrypted" event message initData');
|
22929
|
-
return;
|
22930
|
-
}
|
22931
|
-
} else {
|
22932
|
-
// Support clear-lead key-session creation (otherwise depend on playlist keys)
|
22933
|
-
const psshInfo = parsePssh(initData);
|
22934
|
-
if (psshInfo === null) {
|
22935
|
-
return;
|
22936
|
-
}
|
22937
|
-
if (psshInfo.version === 0 && psshInfo.systemId === KeySystemIds.WIDEVINE && psshInfo.data) {
|
22938
|
-
keyId = psshInfo.data.subarray(8, 24);
|
22939
|
-
}
|
22940
|
-
keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
|
22941
|
-
}
|
22942
|
-
if (!keySystemDomain || !keyId) {
|
22943
|
-
return;
|
22944
|
-
}
|
22945
|
-
const keyIdHex = Hex.hexDump(keyId);
|
22946
|
-
const {
|
22947
|
-
keyIdToKeySessionPromise,
|
22948
|
-
mediaKeySessions
|
22949
|
-
} = this;
|
22950
|
-
let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
|
22951
|
-
for (let i = 0; i < mediaKeySessions.length; i++) {
|
22952
|
-
// Match playlist key
|
22953
|
-
const keyContext = mediaKeySessions[i];
|
22954
|
-
const decryptdata = keyContext.decryptdata;
|
22955
|
-
if (decryptdata.pssh || !decryptdata.keyId) {
|
22956
|
-
continue;
|
22957
|
-
}
|
22958
|
-
const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
|
22959
|
-
if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
|
22960
|
-
keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
|
22961
|
-
delete keyIdToKeySessionPromise[oldKeyIdHex];
|
22962
|
-
decryptdata.pssh = new Uint8Array(initData);
|
22963
|
-
decryptdata.keyId = keyId;
|
22964
|
-
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
|
22965
|
-
return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
|
22966
|
-
});
|
22967
|
-
break;
|
22968
|
-
}
|
22969
|
-
}
|
22970
|
-
if (!keySessionContextPromise) {
|
22971
|
-
// Clear-lead key (not encountered in playlist)
|
22972
|
-
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
|
22973
|
-
keySystem,
|
22974
|
-
mediaKeys
|
22975
|
-
}) => {
|
22976
|
-
var _keySystemToKeySystem;
|
22977
|
-
this.throwIfDestroyed();
|
22978
|
-
const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
|
22979
|
-
decryptdata.pssh = new Uint8Array(initData);
|
22980
|
-
decryptdata.keyId = keyId;
|
22981
|
-
return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
|
22982
|
-
this.throwIfDestroyed();
|
22983
|
-
const keySessionContext = this.createMediaKeySessionContext({
|
22984
|
-
decryptdata,
|
22985
|
-
keySystem,
|
22986
|
-
mediaKeys
|
22987
|
-
});
|
22988
|
-
return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
|
22989
|
-
});
|
22990
|
-
});
|
22991
|
-
}
|
22992
|
-
keySessionContextPromise.catch(error => this.handleError(error));
|
22993
|
-
};
|
22994
|
-
this.onWaitingForKey = event => {
|
22995
|
-
this.log(`"${event.type}" event`);
|
22996
|
-
};
|
21942
|
+
this.setMediaKeysQueue = EMEController.CDMCleanupPromise ? [EMEController.CDMCleanupPromise] : [];
|
21943
|
+
this.onMediaEncrypted = this._onMediaEncrypted.bind(this);
|
21944
|
+
this.onWaitingForKey = this._onWaitingForKey.bind(this);
|
21945
|
+
this.debug = logger.debug.bind(logger, LOGGER_PREFIX);
|
21946
|
+
this.log = logger.log.bind(logger, LOGGER_PREFIX);
|
21947
|
+
this.warn = logger.warn.bind(logger, LOGGER_PREFIX);
|
21948
|
+
this.error = logger.error.bind(logger, LOGGER_PREFIX);
|
22997
21949
|
this.hls = hls;
|
22998
21950
|
this.config = hls.config;
|
22999
21951
|
this.registerListeners();
|
@@ -23007,9 +21959,9 @@ class EMEController extends Logger {
|
|
23007
21959
|
config.licenseXhrSetup = config.licenseResponseCallback = undefined;
|
23008
21960
|
config.drmSystems = config.drmSystemOptions = {};
|
23009
21961
|
// @ts-ignore
|
23010
|
-
this.hls = this.
|
21962
|
+
this.hls = this.onMediaEncrypted = this.onWaitingForKey = this.keyIdToKeySessionPromise = null;
|
23011
21963
|
// @ts-ignore
|
23012
|
-
this.
|
21964
|
+
this.config = null;
|
23013
21965
|
}
|
23014
21966
|
registerListeners() {
|
23015
21967
|
this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
@@ -23273,6 +22225,100 @@ class EMEController extends Logger {
|
|
23273
22225
|
}
|
23274
22226
|
return this.attemptKeySystemAccess(keySystemsToAttempt);
|
23275
22227
|
}
|
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
|
+
}
|
23276
22322
|
attemptSetMediaKeys(keySystem, mediaKeys) {
|
23277
22323
|
const queue = this.setMediaKeysQueue.slice();
|
23278
22324
|
this.log(`Setting media-keys for "${keySystem}"`);
|
@@ -23865,6 +22911,20 @@ class SfItem {
|
|
23865
22911
|
}
|
23866
22912
|
}
|
23867
22913
|
|
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
|
+
|
23868
22928
|
const DICT = 'Dict';
|
23869
22929
|
|
23870
22930
|
function format(value) {
|
@@ -23888,27 +22948,29 @@ function throwError(action, src, type, cause) {
|
|
23888
22948
|
});
|
23889
22949
|
}
|
23890
22950
|
|
23891
|
-
|
23892
|
-
return throwError('serialize', src, type, cause);
|
23893
|
-
}
|
22951
|
+
const BARE_ITEM = 'Bare Item';
|
23894
22952
|
|
23895
|
-
|
23896
|
-
|
23897
|
-
|
23898
|
-
|
23899
|
-
|
23900
|
-
|
23901
|
-
|
23902
|
-
|
23903
|
-
|
23904
|
-
|
23905
|
-
this.description = description;
|
23906
|
-
}
|
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;
|
23907
22963
|
}
|
23908
22964
|
|
23909
|
-
const
|
22965
|
+
const STRING_REGEX = /[\x00-\x1f\x7f]+/; // eslint-disable-line no-control-regex
|
23910
22966
|
|
23911
|
-
const
|
22967
|
+
const TOKEN = 'Token';
|
22968
|
+
|
22969
|
+
const KEY = 'Key';
|
22970
|
+
|
22971
|
+
function serializeError(src, type, cause) {
|
22972
|
+
return throwError('serialize', src, type, cause);
|
22973
|
+
}
|
23912
22974
|
|
23913
22975
|
// 4.1.9. Serializing a Boolean
|
23914
22976
|
//
|
@@ -23947,8 +23009,6 @@ function base64encode(binary) {
|
|
23947
23009
|
return btoa(String.fromCharCode(...binary));
|
23948
23010
|
}
|
23949
23011
|
|
23950
|
-
const BYTES = 'Byte Sequence';
|
23951
|
-
|
23952
23012
|
// 4.1.8. Serializing a Byte Sequence
|
23953
23013
|
//
|
23954
23014
|
// Given a Byte Sequence as input_bytes, return an ASCII string suitable
|
@@ -23980,12 +23040,6 @@ function serializeByteSequence(value) {
|
|
23980
23040
|
return `:${base64encode(value)}:`;
|
23981
23041
|
}
|
23982
23042
|
|
23983
|
-
const INTEGER = 'Integer';
|
23984
|
-
|
23985
|
-
function isInvalidInt(value) {
|
23986
|
-
return value < -999999999999999 || 999999999999999 < value;
|
23987
|
-
}
|
23988
|
-
|
23989
23043
|
// 4.1.4. Serializing an Integer
|
23990
23044
|
//
|
23991
23045
|
// Given an Integer as input_integer, return an ASCII string suitable
|
@@ -24051,8 +23105,6 @@ function roundToEven(value, precision) {
|
|
24051
23105
|
}
|
24052
23106
|
}
|
24053
23107
|
|
24054
|
-
const DECIMAL = 'Decimal';
|
24055
|
-
|
24056
23108
|
// 4.1.5. Serializing a Decimal
|
24057
23109
|
//
|
24058
23110
|
// Given a decimal number as input_decimal, return an ASCII string
|
@@ -24098,8 +23150,6 @@ function serializeDecimal(value) {
|
|
24098
23150
|
|
24099
23151
|
const STRING = 'String';
|
24100
23152
|
|
24101
|
-
const STRING_REGEX = /[\x00-\x1f\x7f]+/; // eslint-disable-line no-control-regex
|
24102
|
-
|
24103
23153
|
// 4.1.6. Serializing a String
|
24104
23154
|
//
|
24105
23155
|
// Given a String as input_string, return an ASCII string suitable for
|
@@ -24135,8 +23185,6 @@ function symbolToStr(symbol) {
|
|
24135
23185
|
return symbol.description || symbol.toString().slice(7, -1);
|
24136
23186
|
}
|
24137
23187
|
|
24138
|
-
const TOKEN = 'Token';
|
24139
|
-
|
24140
23188
|
function serializeToken(token) {
|
24141
23189
|
const value = symbolToStr(token);
|
24142
23190
|
if (/^([a-zA-Z*])([!#$%&'*+\-.^_`|~\w:/]*)$/.test(value) === false) {
|
@@ -24204,8 +23252,6 @@ function serializeBareItem(value) {
|
|
24204
23252
|
}
|
24205
23253
|
}
|
24206
23254
|
|
24207
|
-
const KEY = 'Key';
|
24208
|
-
|
24209
23255
|
// 4.1.1.3. Serializing a Key
|
24210
23256
|
//
|
24211
23257
|
// Given a key as input_key, return an ASCII string suitable for use in
|
@@ -24447,6 +23493,36 @@ function urlToRelativePath(url, base) {
|
|
24447
23493
|
return toPath.join('/');
|
24448
23494
|
}
|
24449
23495
|
|
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
|
+
|
24450
23526
|
const toRounded = value => Math.round(value);
|
24451
23527
|
const toUrlSafe = (value, options) => {
|
24452
23528
|
if (options != null && options.baseUrl) {
|
@@ -24672,36 +23748,6 @@ function appendCmcdQuery(url, cmcd, options) {
|
|
24672
23748
|
return `${url}${separator}${query}`;
|
24673
23749
|
}
|
24674
23750
|
|
24675
|
-
/**
|
24676
|
-
* Generate a random v4 UUID
|
24677
|
-
*
|
24678
|
-
* @returns A random v4 UUID
|
24679
|
-
*
|
24680
|
-
* @group Utils
|
24681
|
-
*
|
24682
|
-
* @beta
|
24683
|
-
*/
|
24684
|
-
function uuid() {
|
24685
|
-
try {
|
24686
|
-
return crypto.randomUUID();
|
24687
|
-
} catch (error) {
|
24688
|
-
try {
|
24689
|
-
const url = URL.createObjectURL(new Blob());
|
24690
|
-
const uuid = url.toString();
|
24691
|
-
URL.revokeObjectURL(url);
|
24692
|
-
return uuid.slice(uuid.lastIndexOf('/') + 1);
|
24693
|
-
} catch (error) {
|
24694
|
-
let dt = new Date().getTime();
|
24695
|
-
const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
24696
|
-
const r = (dt + Math.random() * 16) % 16 | 0;
|
24697
|
-
dt = Math.floor(dt / 16);
|
24698
|
-
return (c == 'x' ? r : r & 0x3 | 0x8).toString(16);
|
24699
|
-
});
|
24700
|
-
return uuid;
|
24701
|
-
}
|
24702
|
-
}
|
24703
|
-
}
|
24704
|
-
|
24705
23751
|
/**
|
24706
23752
|
* Controller to deal with Common Media Client Data (CMCD)
|
24707
23753
|
* @see https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf
|
@@ -24745,7 +23791,7 @@ class CMCDController {
|
|
24745
23791
|
su: !this.initialized
|
24746
23792
|
});
|
24747
23793
|
} catch (error) {
|
24748
|
-
|
23794
|
+
logger.warn('Could not generate manifest CMCD data.', error);
|
24749
23795
|
}
|
24750
23796
|
};
|
24751
23797
|
/**
|
@@ -24765,15 +23811,9 @@ class CMCDController {
|
|
24765
23811
|
data.tb = this.getTopBandwidth(ot) / 1000;
|
24766
23812
|
data.bl = this.getBufferLength(ot);
|
24767
23813
|
}
|
24768
|
-
const next = this.getNextFrag(fragment);
|
24769
|
-
if (next) {
|
24770
|
-
if (next.url && next.url !== fragment.url) {
|
24771
|
-
data.nor = next.url;
|
24772
|
-
}
|
24773
|
-
}
|
24774
23814
|
this.apply(context, data);
|
24775
23815
|
} catch (error) {
|
24776
|
-
|
23816
|
+
logger.warn('Could not generate segment CMCD data.', error);
|
24777
23817
|
}
|
24778
23818
|
};
|
24779
23819
|
this.hls = hls;
|
@@ -24863,7 +23903,7 @@ class CMCDController {
|
|
24863
23903
|
data.su = this.buffering;
|
24864
23904
|
}
|
24865
23905
|
|
24866
|
-
// TODO: Implement rtp, nrr, dl
|
23906
|
+
// TODO: Implement rtp, nrr, nor, dl
|
24867
23907
|
|
24868
23908
|
const {
|
24869
23909
|
includeKeys
|
@@ -24874,28 +23914,15 @@ class CMCDController {
|
|
24874
23914
|
return acc;
|
24875
23915
|
}, {});
|
24876
23916
|
}
|
24877
|
-
const options = {
|
24878
|
-
baseUrl: context.url
|
24879
|
-
};
|
24880
23917
|
if (this.useHeaders) {
|
24881
23918
|
if (!context.headers) {
|
24882
23919
|
context.headers = {};
|
24883
23920
|
}
|
24884
|
-
appendCmcdHeaders(context.headers, data
|
23921
|
+
appendCmcdHeaders(context.headers, data);
|
24885
23922
|
} else {
|
24886
|
-
context.url = appendCmcdQuery(context.url, data
|
23923
|
+
context.url = appendCmcdQuery(context.url, data);
|
24887
23924
|
}
|
24888
23925
|
}
|
24889
|
-
getNextFrag(fragment) {
|
24890
|
-
var _this$hls$levels$frag;
|
24891
|
-
const levelDetails = (_this$hls$levels$frag = this.hls.levels[fragment.level]) == null ? void 0 : _this$hls$levels$frag.details;
|
24892
|
-
if (levelDetails) {
|
24893
|
-
const index = fragment.sn - levelDetails.startSN;
|
24894
|
-
return levelDetails.fragments[index + 1];
|
24895
|
-
}
|
24896
|
-
return undefined;
|
24897
|
-
}
|
24898
|
-
|
24899
23926
|
/**
|
24900
23927
|
* The CMCD object type.
|
24901
23928
|
*/
|
@@ -25024,10 +24051,10 @@ class CMCDController {
|
|
25024
24051
|
}
|
25025
24052
|
|
25026
24053
|
const PATHWAY_PENALTY_DURATION_MS = 300000;
|
25027
|
-
class ContentSteeringController
|
24054
|
+
class ContentSteeringController {
|
25028
24055
|
constructor(hls) {
|
25029
|
-
super('content-steering', hls.logger);
|
25030
24056
|
this.hls = void 0;
|
24057
|
+
this.log = void 0;
|
25031
24058
|
this.loader = null;
|
25032
24059
|
this.uri = null;
|
25033
24060
|
this.pathwayId = '.';
|
@@ -25042,6 +24069,7 @@ class ContentSteeringController extends Logger {
|
|
25042
24069
|
this.subtitleTracks = null;
|
25043
24070
|
this.penalizedPathways = {};
|
25044
24071
|
this.hls = hls;
|
24072
|
+
this.log = logger.log.bind(logger, `[content-steering]:`);
|
25045
24073
|
this.registerListeners();
|
25046
24074
|
}
|
25047
24075
|
registerListeners() {
|
@@ -25165,7 +24193,7 @@ class ContentSteeringController extends Logger {
|
|
25165
24193
|
errorAction.resolved = this.pathwayId !== errorPathway;
|
25166
24194
|
}
|
25167
24195
|
if (!errorAction.resolved) {
|
25168
|
-
|
24196
|
+
logger.warn(`Could not resolve ${data.details} ("${data.error.message}") with content-steering for Pathway: ${errorPathway} levels: ${levels ? levels.length : levels} priorities: ${JSON.stringify(pathwayPriority)} penalized: ${JSON.stringify(this.penalizedPathways)}`);
|
25169
24197
|
}
|
25170
24198
|
}
|
25171
24199
|
}
|
@@ -25336,7 +24364,7 @@ class ContentSteeringController extends Logger {
|
|
25336
24364
|
onSuccess: (response, stats, context, networkDetails) => {
|
25337
24365
|
this.log(`Loaded steering manifest: "${url}"`);
|
25338
24366
|
const steeringData = response.data;
|
25339
|
-
if (
|
24367
|
+
if (steeringData.VERSION !== 1) {
|
25340
24368
|
this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
|
25341
24369
|
return;
|
25342
24370
|
}
|
@@ -26306,7 +25334,7 @@ function timelineConfig() {
|
|
26306
25334
|
/**
|
26307
25335
|
* @ignore
|
26308
25336
|
*/
|
26309
|
-
function mergeConfig(defaultConfig, userConfig
|
25337
|
+
function mergeConfig(defaultConfig, userConfig) {
|
26310
25338
|
if ((userConfig.liveSyncDurationCount || userConfig.liveMaxLatencyDurationCount) && (userConfig.liveSyncDuration || userConfig.liveMaxLatencyDuration)) {
|
26311
25339
|
throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration");
|
26312
25340
|
}
|
@@ -26376,7 +25404,7 @@ function deepCpy(obj) {
|
|
26376
25404
|
/**
|
26377
25405
|
* @ignore
|
26378
25406
|
*/
|
26379
|
-
function enableStreamingMode(config
|
25407
|
+
function enableStreamingMode(config) {
|
26380
25408
|
const currentLoader = config.loader;
|
26381
25409
|
if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
|
26382
25410
|
// If a developer has configured their own loader, respect that choice
|
@@ -26393,9 +25421,10 @@ function enableStreamingMode(config, logger) {
|
|
26393
25421
|
}
|
26394
25422
|
}
|
26395
25423
|
|
25424
|
+
let chromeOrFirefox;
|
26396
25425
|
class LevelController extends BasePlaylistController {
|
26397
25426
|
constructor(hls, contentSteeringController) {
|
26398
|
-
super(hls, 'level-controller');
|
25427
|
+
super(hls, '[level-controller]');
|
26399
25428
|
this._levels = [];
|
26400
25429
|
this._firstLevel = -1;
|
26401
25430
|
this._maxAutoLevel = -1;
|
@@ -26466,15 +25495,23 @@ class LevelController extends BasePlaylistController {
|
|
26466
25495
|
let videoCodecFound = false;
|
26467
25496
|
let audioCodecFound = false;
|
26468
25497
|
data.levels.forEach(levelParsed => {
|
26469
|
-
var _videoCodec;
|
25498
|
+
var _audioCodec, _videoCodec;
|
26470
25499
|
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
|
26471
25503
|
let {
|
26472
25504
|
audioCodec,
|
26473
25505
|
videoCodec
|
26474
25506
|
} = 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
|
+
}
|
26475
25513
|
if (audioCodec) {
|
26476
|
-
|
26477
|
-
levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource) || undefined;
|
25514
|
+
levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource);
|
26478
25515
|
}
|
26479
25516
|
if (((_videoCodec = videoCodec) == null ? void 0 : _videoCodec.indexOf('avc1')) === 0) {
|
26480
25517
|
videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
|
@@ -26816,12 +25853,7 @@ class LevelController extends BasePlaylistController {
|
|
26816
25853
|
if (curLevel.fragmentError === 0) {
|
26817
25854
|
curLevel.loadError = 0;
|
26818
25855
|
}
|
26819
|
-
|
26820
|
-
let previousDetails = curLevel.details;
|
26821
|
-
if (previousDetails === data.details && previousDetails.advanced) {
|
26822
|
-
previousDetails = undefined;
|
26823
|
-
}
|
26824
|
-
this.playlistLoaded(level, data, previousDetails);
|
25856
|
+
this.playlistLoaded(level, data, curLevel.details);
|
26825
25857
|
} else if ((_data$deliveryDirecti2 = data.deliveryDirectives) != null && _data$deliveryDirecti2.skip) {
|
26826
25858
|
// received a delta playlist update that cannot be merged
|
26827
25859
|
details.deltaUpdateFailed = true;
|
@@ -27065,8 +26097,6 @@ class KeyLoader {
|
|
27065
26097
|
}
|
27066
26098
|
return this.loadKeyEME(keyInfo, frag);
|
27067
26099
|
case 'AES-128':
|
27068
|
-
case 'AES-256':
|
27069
|
-
case 'AES-256-CTR':
|
27070
26100
|
return this.loadKeyHTTP(keyInfo, frag);
|
27071
26101
|
default:
|
27072
26102
|
return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`)));
|
@@ -27204,9 +26234,8 @@ const STALL_MINIMUM_DURATION_MS = 250;
|
|
27204
26234
|
const MAX_START_GAP_JUMP = 2.0;
|
27205
26235
|
const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
|
27206
26236
|
const SKIP_BUFFER_RANGE_START = 0.05;
|
27207
|
-
class GapController
|
26237
|
+
class GapController {
|
27208
26238
|
constructor(config, media, fragmentTracker, hls) {
|
27209
|
-
super('gap-controller', hls.logger);
|
27210
26239
|
this.config = void 0;
|
27211
26240
|
this.media = null;
|
27212
26241
|
this.fragmentTracker = void 0;
|
@@ -27216,7 +26245,6 @@ class GapController extends Logger {
|
|
27216
26245
|
this.stalled = null;
|
27217
26246
|
this.moved = false;
|
27218
26247
|
this.seeking = false;
|
27219
|
-
this.ended = 0;
|
27220
26248
|
this.config = config;
|
27221
26249
|
this.media = media;
|
27222
26250
|
this.fragmentTracker = fragmentTracker;
|
@@ -27234,7 +26262,7 @@ class GapController extends Logger {
|
|
27234
26262
|
*
|
27235
26263
|
* @param lastCurrentTime - Previously read playhead position
|
27236
26264
|
*/
|
27237
|
-
poll(lastCurrentTime, activeFrag
|
26265
|
+
poll(lastCurrentTime, activeFrag) {
|
27238
26266
|
const {
|
27239
26267
|
config,
|
27240
26268
|
media,
|
@@ -27253,7 +26281,6 @@ class GapController extends Logger {
|
|
27253
26281
|
|
27254
26282
|
// The playhead is moving, no-op
|
27255
26283
|
if (currentTime !== lastCurrentTime) {
|
27256
|
-
this.ended = 0;
|
27257
26284
|
this.moved = true;
|
27258
26285
|
if (!seeking) {
|
27259
26286
|
this.nudgeRetry = 0;
|
@@ -27262,7 +26289,7 @@ class GapController extends Logger {
|
|
27262
26289
|
// The playhead is now moving, but was previously stalled
|
27263
26290
|
if (this.stallReported) {
|
27264
26291
|
const _stalledDuration = self.performance.now() - stalled;
|
27265
|
-
|
26292
|
+
logger.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
|
27266
26293
|
this.stallReported = false;
|
27267
26294
|
}
|
27268
26295
|
this.stalled = null;
|
@@ -27298,6 +26325,7 @@ class GapController extends Logger {
|
|
27298
26325
|
// Skip start gaps if we haven't played, but the last poll detected the start of a stall
|
27299
26326
|
// The addition poll gives the browser a chance to jump the gap for us
|
27300
26327
|
if (!this.moved && this.stalled !== null) {
|
26328
|
+
var _level$details;
|
27301
26329
|
// There is no playable buffer (seeked, waiting for buffer)
|
27302
26330
|
const isBuffered = bufferInfo.len > 0;
|
27303
26331
|
if (!isBuffered && !nextStart) {
|
@@ -27309,8 +26337,9 @@ class GapController extends Logger {
|
|
27309
26337
|
// When joining a live stream with audio tracks, account for live playlist window sliding by allowing
|
27310
26338
|
// a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
|
27311
26339
|
// that begins over 1 target duration after the video start position.
|
27312
|
-
const
|
27313
|
-
const
|
26340
|
+
const level = this.hls.levels ? this.hls.levels[this.hls.currentLevel] : null;
|
26341
|
+
const isLive = level == null ? void 0 : (_level$details = level.details) == null ? void 0 : _level$details.live;
|
26342
|
+
const maxStartGapJump = isLive ? level.details.targetduration * 2 : MAX_START_GAP_JUMP;
|
27314
26343
|
const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
|
27315
26344
|
if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
|
27316
26345
|
if (!media.paused) {
|
@@ -27328,17 +26357,6 @@ class GapController extends Logger {
|
|
27328
26357
|
}
|
27329
26358
|
const stalledDuration = tnow - stalled;
|
27330
26359
|
if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) {
|
27331
|
-
// Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
|
27332
|
-
if (state === State.ENDED && !(levelDetails && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? void 0 : levelDetails.edge) || 0)) < 1) {
|
27333
|
-
if (stalledDuration < 1000 || this.ended) {
|
27334
|
-
return;
|
27335
|
-
}
|
27336
|
-
this.ended = currentTime;
|
27337
|
-
this.hls.trigger(Events.MEDIA_ENDED, {
|
27338
|
-
stalled: true
|
27339
|
-
});
|
27340
|
-
return;
|
27341
|
-
}
|
27342
26360
|
// Report stalling after trying to fix
|
27343
26361
|
this._reportStall(bufferInfo);
|
27344
26362
|
if (!this.media) {
|
@@ -27382,7 +26400,7 @@ class GapController extends Logger {
|
|
27382
26400
|
// needs to cross some sort of threshold covering all source-buffers content
|
27383
26401
|
// to start playing properly.
|
27384
26402
|
if ((bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
|
27385
|
-
|
26403
|
+
logger.warn('Trying to nudge playhead over buffer-hole');
|
27386
26404
|
// Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
|
27387
26405
|
// We only try to jump the hole if it's under the configured size
|
27388
26406
|
// Reset stalled so to rearm watchdog timer
|
@@ -27406,7 +26424,7 @@ class GapController extends Logger {
|
|
27406
26424
|
// Report stalled error once
|
27407
26425
|
this.stallReported = true;
|
27408
26426
|
const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${JSON.stringify(bufferInfo)})`);
|
27409
|
-
|
26427
|
+
logger.warn(error.message);
|
27410
26428
|
hls.trigger(Events.ERROR, {
|
27411
26429
|
type: ErrorTypes.MEDIA_ERROR,
|
27412
26430
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
@@ -27474,7 +26492,7 @@ class GapController extends Logger {
|
|
27474
26492
|
}
|
27475
26493
|
}
|
27476
26494
|
const targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
|
27477
|
-
|
26495
|
+
logger.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
|
27478
26496
|
this.moved = true;
|
27479
26497
|
this.stalled = null;
|
27480
26498
|
media.currentTime = targetTime;
|
@@ -27515,7 +26533,7 @@ class GapController extends Logger {
|
|
27515
26533
|
const targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset;
|
27516
26534
|
// playback stalled in buffered area ... let's nudge currentTime to try to overcome this
|
27517
26535
|
const error = new Error(`Nudging 'currentTime' from ${currentTime} to ${targetTime}`);
|
27518
|
-
|
26536
|
+
logger.warn(error.message);
|
27519
26537
|
media.currentTime = targetTime;
|
27520
26538
|
hls.trigger(Events.ERROR, {
|
27521
26539
|
type: ErrorTypes.MEDIA_ERROR,
|
@@ -27525,7 +26543,7 @@ class GapController extends Logger {
|
|
27525
26543
|
});
|
27526
26544
|
} else {
|
27527
26545
|
const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`);
|
27528
|
-
|
26546
|
+
logger.error(error.message);
|
27529
26547
|
hls.trigger(Events.ERROR, {
|
27530
26548
|
type: ErrorTypes.MEDIA_ERROR,
|
27531
26549
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
@@ -27540,7 +26558,7 @@ const TICK_INTERVAL = 100; // how often to tick in ms
|
|
27540
26558
|
|
27541
26559
|
class StreamController extends BaseStreamController {
|
27542
26560
|
constructor(hls, fragmentTracker, keyLoader) {
|
27543
|
-
super(hls, fragmentTracker, keyLoader, 'stream-controller', PlaylistLevelType.MAIN);
|
26561
|
+
super(hls, fragmentTracker, keyLoader, '[stream-controller]', PlaylistLevelType.MAIN);
|
27544
26562
|
this.audioCodecSwap = false;
|
27545
26563
|
this.gapController = null;
|
27546
26564
|
this.level = -1;
|
@@ -27548,43 +26566,27 @@ class StreamController extends BaseStreamController {
|
|
27548
26566
|
this.altAudio = false;
|
27549
26567
|
this.audioOnly = false;
|
27550
26568
|
this.fragPlaying = null;
|
26569
|
+
this.onvplaying = null;
|
26570
|
+
this.onvseeked = null;
|
27551
26571
|
this.fragLastKbps = 0;
|
27552
26572
|
this.couldBacktrack = false;
|
27553
26573
|
this.backtrackFragment = null;
|
27554
26574
|
this.audioCodecSwitch = false;
|
27555
26575
|
this.videoBuffer = null;
|
27556
|
-
this.
|
27557
|
-
// tick to speed up FRAG_CHANGED triggering
|
27558
|
-
this.tick();
|
27559
|
-
};
|
27560
|
-
this.onMediaSeeked = () => {
|
27561
|
-
const media = this.media;
|
27562
|
-
const currentTime = media ? media.currentTime : null;
|
27563
|
-
if (isFiniteNumber(currentTime)) {
|
27564
|
-
this.log(`Media seeked to ${currentTime.toFixed(3)}`);
|
27565
|
-
}
|
27566
|
-
|
27567
|
-
// If seeked was issued before buffer was appended do not tick immediately
|
27568
|
-
const bufferInfo = this.getMainFwdBufferInfo();
|
27569
|
-
if (bufferInfo === null || bufferInfo.len === 0) {
|
27570
|
-
this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
|
27571
|
-
return;
|
27572
|
-
}
|
27573
|
-
|
27574
|
-
// tick to speed up FRAG_CHANGED triggering
|
27575
|
-
this.tick();
|
27576
|
-
};
|
27577
|
-
this.registerListeners();
|
26576
|
+
this._registerListeners();
|
27578
26577
|
}
|
27579
|
-
|
27580
|
-
super.registerListeners();
|
26578
|
+
_registerListeners() {
|
27581
26579
|
const {
|
27582
26580
|
hls
|
27583
26581
|
} = 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);
|
27584
26585
|
hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
27585
26586
|
hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
|
27586
26587
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
27587
26588
|
hls.on(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
|
26589
|
+
hls.on(Events.ERROR, this.onError, this);
|
27588
26590
|
hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
27589
26591
|
hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
27590
26592
|
hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -27592,14 +26594,17 @@ class StreamController extends BaseStreamController {
|
|
27592
26594
|
hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
|
27593
26595
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
27594
26596
|
}
|
27595
|
-
|
27596
|
-
super.unregisterListeners();
|
26597
|
+
_unregisterListeners() {
|
27597
26598
|
const {
|
27598
26599
|
hls
|
27599
26600
|
} = 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);
|
27600
26604
|
hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
27601
26605
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
27602
26606
|
hls.off(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
|
26607
|
+
hls.off(Events.ERROR, this.onError, this);
|
27603
26608
|
hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
27604
26609
|
hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
27605
26610
|
hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -27608,9 +26613,7 @@ class StreamController extends BaseStreamController {
|
|
27608
26613
|
hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
27609
26614
|
}
|
27610
26615
|
onHandlerDestroying() {
|
27611
|
-
|
27612
|
-
this.onMediaPlaying = this.onMediaSeeked = null;
|
27613
|
-
this.unregisterListeners();
|
26616
|
+
this._unregisterListeners();
|
27614
26617
|
super.onHandlerDestroying();
|
27615
26618
|
}
|
27616
26619
|
startLoad(startPosition) {
|
@@ -27708,9 +26711,6 @@ class StreamController extends BaseStreamController {
|
|
27708
26711
|
this.checkFragmentChanged();
|
27709
26712
|
}
|
27710
26713
|
doTickIdle() {
|
27711
|
-
if (!this.buffering) {
|
27712
|
-
return;
|
27713
|
-
}
|
27714
26714
|
const {
|
27715
26715
|
hls,
|
27716
26716
|
levelLastLoaded,
|
@@ -27938,17 +26938,20 @@ class StreamController extends BaseStreamController {
|
|
27938
26938
|
onMediaAttached(event, data) {
|
27939
26939
|
super.onMediaAttached(event, data);
|
27940
26940
|
const media = data.media;
|
27941
|
-
|
27942
|
-
|
26941
|
+
this.onvplaying = this.onMediaPlaying.bind(this);
|
26942
|
+
this.onvseeked = this.onMediaSeeked.bind(this);
|
26943
|
+
media.addEventListener('playing', this.onvplaying);
|
26944
|
+
media.addEventListener('seeked', this.onvseeked);
|
27943
26945
|
this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
|
27944
26946
|
}
|
27945
26947
|
onMediaDetaching() {
|
27946
26948
|
const {
|
27947
26949
|
media
|
27948
26950
|
} = this;
|
27949
|
-
if (media) {
|
27950
|
-
media.removeEventListener('playing', this.
|
27951
|
-
media.removeEventListener('seeked', this.
|
26951
|
+
if (media && this.onvplaying && this.onvseeked) {
|
26952
|
+
media.removeEventListener('playing', this.onvplaying);
|
26953
|
+
media.removeEventListener('seeked', this.onvseeked);
|
26954
|
+
this.onvplaying = this.onvseeked = null;
|
27952
26955
|
this.videoBuffer = null;
|
27953
26956
|
}
|
27954
26957
|
this.fragPlaying = null;
|
@@ -27958,6 +26961,27 @@ class StreamController extends BaseStreamController {
|
|
27958
26961
|
}
|
27959
26962
|
super.onMediaDetaching();
|
27960
26963
|
}
|
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
|
+
}
|
27961
26985
|
onManifestLoading() {
|
27962
26986
|
// reset buffer on manifest loading
|
27963
26987
|
this.log('Trigger BUFFER_RESET');
|
@@ -28249,10 +27273,8 @@ class StreamController extends BaseStreamController {
|
|
28249
27273
|
}
|
28250
27274
|
if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) {
|
28251
27275
|
// Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
|
28252
|
-
const
|
28253
|
-
|
28254
|
-
const levelDetails = this.getLevelDetails();
|
28255
|
-
gapController.poll(this.lastCurrentTime, activeFrag, levelDetails, state);
|
27276
|
+
const activeFrag = this.state !== State.IDLE ? this.fragCurrent : null;
|
27277
|
+
gapController.poll(this.lastCurrentTime, activeFrag);
|
28256
27278
|
}
|
28257
27279
|
this.lastCurrentTime = media.currentTime;
|
28258
27280
|
}
|
@@ -28585,17 +27607,6 @@ class StreamController extends BaseStreamController {
|
|
28585
27607
|
getMainFwdBufferInfo() {
|
28586
27608
|
return this.getFwdBufferInfo(this.mediaBuffer ? this.mediaBuffer : this.media, PlaylistLevelType.MAIN);
|
28587
27609
|
}
|
28588
|
-
get maxBufferLength() {
|
28589
|
-
const {
|
28590
|
-
levels,
|
28591
|
-
level
|
28592
|
-
} = this;
|
28593
|
-
const levelInfo = levels == null ? void 0 : levels[level];
|
28594
|
-
if (!levelInfo) {
|
28595
|
-
return this.config.maxBufferLength;
|
28596
|
-
}
|
28597
|
-
return this.getMaxBufferLength(levelInfo.maxBitrate);
|
28598
|
-
}
|
28599
27610
|
backtrack(frag) {
|
28600
27611
|
this.couldBacktrack = true;
|
28601
27612
|
// Causes findFragments to backtrack through fragments to find the keyframe
|
@@ -28701,7 +27712,7 @@ class Hls {
|
|
28701
27712
|
* Get the video-dev/hls.js package version.
|
28702
27713
|
*/
|
28703
27714
|
static get version() {
|
28704
|
-
return "1.5.7
|
27715
|
+
return "1.5.7";
|
28705
27716
|
}
|
28706
27717
|
|
28707
27718
|
/**
|
@@ -28764,12 +27775,9 @@ class Hls {
|
|
28764
27775
|
* The configuration object provided on player instantiation.
|
28765
27776
|
*/
|
28766
27777
|
this.userConfig = void 0;
|
28767
|
-
/**
|
28768
|
-
* The logger functions used by this player instance, configured on player instantiation.
|
28769
|
-
*/
|
28770
|
-
this.logger = void 0;
|
28771
27778
|
this.coreComponents = void 0;
|
28772
27779
|
this.networkControllers = void 0;
|
27780
|
+
this.started = false;
|
28773
27781
|
this._emitter = new EventEmitter();
|
28774
27782
|
this._autoLevelCapping = -1;
|
28775
27783
|
this._maxHdcpLevel = null;
|
@@ -28786,11 +27794,11 @@ class Hls {
|
|
28786
27794
|
this._media = null;
|
28787
27795
|
this.url = null;
|
28788
27796
|
this.triggeringException = void 0;
|
28789
|
-
|
28790
|
-
const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig
|
27797
|
+
enableLogs(userConfig.debug || false, 'Hls instance');
|
27798
|
+
const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig);
|
28791
27799
|
this.userConfig = userConfig;
|
28792
27800
|
if (config.progressive) {
|
28793
|
-
enableStreamingMode(config
|
27801
|
+
enableStreamingMode(config);
|
28794
27802
|
}
|
28795
27803
|
|
28796
27804
|
// core controllers and network loaders
|
@@ -28803,9 +27811,7 @@ class Hls {
|
|
28803
27811
|
} = config;
|
28804
27812
|
const errorController = new ConfigErrorController(this);
|
28805
27813
|
const abrController = this.abrController = new ConfigAbrController(this);
|
28806
|
-
|
28807
|
-
const fragmentTracker = new FragmentTracker(this);
|
28808
|
-
const bufferController = this.bufferController = new ConfigBufferController(this, fragmentTracker);
|
27814
|
+
const bufferController = this.bufferController = new ConfigBufferController(this);
|
28809
27815
|
const capLevelController = this.capLevelController = new ConfigCapLevelController(this);
|
28810
27816
|
const fpsController = new ConfigFpsController(this);
|
28811
27817
|
const playListLoader = new PlaylistLoader(this);
|
@@ -28814,6 +27820,8 @@ class Hls {
|
|
28814
27820
|
// ConentSteeringController is defined before LevelController to receive Multivariant Playlist events first
|
28815
27821
|
const contentSteering = ConfigContentSteeringController ? new ConfigContentSteeringController(this) : null;
|
28816
27822
|
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);
|
28817
27825
|
const keyLoader = new KeyLoader(this.config);
|
28818
27826
|
const streamController = this.streamController = new StreamController(this, fragmentTracker, keyLoader);
|
28819
27827
|
|
@@ -28889,7 +27897,7 @@ class Hls {
|
|
28889
27897
|
try {
|
28890
27898
|
return this.emit(event, event, eventObject);
|
28891
27899
|
} catch (error) {
|
28892
|
-
|
27900
|
+
logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
|
28893
27901
|
// Prevent recursion in error event handlers that throw #5497
|
28894
27902
|
if (!this.triggeringException) {
|
28895
27903
|
this.triggeringException = true;
|
@@ -28915,7 +27923,7 @@ class Hls {
|
|
28915
27923
|
* Dispose of the instance
|
28916
27924
|
*/
|
28917
27925
|
destroy() {
|
28918
|
-
|
27926
|
+
logger.log('destroy');
|
28919
27927
|
this.trigger(Events.DESTROYING, undefined);
|
28920
27928
|
this.detachMedia();
|
28921
27929
|
this.removeAllListeners();
|
@@ -28936,7 +27944,7 @@ class Hls {
|
|
28936
27944
|
* Attaches Hls.js to a media element
|
28937
27945
|
*/
|
28938
27946
|
attachMedia(media) {
|
28939
|
-
|
27947
|
+
logger.log('attachMedia');
|
28940
27948
|
this._media = media;
|
28941
27949
|
this.trigger(Events.MEDIA_ATTACHING, {
|
28942
27950
|
media: media
|
@@ -28947,7 +27955,7 @@ class Hls {
|
|
28947
27955
|
* Detach Hls.js from the media
|
28948
27956
|
*/
|
28949
27957
|
detachMedia() {
|
28950
|
-
|
27958
|
+
logger.log('detachMedia');
|
28951
27959
|
this.trigger(Events.MEDIA_DETACHING, undefined);
|
28952
27960
|
this._media = null;
|
28953
27961
|
}
|
@@ -28964,7 +27972,7 @@ class Hls {
|
|
28964
27972
|
});
|
28965
27973
|
this._autoLevelCapping = -1;
|
28966
27974
|
this._maxHdcpLevel = null;
|
28967
|
-
|
27975
|
+
logger.log(`loadSource:${loadingSource}`);
|
28968
27976
|
if (media && loadedSource && (loadedSource !== loadingSource || this.bufferController.hasSourceTypes())) {
|
28969
27977
|
this.detachMedia();
|
28970
27978
|
this.attachMedia(media);
|
@@ -28983,7 +27991,8 @@ class Hls {
|
|
28983
27991
|
* Defaults to -1 (None: starts from earliest point)
|
28984
27992
|
*/
|
28985
27993
|
startLoad(startPosition = -1) {
|
28986
|
-
|
27994
|
+
logger.log(`startLoad(${startPosition})`);
|
27995
|
+
this.started = true;
|
28987
27996
|
this.networkControllers.forEach(controller => {
|
28988
27997
|
controller.startLoad(startPosition);
|
28989
27998
|
});
|
@@ -28993,31 +28002,34 @@ class Hls {
|
|
28993
28002
|
* Stop loading of any stream data.
|
28994
28003
|
*/
|
28995
28004
|
stopLoad() {
|
28996
|
-
|
28005
|
+
logger.log('stopLoad');
|
28006
|
+
this.started = false;
|
28997
28007
|
this.networkControllers.forEach(controller => {
|
28998
28008
|
controller.stopLoad();
|
28999
28009
|
});
|
29000
28010
|
}
|
29001
28011
|
|
29002
28012
|
/**
|
29003
|
-
* Resumes stream controller segment loading
|
28013
|
+
* Resumes stream controller segment loading if previously started.
|
29004
28014
|
*/
|
29005
28015
|
resumeBuffering() {
|
29006
|
-
this.
|
29007
|
-
|
29008
|
-
controller
|
29009
|
-
|
29010
|
-
|
28016
|
+
if (this.started) {
|
28017
|
+
this.networkControllers.forEach(controller => {
|
28018
|
+
if ('fragmentLoader' in controller) {
|
28019
|
+
controller.startLoad(-1);
|
28020
|
+
}
|
28021
|
+
});
|
28022
|
+
}
|
29011
28023
|
}
|
29012
28024
|
|
29013
28025
|
/**
|
29014
|
-
*
|
28026
|
+
* Stops stream controller segment loading without changing 'started' state like stopLoad().
|
29015
28027
|
* This allows for media buffering to be paused without interupting playlist loading.
|
29016
28028
|
*/
|
29017
28029
|
pauseBuffering() {
|
29018
28030
|
this.networkControllers.forEach(controller => {
|
29019
|
-
if (controller
|
29020
|
-
controller.
|
28031
|
+
if ('fragmentLoader' in controller) {
|
28032
|
+
controller.stopLoad();
|
29021
28033
|
}
|
29022
28034
|
});
|
29023
28035
|
}
|
@@ -29026,7 +28038,7 @@ class Hls {
|
|
29026
28038
|
* Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
|
29027
28039
|
*/
|
29028
28040
|
swapAudioCodec() {
|
29029
|
-
|
28041
|
+
logger.log('swapAudioCodec');
|
29030
28042
|
this.streamController.swapAudioCodec();
|
29031
28043
|
}
|
29032
28044
|
|
@@ -29037,7 +28049,7 @@ class Hls {
|
|
29037
28049
|
* Automatic recovery of media-errors by this process is configurable.
|
29038
28050
|
*/
|
29039
28051
|
recoverMediaError() {
|
29040
|
-
|
28052
|
+
logger.log('recoverMediaError');
|
29041
28053
|
const media = this._media;
|
29042
28054
|
this.detachMedia();
|
29043
28055
|
if (media) {
|
@@ -29067,7 +28079,7 @@ class Hls {
|
|
29067
28079
|
* 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.
|
29068
28080
|
*/
|
29069
28081
|
set currentLevel(newLevel) {
|
29070
|
-
|
28082
|
+
logger.log(`set currentLevel:${newLevel}`);
|
29071
28083
|
this.levelController.manualLevel = newLevel;
|
29072
28084
|
this.streamController.immediateLevelSwitch();
|
29073
28085
|
}
|
@@ -29086,7 +28098,7 @@ class Hls {
|
|
29086
28098
|
* @param newLevel - Pass -1 for automatic level selection
|
29087
28099
|
*/
|
29088
28100
|
set nextLevel(newLevel) {
|
29089
|
-
|
28101
|
+
logger.log(`set nextLevel:${newLevel}`);
|
29090
28102
|
this.levelController.manualLevel = newLevel;
|
29091
28103
|
this.streamController.nextLevelSwitch();
|
29092
28104
|
}
|
@@ -29105,7 +28117,7 @@ class Hls {
|
|
29105
28117
|
* @param newLevel - Pass -1 for automatic level selection
|
29106
28118
|
*/
|
29107
28119
|
set loadLevel(newLevel) {
|
29108
|
-
|
28120
|
+
logger.log(`set loadLevel:${newLevel}`);
|
29109
28121
|
this.levelController.manualLevel = newLevel;
|
29110
28122
|
}
|
29111
28123
|
|
@@ -29136,7 +28148,7 @@ class Hls {
|
|
29136
28148
|
* Sets "first-level", see getter.
|
29137
28149
|
*/
|
29138
28150
|
set firstLevel(newLevel) {
|
29139
|
-
|
28151
|
+
logger.log(`set firstLevel:${newLevel}`);
|
29140
28152
|
this.levelController.firstLevel = newLevel;
|
29141
28153
|
}
|
29142
28154
|
|
@@ -29161,7 +28173,7 @@ class Hls {
|
|
29161
28173
|
* (determined from download of first segment)
|
29162
28174
|
*/
|
29163
28175
|
set startLevel(newLevel) {
|
29164
|
-
|
28176
|
+
logger.log(`set startLevel:${newLevel}`);
|
29165
28177
|
// if not in automatic start level detection, ensure startLevel is greater than minAutoLevel
|
29166
28178
|
if (newLevel !== -1) {
|
29167
28179
|
newLevel = Math.max(newLevel, this.minAutoLevel);
|
@@ -29236,7 +28248,7 @@ class Hls {
|
|
29236
28248
|
*/
|
29237
28249
|
set autoLevelCapping(newLevel) {
|
29238
28250
|
if (this._autoLevelCapping !== newLevel) {
|
29239
|
-
|
28251
|
+
logger.log(`set autoLevelCapping:${newLevel}`);
|
29240
28252
|
this._autoLevelCapping = newLevel;
|
29241
28253
|
this.levelController.checkMaxAutoUpdated();
|
29242
28254
|
}
|
@@ -29341,9 +28353,6 @@ class Hls {
|
|
29341
28353
|
get mainForwardBufferInfo() {
|
29342
28354
|
return this.streamController.getMainFwdBufferInfo();
|
29343
28355
|
}
|
29344
|
-
get maxBufferLength() {
|
29345
|
-
return this.streamController.maxBufferLength;
|
29346
|
-
}
|
29347
28356
|
|
29348
28357
|
/**
|
29349
28358
|
* Find and select the best matching audio track, making a level switch when a Group change is necessary.
|