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