hls.js 1.5.5-0.canary.9997 → 1.5.5
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 +20 -20
- 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.5"}`);
|
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.5-0.canary.9997"}`);
|
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
|
|
@@ -2728,12 +2686,12 @@ class LevelKey {
|
|
2728
2686
|
this.keyFormatVersions = formatversions;
|
2729
2687
|
this.iv = iv;
|
2730
2688
|
this.encrypted = method ? method !== 'NONE' : false;
|
2731
|
-
this.isCommonEncryption = this.encrypted &&
|
2689
|
+
this.isCommonEncryption = this.encrypted && method !== 'AES-128';
|
2732
2690
|
}
|
2733
2691
|
isSupported() {
|
2734
2692
|
// If it's Segment encryption or No encryption, just select that key system
|
2735
2693
|
if (this.method) {
|
2736
|
-
if (
|
2694
|
+
if (this.method === 'AES-128' || this.method === 'NONE') {
|
2737
2695
|
return true;
|
2738
2696
|
}
|
2739
2697
|
if (this.keyFormat === 'identity') {
|
@@ -2755,13 +2713,14 @@ class LevelKey {
|
|
2755
2713
|
if (!this.encrypted || !this.uri) {
|
2756
2714
|
return null;
|
2757
2715
|
}
|
2758
|
-
if (
|
2716
|
+
if (this.method === 'AES-128' && this.uri && !this.iv) {
|
2759
2717
|
if (typeof sn !== 'number') {
|
2760
2718
|
// We are fetching decryption data for a initialization segment
|
2761
|
-
// If the segment was encrypted with AES-128
|
2719
|
+
// If the segment was encrypted with AES-128
|
2762
2720
|
// It must have an IV defined. We cannot substitute the Segment Number in.
|
2763
|
-
|
2764
|
-
|
2721
|
+
if (this.method === 'AES-128' && !this.iv) {
|
2722
|
+
logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
|
2723
|
+
}
|
2765
2724
|
// Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
|
2766
2725
|
sn = 0;
|
2767
2726
|
}
|
@@ -3040,28 +2999,23 @@ function getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource =
|
|
3040
2999
|
if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) {
|
3041
3000
|
return CODEC_COMPATIBLE_NAMES[lowerCaseCodec];
|
3042
3001
|
}
|
3002
|
+
|
3003
|
+
// Idealy fLaC and Opus would be first (spec-compliant) but
|
3004
|
+
// some browsers will report that fLaC is supported then fail.
|
3005
|
+
// see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
|
3043
3006
|
const codecsToCheck = {
|
3044
|
-
// Idealy fLaC and Opus would be first (spec-compliant) but
|
3045
|
-
// some browsers will report that fLaC is supported then fail.
|
3046
|
-
// see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
|
3047
3007
|
flac: ['flac', 'fLaC', 'FLAC'],
|
3048
|
-
opus: ['opus', 'Opus']
|
3049
|
-
// Replace audio codec info if browser does not support mp4a.40.34,
|
3050
|
-
// and demuxer can fallback to 'audio/mpeg' or 'audio/mp4;codecs="mp3"'
|
3051
|
-
'mp4a.40.34': ['mp3']
|
3008
|
+
opus: ['opus', 'Opus']
|
3052
3009
|
}[lowerCaseCodec];
|
3053
3010
|
for (let i = 0; i < codecsToCheck.length; i++) {
|
3054
|
-
var _getMediaSource;
|
3055
3011
|
if (isCodecMediaSourceSupported(codecsToCheck[i], 'audio', preferManagedMediaSource)) {
|
3056
3012
|
CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
|
3057
3013
|
return codecsToCheck[i];
|
3058
|
-
} else if (codecsToCheck[i] === 'mp3' && (_getMediaSource = getMediaSource(preferManagedMediaSource)) != null && _getMediaSource.isTypeSupported('audio/mpeg')) {
|
3059
|
-
return '';
|
3060
3014
|
}
|
3061
3015
|
}
|
3062
3016
|
return lowerCaseCodec;
|
3063
3017
|
}
|
3064
|
-
const AUDIO_CODEC_REGEXP = /flac|opus
|
3018
|
+
const AUDIO_CODEC_REGEXP = /flac|opus/i;
|
3065
3019
|
function getCodecCompatibleName(codec, preferManagedMediaSource = true) {
|
3066
3020
|
return codec.replace(AUDIO_CODEC_REGEXP, m => getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource));
|
3067
3021
|
}
|
@@ -3084,16 +3038,6 @@ function convertAVC1ToAVCOTI(codec) {
|
|
3084
3038
|
}
|
3085
3039
|
return codec;
|
3086
3040
|
}
|
3087
|
-
function getM2TSSupportedAudioTypes(preferManagedMediaSource) {
|
3088
|
-
const MediaSource = getMediaSource(preferManagedMediaSource) || {
|
3089
|
-
isTypeSupported: () => false
|
3090
|
-
};
|
3091
|
-
return {
|
3092
|
-
mpeg: MediaSource.isTypeSupported('audio/mpeg'),
|
3093
|
-
mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
|
3094
|
-
ac3: MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
|
3095
|
-
};
|
3096
|
-
}
|
3097
3041
|
|
3098
3042
|
const MASTER_PLAYLIST_REGEX = /#EXT-X-STREAM-INF:([^\r\n]*)(?:[\r\n](?:#[^\r\n]*)?)*([^\r\n]+)|#EXT-X-(SESSION-DATA|SESSION-KEY|DEFINE|CONTENT-STEERING|START):([^\r\n]*)[\r\n]+/g;
|
3099
3043
|
const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g;
|
@@ -3943,10 +3887,10 @@ class PlaylistLoader {
|
|
3943
3887
|
const loaderContext = loader.context;
|
3944
3888
|
if (loaderContext && loaderContext.url === context.url && loaderContext.level === context.level) {
|
3945
3889
|
// same URL can't overlap
|
3946
|
-
|
3890
|
+
logger.trace('[playlist-loader]: playlist request ongoing');
|
3947
3891
|
return;
|
3948
3892
|
}
|
3949
|
-
|
3893
|
+
logger.log(`[playlist-loader]: aborting previous loader for type: ${context.type}`);
|
3950
3894
|
loader.abort();
|
3951
3895
|
}
|
3952
3896
|
|
@@ -4056,7 +4000,7 @@ class PlaylistLoader {
|
|
4056
4000
|
// alt audio rendition in which quality levels (main)
|
4057
4001
|
// contains both audio+video. but with mixed audio track not signaled
|
4058
4002
|
if (!embeddedAudioFound && levels[0].audioCodec && !levels[0].attrs.AUDIO) {
|
4059
|
-
|
4003
|
+
logger.log('[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one');
|
4060
4004
|
audioTracks.unshift({
|
4061
4005
|
type: 'main',
|
4062
4006
|
name: 'main',
|
@@ -4155,7 +4099,7 @@ class PlaylistLoader {
|
|
4155
4099
|
message += ` id: ${context.id} group-id: "${context.groupId}"`;
|
4156
4100
|
}
|
4157
4101
|
const error = new Error(message);
|
4158
|
-
|
4102
|
+
logger.warn(`[playlist-loader]: ${message}`);
|
4159
4103
|
let details = ErrorDetails.UNKNOWN;
|
4160
4104
|
let fatal = false;
|
4161
4105
|
const loader = this.getInternalLoader(context);
|
@@ -4760,47 +4704,7 @@ class LatencyController {
|
|
4760
4704
|
this.currentTime = 0;
|
4761
4705
|
this.stallCount = 0;
|
4762
4706
|
this._latency = null;
|
4763
|
-
this.
|
4764
|
-
const {
|
4765
|
-
media,
|
4766
|
-
levelDetails
|
4767
|
-
} = this;
|
4768
|
-
if (!media || !levelDetails) {
|
4769
|
-
return;
|
4770
|
-
}
|
4771
|
-
this.currentTime = media.currentTime;
|
4772
|
-
const latency = this.computeLatency();
|
4773
|
-
if (latency === null) {
|
4774
|
-
return;
|
4775
|
-
}
|
4776
|
-
this._latency = latency;
|
4777
|
-
|
4778
|
-
// Adapt playbackRate to meet target latency in low-latency mode
|
4779
|
-
const {
|
4780
|
-
lowLatencyMode,
|
4781
|
-
maxLiveSyncPlaybackRate
|
4782
|
-
} = this.config;
|
4783
|
-
if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
|
4784
|
-
return;
|
4785
|
-
}
|
4786
|
-
const targetLatency = this.targetLatency;
|
4787
|
-
if (targetLatency === null) {
|
4788
|
-
return;
|
4789
|
-
}
|
4790
|
-
const distanceFromTarget = latency - targetLatency;
|
4791
|
-
// Only adjust playbackRate when within one target duration of targetLatency
|
4792
|
-
// and more than one second from under-buffering.
|
4793
|
-
// Playback further than one target duration from target can be considered DVR playback.
|
4794
|
-
const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
|
4795
|
-
const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
|
4796
|
-
if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
|
4797
|
-
const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
|
4798
|
-
const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
|
4799
|
-
media.playbackRate = Math.min(max, Math.max(1, rate));
|
4800
|
-
} else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
|
4801
|
-
media.playbackRate = 1;
|
4802
|
-
}
|
4803
|
-
};
|
4707
|
+
this.timeupdateHandler = () => this.timeupdate();
|
4804
4708
|
this.hls = hls;
|
4805
4709
|
this.config = hls.config;
|
4806
4710
|
this.registerListeners();
|
@@ -4892,7 +4796,7 @@ class LatencyController {
|
|
4892
4796
|
this.onMediaDetaching();
|
4893
4797
|
this.levelDetails = null;
|
4894
4798
|
// @ts-ignore
|
4895
|
-
this.hls = null;
|
4799
|
+
this.hls = this.timeupdateHandler = null;
|
4896
4800
|
}
|
4897
4801
|
registerListeners() {
|
4898
4802
|
this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
@@ -4910,11 +4814,11 @@ class LatencyController {
|
|
4910
4814
|
}
|
4911
4815
|
onMediaAttached(event, data) {
|
4912
4816
|
this.media = data.media;
|
4913
|
-
this.media.addEventListener('timeupdate', this.
|
4817
|
+
this.media.addEventListener('timeupdate', this.timeupdateHandler);
|
4914
4818
|
}
|
4915
4819
|
onMediaDetaching() {
|
4916
4820
|
if (this.media) {
|
4917
|
-
this.media.removeEventListener('timeupdate', this.
|
4821
|
+
this.media.removeEventListener('timeupdate', this.timeupdateHandler);
|
4918
4822
|
this.media = null;
|
4919
4823
|
}
|
4920
4824
|
}
|
@@ -4928,10 +4832,10 @@ class LatencyController {
|
|
4928
4832
|
}) {
|
4929
4833
|
this.levelDetails = details;
|
4930
4834
|
if (details.advanced) {
|
4931
|
-
this.
|
4835
|
+
this.timeupdate();
|
4932
4836
|
}
|
4933
4837
|
if (!details.live && this.media) {
|
4934
|
-
this.media.removeEventListener('timeupdate', this.
|
4838
|
+
this.media.removeEventListener('timeupdate', this.timeupdateHandler);
|
4935
4839
|
}
|
4936
4840
|
}
|
4937
4841
|
onError(event, data) {
|
@@ -4941,7 +4845,48 @@ class LatencyController {
|
|
4941
4845
|
}
|
4942
4846
|
this.stallCount++;
|
4943
4847
|
if ((_this$levelDetails = this.levelDetails) != null && _this$levelDetails.live) {
|
4944
|
-
|
4848
|
+
logger.warn('[playback-rate-controller]: Stall detected, adjusting target latency');
|
4849
|
+
}
|
4850
|
+
}
|
4851
|
+
timeupdate() {
|
4852
|
+
const {
|
4853
|
+
media,
|
4854
|
+
levelDetails
|
4855
|
+
} = this;
|
4856
|
+
if (!media || !levelDetails) {
|
4857
|
+
return;
|
4858
|
+
}
|
4859
|
+
this.currentTime = media.currentTime;
|
4860
|
+
const latency = this.computeLatency();
|
4861
|
+
if (latency === null) {
|
4862
|
+
return;
|
4863
|
+
}
|
4864
|
+
this._latency = latency;
|
4865
|
+
|
4866
|
+
// Adapt playbackRate to meet target latency in low-latency mode
|
4867
|
+
const {
|
4868
|
+
lowLatencyMode,
|
4869
|
+
maxLiveSyncPlaybackRate
|
4870
|
+
} = this.config;
|
4871
|
+
if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
|
4872
|
+
return;
|
4873
|
+
}
|
4874
|
+
const targetLatency = this.targetLatency;
|
4875
|
+
if (targetLatency === null) {
|
4876
|
+
return;
|
4877
|
+
}
|
4878
|
+
const distanceFromTarget = latency - targetLatency;
|
4879
|
+
// Only adjust playbackRate when within one target duration of targetLatency
|
4880
|
+
// and more than one second from under-buffering.
|
4881
|
+
// Playback further than one target duration from target can be considered DVR playback.
|
4882
|
+
const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
|
4883
|
+
const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
|
4884
|
+
if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
|
4885
|
+
const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
|
4886
|
+
const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
|
4887
|
+
media.playbackRate = Math.min(max, Math.max(1, rate));
|
4888
|
+
} else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
|
4889
|
+
media.playbackRate = 1;
|
4945
4890
|
}
|
4946
4891
|
}
|
4947
4892
|
estimateLiveEdge() {
|
@@ -5713,13 +5658,18 @@ var ErrorActionFlags = {
|
|
5713
5658
|
MoveAllAlternatesMatchingHDCP: 2,
|
5714
5659
|
SwitchToSDR: 4
|
5715
5660
|
}; // Reserved for future use
|
5716
|
-
class ErrorController
|
5661
|
+
class ErrorController {
|
5717
5662
|
constructor(hls) {
|
5718
|
-
super('error-controller', hls.logger);
|
5719
5663
|
this.hls = void 0;
|
5720
5664
|
this.playlistError = 0;
|
5721
5665
|
this.penalizedRenditions = {};
|
5666
|
+
this.log = void 0;
|
5667
|
+
this.warn = void 0;
|
5668
|
+
this.error = void 0;
|
5722
5669
|
this.hls = hls;
|
5670
|
+
this.log = logger.log.bind(logger, `[info]:`);
|
5671
|
+
this.warn = logger.warn.bind(logger, `[warning]:`);
|
5672
|
+
this.error = logger.error.bind(logger, `[error]:`);
|
5723
5673
|
this.registerListeners();
|
5724
5674
|
}
|
5725
5675
|
registerListeners() {
|
@@ -6071,13 +6021,16 @@ class ErrorController extends Logger {
|
|
6071
6021
|
}
|
6072
6022
|
}
|
6073
6023
|
|
6074
|
-
class BasePlaylistController
|
6024
|
+
class BasePlaylistController {
|
6075
6025
|
constructor(hls, logPrefix) {
|
6076
|
-
super(logPrefix, hls.logger);
|
6077
6026
|
this.hls = void 0;
|
6078
6027
|
this.timer = -1;
|
6079
6028
|
this.requestScheduled = -1;
|
6080
6029
|
this.canLoad = false;
|
6030
|
+
this.log = void 0;
|
6031
|
+
this.warn = void 0;
|
6032
|
+
this.log = logger.log.bind(logger, `${logPrefix}:`);
|
6033
|
+
this.warn = logger.warn.bind(logger, `${logPrefix}:`);
|
6081
6034
|
this.hls = hls;
|
6082
6035
|
}
|
6083
6036
|
destroy() {
|
@@ -6110,7 +6063,7 @@ class BasePlaylistController extends Logger {
|
|
6110
6063
|
try {
|
6111
6064
|
uri = new self.URL(attr.URI, previous.url).href;
|
6112
6065
|
} catch (error) {
|
6113
|
-
|
6066
|
+
logger.warn(`Could not construct new URL for Rendition Report: ${error}`);
|
6114
6067
|
uri = attr.URI || '';
|
6115
6068
|
}
|
6116
6069
|
// Use exact match. Otherwise, the last partial match, if any, will be used
|
@@ -6197,12 +6150,7 @@ class BasePlaylistController extends Logger {
|
|
6197
6150
|
const cdnAge = lastAdvanced + details.ageHeader;
|
6198
6151
|
let currentGoal = Math.min(cdnAge - details.partTarget, details.targetduration * 1.5);
|
6199
6152
|
if (currentGoal > 0) {
|
6200
|
-
if (
|
6201
|
-
// Omit segment and part directives when the last response was more than 3 target durations ago,
|
6202
|
-
this.log(`Playlist last advanced ${lastAdvanced.toFixed(2)}s ago. Omitting segment and part directives.`);
|
6203
|
-
msn = undefined;
|
6204
|
-
part = undefined;
|
6205
|
-
} else if (previousDetails != null && previousDetails.tuneInGoal && cdnAge - details.partTarget > previousDetails.tuneInGoal) {
|
6153
|
+
if (previousDetails && currentGoal > previousDetails.tuneInGoal) {
|
6206
6154
|
// If we attempted to get the next or latest playlist update, but currentGoal increased,
|
6207
6155
|
// then we either can't catchup, or the "age" header cannot be trusted.
|
6208
6156
|
this.warn(`CDN Tune-in goal increased from: ${previousDetails.tuneInGoal} to: ${currentGoal} with playlist age: ${details.age}`);
|
@@ -6874,9 +6822,8 @@ function searchDownAndUpList(arr, searchIndex, predicate) {
|
|
6874
6822
|
return -1;
|
6875
6823
|
}
|
6876
6824
|
|
6877
|
-
class AbrController
|
6825
|
+
class AbrController {
|
6878
6826
|
constructor(_hls) {
|
6879
|
-
super('abr', _hls.logger);
|
6880
6827
|
this.hls = void 0;
|
6881
6828
|
this.lastLevelLoadSec = 0;
|
6882
6829
|
this.lastLoadedFragLevel = -1;
|
@@ -6990,7 +6937,7 @@ class AbrController extends Logger {
|
|
6990
6937
|
this.resetEstimator(nextLoadLevelBitrate);
|
6991
6938
|
}
|
6992
6939
|
this.clearTimer();
|
6993
|
-
|
6940
|
+
logger.warn(`[abr] Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
|
6994
6941
|
Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s
|
6995
6942
|
Estimated load time for current fragment: ${fragLoadedDelay.toFixed(3)} s
|
6996
6943
|
Estimated load time for down switch fragment: ${fragLevelNextLoadedDelay.toFixed(3)} s
|
@@ -7010,7 +6957,7 @@ class AbrController extends Logger {
|
|
7010
6957
|
}
|
7011
6958
|
resetEstimator(abrEwmaDefaultEstimate) {
|
7012
6959
|
if (abrEwmaDefaultEstimate) {
|
7013
|
-
|
6960
|
+
logger.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
|
7014
6961
|
this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate;
|
7015
6962
|
}
|
7016
6963
|
this.firstSelection = -1;
|
@@ -7242,7 +7189,7 @@ class AbrController extends Logger {
|
|
7242
7189
|
}
|
7243
7190
|
const firstLevel = this.hls.firstLevel;
|
7244
7191
|
const clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel);
|
7245
|
-
|
7192
|
+
logger.warn(`[abr] Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
|
7246
7193
|
return clamped;
|
7247
7194
|
}
|
7248
7195
|
get forcedAutoLevel() {
|
@@ -7327,13 +7274,13 @@ class AbrController extends Logger {
|
|
7327
7274
|
// cap maxLoadingDelay and ensure it is not bigger 'than bitrate test' frag duration
|
7328
7275
|
const maxLoadingDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxLoadingDelay) : config.maxLoadingDelay;
|
7329
7276
|
maxStarvationDelay = maxLoadingDelay - bitrateTestDelay;
|
7330
|
-
|
7277
|
+
logger.info(`[abr] bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
|
7331
7278
|
// don't use conservative factor on bitrate test
|
7332
7279
|
bwFactor = bwUpFactor = 1;
|
7333
7280
|
}
|
7334
7281
|
}
|
7335
7282
|
const bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor);
|
7336
|
-
|
7283
|
+
logger.info(`[abr] ${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
|
7337
7284
|
if (bestLevel > -1) {
|
7338
7285
|
return bestLevel;
|
7339
7286
|
}
|
@@ -7395,7 +7342,7 @@ class AbrController extends Logger {
|
|
7395
7342
|
currentVideoRange = preferHDR ? videoRanges[videoRanges.length - 1] : videoRanges[0];
|
7396
7343
|
currentFrameRate = minFramerate;
|
7397
7344
|
currentBw = Math.max(currentBw, minBitrate);
|
7398
|
-
|
7345
|
+
logger.log(`[abr] picked start tier ${JSON.stringify(startTier)}`);
|
7399
7346
|
} else {
|
7400
7347
|
currentCodecSet = level == null ? void 0 : level.codecSet;
|
7401
7348
|
currentVideoRange = level == null ? void 0 : level.videoRange;
|
@@ -7419,11 +7366,11 @@ class AbrController extends Logger {
|
|
7419
7366
|
const levels = this.hls.levels;
|
7420
7367
|
const index = levels.indexOf(levelInfo);
|
7421
7368
|
if (decodingInfo.error) {
|
7422
|
-
|
7369
|
+
logger.warn(`[abr] MediaCapabilities decodingInfo error: "${decodingInfo.error}" for level ${index} ${JSON.stringify(decodingInfo)}`);
|
7423
7370
|
} else if (!decodingInfo.supported) {
|
7424
|
-
|
7371
|
+
logger.warn(`[abr] Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify(decodingInfo)}`);
|
7425
7372
|
if (index > -1 && levels.length > 1) {
|
7426
|
-
|
7373
|
+
logger.log(`[abr] Removing unsupported level ${index}`);
|
7427
7374
|
this.hls.removeLevel(index);
|
7428
7375
|
}
|
7429
7376
|
}
|
@@ -7470,9 +7417,9 @@ class AbrController extends Logger {
|
|
7470
7417
|
const forcedAutoLevel = this.forcedAutoLevel;
|
7471
7418
|
if (i !== loadLevel && (forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)) {
|
7472
7419
|
if (levelsSkipped.length) {
|
7473
|
-
|
7420
|
+
logger.trace(`[abr] Skipped level(s) ${levelsSkipped.join(',')} of ${maxAutoLevel} max with CODECS and VIDEO-RANGE:"${levels[levelsSkipped[0]].codecs}" ${levels[levelsSkipped[0]].videoRange}; not compatible with "${level.codecs}" ${currentVideoRange}`);
|
7474
7421
|
}
|
7475
|
-
|
7422
|
+
logger.info(`[abr] switch candidate:${selectionBaseLevel}->${i} adjustedbw(${Math.round(adjustedbw)})-bitrate=${Math.round(adjustedbw - bitrate)} ttfb:${ttfbEstimateSec.toFixed(1)} avgDuration:${avgDuration.toFixed(1)} maxFetchDuration:${maxFetchDuration.toFixed(1)} fetchDuration:${fetchDuration.toFixed(1)} firstSelection:${firstSelection} codecSet:${currentCodecSet} videoRange:${currentVideoRange} hls.loadLevel:${loadLevel}`);
|
7476
7423
|
}
|
7477
7424
|
if (firstSelection) {
|
7478
7425
|
this.firstSelection = i;
|
@@ -7526,9 +7473,8 @@ class AbrController extends Logger {
|
|
7526
7473
|
* we are limiting the task execution per call stack to exactly one, but scheduling/post-poning further
|
7527
7474
|
* task processing on the next main loop iteration (also known as "next tick" in the Node/JS runtime lingo).
|
7528
7475
|
*/
|
7529
|
-
class TaskLoop
|
7530
|
-
constructor(
|
7531
|
-
super(label, logger);
|
7476
|
+
class TaskLoop {
|
7477
|
+
constructor() {
|
7532
7478
|
this._boundTick = void 0;
|
7533
7479
|
this._tickTimer = null;
|
7534
7480
|
this._tickInterval = null;
|
@@ -8619,8 +8565,8 @@ function createLoaderContext(frag, part = null) {
|
|
8619
8565
|
var _frag$decryptdata;
|
8620
8566
|
let byteRangeStart = start;
|
8621
8567
|
let byteRangeEnd = end;
|
8622
|
-
if (frag.sn === 'initSegment' &&
|
8623
|
-
// MAP segment encrypted with method 'AES-128'
|
8568
|
+
if (frag.sn === 'initSegment' && ((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method) === 'AES-128') {
|
8569
|
+
// MAP segment encrypted with method 'AES-128', when served with HTTP Range,
|
8624
8570
|
// has the unencrypted size specified in the range.
|
8625
8571
|
// Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
|
8626
8572
|
const fragmentLen = end - start;
|
@@ -8653,9 +8599,6 @@ function createGapLoadError(frag, part) {
|
|
8653
8599
|
(part ? part : frag).stats.aborted = true;
|
8654
8600
|
return new LoadError(errorData);
|
8655
8601
|
}
|
8656
|
-
function isMethodFullSegmentAesCbc(method) {
|
8657
|
-
return method === 'AES-128' || method === 'AES-256';
|
8658
|
-
}
|
8659
8602
|
class LoadError extends Error {
|
8660
8603
|
constructor(data) {
|
8661
8604
|
super(data.error.message);
|
@@ -8665,61 +8608,33 @@ class LoadError extends Error {
|
|
8665
8608
|
}
|
8666
8609
|
|
8667
8610
|
class AESCrypto {
|
8668
|
-
constructor(subtle, iv
|
8611
|
+
constructor(subtle, iv) {
|
8669
8612
|
this.subtle = void 0;
|
8670
8613
|
this.aesIV = void 0;
|
8671
|
-
this.aesMode = void 0;
|
8672
8614
|
this.subtle = subtle;
|
8673
8615
|
this.aesIV = iv;
|
8674
|
-
this.aesMode = aesMode;
|
8675
8616
|
}
|
8676
8617
|
decrypt(data, key) {
|
8677
|
-
|
8678
|
-
|
8679
|
-
|
8680
|
-
|
8681
|
-
iv: this.aesIV
|
8682
|
-
}, key, data);
|
8683
|
-
case DecrypterAesMode.ctr:
|
8684
|
-
return this.subtle.decrypt({
|
8685
|
-
name: 'AES-CTR',
|
8686
|
-
counter: this.aesIV,
|
8687
|
-
length: 64
|
8688
|
-
},
|
8689
|
-
//64 : NIST SP800-38A standard suggests that the counter should occupy half of the counter block
|
8690
|
-
key, data);
|
8691
|
-
default:
|
8692
|
-
throw new Error(`[AESCrypto] invalid aes mode ${this.aesMode}`);
|
8693
|
-
}
|
8618
|
+
return this.subtle.decrypt({
|
8619
|
+
name: 'AES-CBC',
|
8620
|
+
iv: this.aesIV
|
8621
|
+
}, key, data);
|
8694
8622
|
}
|
8695
8623
|
}
|
8696
8624
|
|
8697
8625
|
class FastAESKey {
|
8698
|
-
constructor(subtle, key
|
8626
|
+
constructor(subtle, key) {
|
8699
8627
|
this.subtle = void 0;
|
8700
8628
|
this.key = void 0;
|
8701
|
-
this.aesMode = void 0;
|
8702
8629
|
this.subtle = subtle;
|
8703
8630
|
this.key = key;
|
8704
|
-
this.aesMode = aesMode;
|
8705
8631
|
}
|
8706
8632
|
expandKey() {
|
8707
|
-
const subtleAlgoName = getSubtleAlgoName(this.aesMode);
|
8708
8633
|
return this.subtle.importKey('raw', this.key, {
|
8709
|
-
name:
|
8634
|
+
name: 'AES-CBC'
|
8710
8635
|
}, false, ['encrypt', 'decrypt']);
|
8711
8636
|
}
|
8712
8637
|
}
|
8713
|
-
function getSubtleAlgoName(aesMode) {
|
8714
|
-
switch (aesMode) {
|
8715
|
-
case DecrypterAesMode.cbc:
|
8716
|
-
return 'AES-CBC';
|
8717
|
-
case DecrypterAesMode.ctr:
|
8718
|
-
return 'AES-CTR';
|
8719
|
-
default:
|
8720
|
-
throw new Error(`[FastAESKey] invalid aes mode ${aesMode}`);
|
8721
|
-
}
|
8722
|
-
}
|
8723
8638
|
|
8724
8639
|
// PKCS7
|
8725
8640
|
function removePadding(array) {
|
@@ -8969,8 +8884,7 @@ class Decrypter {
|
|
8969
8884
|
this.currentIV = null;
|
8970
8885
|
this.currentResult = null;
|
8971
8886
|
this.useSoftware = void 0;
|
8972
|
-
this.
|
8973
|
-
this.enableSoftwareAES = config.enableSoftwareAES;
|
8887
|
+
this.useSoftware = config.enableSoftwareAES;
|
8974
8888
|
this.removePKCS7Padding = removePKCS7Padding;
|
8975
8889
|
// built in decryptor expects PKCS7 padding
|
8976
8890
|
if (removePKCS7Padding) {
|
@@ -8983,7 +8897,9 @@ class Decrypter {
|
|
8983
8897
|
/* no-op */
|
8984
8898
|
}
|
8985
8899
|
}
|
8986
|
-
|
8900
|
+
if (this.subtle === null) {
|
8901
|
+
this.useSoftware = true;
|
8902
|
+
}
|
8987
8903
|
}
|
8988
8904
|
destroy() {
|
8989
8905
|
this.subtle = null;
|
@@ -9021,10 +8937,10 @@ class Decrypter {
|
|
9021
8937
|
this.softwareDecrypter = null;
|
9022
8938
|
}
|
9023
8939
|
}
|
9024
|
-
decrypt(data, key, iv
|
8940
|
+
decrypt(data, key, iv) {
|
9025
8941
|
if (this.useSoftware) {
|
9026
8942
|
return new Promise((resolve, reject) => {
|
9027
|
-
this.softwareDecrypt(new Uint8Array(data), key, iv
|
8943
|
+
this.softwareDecrypt(new Uint8Array(data), key, iv);
|
9028
8944
|
const decryptResult = this.flush();
|
9029
8945
|
if (decryptResult) {
|
9030
8946
|
resolve(decryptResult.buffer);
|
@@ -9033,21 +8949,17 @@ class Decrypter {
|
|
9033
8949
|
}
|
9034
8950
|
});
|
9035
8951
|
}
|
9036
|
-
return this.webCryptoDecrypt(new Uint8Array(data), key, iv
|
8952
|
+
return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
|
9037
8953
|
}
|
9038
8954
|
|
9039
8955
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
9040
8956
|
// data is handled in the flush() call
|
9041
|
-
softwareDecrypt(data, key, iv
|
8957
|
+
softwareDecrypt(data, key, iv) {
|
9042
8958
|
const {
|
9043
8959
|
currentIV,
|
9044
8960
|
currentResult,
|
9045
8961
|
remainderData
|
9046
8962
|
} = this;
|
9047
|
-
if (aesMode !== DecrypterAesMode.cbc || key.byteLength !== 16) {
|
9048
|
-
logger.warn('SoftwareDecrypt: can only handle AES-128-CBC');
|
9049
|
-
return null;
|
9050
|
-
}
|
9051
8963
|
this.logOnce('JS AES decrypt');
|
9052
8964
|
// The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
|
9053
8965
|
// This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached
|
@@ -9080,11 +8992,11 @@ class Decrypter {
|
|
9080
8992
|
}
|
9081
8993
|
return result;
|
9082
8994
|
}
|
9083
|
-
webCryptoDecrypt(data, key, iv
|
8995
|
+
webCryptoDecrypt(data, key, iv) {
|
9084
8996
|
const subtle = this.subtle;
|
9085
8997
|
if (this.key !== key || !this.fastAesKey) {
|
9086
8998
|
this.key = key;
|
9087
|
-
this.fastAesKey = new FastAESKey(subtle, key
|
8999
|
+
this.fastAesKey = new FastAESKey(subtle, key);
|
9088
9000
|
}
|
9089
9001
|
return this.fastAesKey.expandKey().then(aesKey => {
|
9090
9002
|
// decrypt using web crypto
|
@@ -9092,25 +9004,22 @@ class Decrypter {
|
|
9092
9004
|
return Promise.reject(new Error('web crypto not initialized'));
|
9093
9005
|
}
|
9094
9006
|
this.logOnce('WebCrypto AES decrypt');
|
9095
|
-
const crypto = new AESCrypto(subtle, new Uint8Array(iv)
|
9007
|
+
const crypto = new AESCrypto(subtle, new Uint8Array(iv));
|
9096
9008
|
return crypto.decrypt(data.buffer, aesKey);
|
9097
9009
|
}).catch(err => {
|
9098
9010
|
logger.warn(`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`);
|
9099
|
-
return this.onWebCryptoError(data, key, iv
|
9011
|
+
return this.onWebCryptoError(data, key, iv);
|
9100
9012
|
});
|
9101
9013
|
}
|
9102
|
-
onWebCryptoError(data, key, iv
|
9103
|
-
|
9104
|
-
|
9105
|
-
|
9106
|
-
|
9107
|
-
|
9108
|
-
|
9109
|
-
if (decryptResult) {
|
9110
|
-
return decryptResult.buffer;
|
9111
|
-
}
|
9014
|
+
onWebCryptoError(data, key, iv) {
|
9015
|
+
this.useSoftware = true;
|
9016
|
+
this.logEnabled = true;
|
9017
|
+
this.softwareDecrypt(data, key, iv);
|
9018
|
+
const decryptResult = this.flush();
|
9019
|
+
if (decryptResult) {
|
9020
|
+
return decryptResult.buffer;
|
9112
9021
|
}
|
9113
|
-
throw new Error('WebCrypto
|
9022
|
+
throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data');
|
9114
9023
|
}
|
9115
9024
|
getValidChunk(data) {
|
9116
9025
|
let currentChunk = data;
|
@@ -9161,7 +9070,7 @@ const State = {
|
|
9161
9070
|
};
|
9162
9071
|
class BaseStreamController extends TaskLoop {
|
9163
9072
|
constructor(hls, fragmentTracker, keyLoader, logPrefix, playlistType) {
|
9164
|
-
super(
|
9073
|
+
super();
|
9165
9074
|
this.hls = void 0;
|
9166
9075
|
this.fragPrevious = null;
|
9167
9076
|
this.fragCurrent = null;
|
@@ -9186,98 +9095,22 @@ class BaseStreamController extends TaskLoop {
|
|
9186
9095
|
this.startFragRequested = false;
|
9187
9096
|
this.decrypter = void 0;
|
9188
9097
|
this.initPTS = [];
|
9189
|
-
this.
|
9190
|
-
this.
|
9191
|
-
this.
|
9192
|
-
|
9193
|
-
|
9194
|
-
fragCurrent,
|
9195
|
-
media,
|
9196
|
-
mediaBuffer,
|
9197
|
-
state
|
9198
|
-
} = this;
|
9199
|
-
const currentTime = media ? media.currentTime : 0;
|
9200
|
-
const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
|
9201
|
-
this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
|
9202
|
-
if (this.state === State.ENDED) {
|
9203
|
-
this.resetLoadingState();
|
9204
|
-
} else if (fragCurrent) {
|
9205
|
-
// Seeking while frag load is in progress
|
9206
|
-
const tolerance = config.maxFragLookUpTolerance;
|
9207
|
-
const fragStartOffset = fragCurrent.start - tolerance;
|
9208
|
-
const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
|
9209
|
-
// if seeking out of buffered range or into new one
|
9210
|
-
if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
|
9211
|
-
const pastFragment = currentTime > fragEndOffset;
|
9212
|
-
// if the seek position is outside the current fragment range
|
9213
|
-
if (currentTime < fragStartOffset || pastFragment) {
|
9214
|
-
if (pastFragment && fragCurrent.loader) {
|
9215
|
-
this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
|
9216
|
-
fragCurrent.abortRequests();
|
9217
|
-
this.resetLoadingState();
|
9218
|
-
}
|
9219
|
-
this.fragPrevious = null;
|
9220
|
-
}
|
9221
|
-
}
|
9222
|
-
}
|
9223
|
-
if (media) {
|
9224
|
-
// Remove gap fragments
|
9225
|
-
this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
|
9226
|
-
this.lastCurrentTime = currentTime;
|
9227
|
-
if (!this.loadingParts) {
|
9228
|
-
const bufferEnd = Math.max(bufferInfo.end, currentTime);
|
9229
|
-
const shouldLoadParts = this.shouldLoadParts(this.getLevelDetails(), bufferEnd);
|
9230
|
-
if (shouldLoadParts) {
|
9231
|
-
this.log(`LL-Part loading ON after seeking to ${currentTime.toFixed(2)} with buffer @${bufferEnd.toFixed(2)}`);
|
9232
|
-
this.loadingParts = shouldLoadParts;
|
9233
|
-
}
|
9234
|
-
}
|
9235
|
-
}
|
9236
|
-
|
9237
|
-
// in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
|
9238
|
-
if (!this.loadedmetadata && !bufferInfo.len) {
|
9239
|
-
this.nextLoadPosition = this.startPosition = currentTime;
|
9240
|
-
}
|
9241
|
-
|
9242
|
-
// Async tick to speed up processing
|
9243
|
-
this.tickImmediate();
|
9244
|
-
};
|
9245
|
-
this.onMediaEnded = () => {
|
9246
|
-
// reset startPosition and lastCurrentTime to restart playback @ stream beginning
|
9247
|
-
this.startPosition = this.lastCurrentTime = 0;
|
9248
|
-
if (this.playlistType === PlaylistLevelType.MAIN) {
|
9249
|
-
this.hls.trigger(Events.MEDIA_ENDED, {
|
9250
|
-
stalled: false
|
9251
|
-
});
|
9252
|
-
}
|
9253
|
-
};
|
9098
|
+
this.onvseeking = null;
|
9099
|
+
this.onvended = null;
|
9100
|
+
this.logPrefix = '';
|
9101
|
+
this.log = void 0;
|
9102
|
+
this.warn = void 0;
|
9254
9103
|
this.playlistType = playlistType;
|
9104
|
+
this.logPrefix = logPrefix;
|
9105
|
+
this.log = logger.log.bind(logger, `${logPrefix}:`);
|
9106
|
+
this.warn = logger.warn.bind(logger, `${logPrefix}:`);
|
9255
9107
|
this.hls = hls;
|
9256
9108
|
this.fragmentLoader = new FragmentLoader(hls.config);
|
9257
9109
|
this.keyLoader = keyLoader;
|
9258
9110
|
this.fragmentTracker = fragmentTracker;
|
9259
9111
|
this.config = hls.config;
|
9260
9112
|
this.decrypter = new Decrypter(hls.config);
|
9261
|
-
}
|
9262
|
-
registerListeners() {
|
9263
|
-
const {
|
9264
|
-
hls
|
9265
|
-
} = this;
|
9266
|
-
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
9267
|
-
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
9268
|
-
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
9269
9113
|
hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
9270
|
-
hls.on(Events.ERROR, this.onError, this);
|
9271
|
-
}
|
9272
|
-
unregisterListeners() {
|
9273
|
-
const {
|
9274
|
-
hls
|
9275
|
-
} = this;
|
9276
|
-
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
9277
|
-
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
9278
|
-
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
9279
|
-
hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
9280
|
-
hls.off(Events.ERROR, this.onError, this);
|
9281
9114
|
}
|
9282
9115
|
doTick() {
|
9283
9116
|
this.onTickEnd();
|
@@ -9301,12 +9134,6 @@ class BaseStreamController extends TaskLoop {
|
|
9301
9134
|
this.clearNextTick();
|
9302
9135
|
this.state = State.STOPPED;
|
9303
9136
|
}
|
9304
|
-
pauseBuffering() {
|
9305
|
-
this.buffering = false;
|
9306
|
-
}
|
9307
|
-
resumeBuffering() {
|
9308
|
-
this.buffering = true;
|
9309
|
-
}
|
9310
9137
|
_streamEnded(bufferInfo, levelDetails) {
|
9311
9138
|
// If playlist is live, there is another buffered range after the current range, nothing buffered, media is detached,
|
9312
9139
|
// of nothing loading/loaded return false
|
@@ -9337,8 +9164,10 @@ class BaseStreamController extends TaskLoop {
|
|
9337
9164
|
}
|
9338
9165
|
onMediaAttached(event, data) {
|
9339
9166
|
const media = this.media = this.mediaBuffer = data.media;
|
9340
|
-
|
9341
|
-
|
9167
|
+
this.onvseeking = this.onMediaSeeking.bind(this);
|
9168
|
+
this.onvended = this.onMediaEnded.bind(this);
|
9169
|
+
media.addEventListener('seeking', this.onvseeking);
|
9170
|
+
media.addEventListener('ended', this.onvended);
|
9342
9171
|
const config = this.config;
|
9343
9172
|
if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
|
9344
9173
|
this.startLoad(config.startPosition);
|
@@ -9352,9 +9181,10 @@ class BaseStreamController extends TaskLoop {
|
|
9352
9181
|
}
|
9353
9182
|
|
9354
9183
|
// remove video listeners
|
9355
|
-
if (media) {
|
9356
|
-
media.removeEventListener('seeking', this.
|
9357
|
-
media.removeEventListener('ended', this.
|
9184
|
+
if (media && this.onvseeking && this.onvended) {
|
9185
|
+
media.removeEventListener('seeking', this.onvseeking);
|
9186
|
+
media.removeEventListener('ended', this.onvended);
|
9187
|
+
this.onvseeking = this.onvended = null;
|
9358
9188
|
}
|
9359
9189
|
if (this.keyLoader) {
|
9360
9190
|
this.keyLoader.detach();
|
@@ -9364,8 +9194,56 @@ class BaseStreamController extends TaskLoop {
|
|
9364
9194
|
this.fragmentTracker.removeAllFragments();
|
9365
9195
|
this.stopLoad();
|
9366
9196
|
}
|
9367
|
-
|
9368
|
-
|
9197
|
+
onMediaSeeking() {
|
9198
|
+
const {
|
9199
|
+
config,
|
9200
|
+
fragCurrent,
|
9201
|
+
media,
|
9202
|
+
mediaBuffer,
|
9203
|
+
state
|
9204
|
+
} = this;
|
9205
|
+
const currentTime = media ? media.currentTime : 0;
|
9206
|
+
const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
|
9207
|
+
this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
|
9208
|
+
if (this.state === State.ENDED) {
|
9209
|
+
this.resetLoadingState();
|
9210
|
+
} else if (fragCurrent) {
|
9211
|
+
// Seeking while frag load is in progress
|
9212
|
+
const tolerance = config.maxFragLookUpTolerance;
|
9213
|
+
const fragStartOffset = fragCurrent.start - tolerance;
|
9214
|
+
const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
|
9215
|
+
// if seeking out of buffered range or into new one
|
9216
|
+
if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
|
9217
|
+
const pastFragment = currentTime > fragEndOffset;
|
9218
|
+
// if the seek position is outside the current fragment range
|
9219
|
+
if (currentTime < fragStartOffset || pastFragment) {
|
9220
|
+
if (pastFragment && fragCurrent.loader) {
|
9221
|
+
this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
|
9222
|
+
fragCurrent.abortRequests();
|
9223
|
+
this.resetLoadingState();
|
9224
|
+
}
|
9225
|
+
this.fragPrevious = null;
|
9226
|
+
}
|
9227
|
+
}
|
9228
|
+
}
|
9229
|
+
if (media) {
|
9230
|
+
// Remove gap fragments
|
9231
|
+
this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
|
9232
|
+
this.lastCurrentTime = currentTime;
|
9233
|
+
}
|
9234
|
+
|
9235
|
+
// in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
|
9236
|
+
if (!this.loadedmetadata && !bufferInfo.len) {
|
9237
|
+
this.nextLoadPosition = this.startPosition = currentTime;
|
9238
|
+
}
|
9239
|
+
|
9240
|
+
// Async tick to speed up processing
|
9241
|
+
this.tickImmediate();
|
9242
|
+
}
|
9243
|
+
onMediaEnded() {
|
9244
|
+
// reset startPosition and lastCurrentTime to restart playback @ stream beginning
|
9245
|
+
this.startPosition = this.lastCurrentTime = 0;
|
9246
|
+
}
|
9369
9247
|
onManifestLoaded(event, data) {
|
9370
9248
|
this.startTimeOffset = data.startTimeOffset;
|
9371
9249
|
this.initPTS = [];
|
@@ -9375,7 +9253,7 @@ class BaseStreamController extends TaskLoop {
|
|
9375
9253
|
this.stopLoad();
|
9376
9254
|
super.onHandlerDestroying();
|
9377
9255
|
// @ts-ignore
|
9378
|
-
this.hls =
|
9256
|
+
this.hls = null;
|
9379
9257
|
}
|
9380
9258
|
onHandlerDestroyed() {
|
9381
9259
|
this.state = State.STOPPED;
|
@@ -9506,10 +9384,10 @@ class BaseStreamController extends TaskLoop {
|
|
9506
9384
|
const decryptData = frag.decryptdata;
|
9507
9385
|
|
9508
9386
|
// check to see if the payload needs to be decrypted
|
9509
|
-
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv &&
|
9387
|
+
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
|
9510
9388
|
const startTime = self.performance.now();
|
9511
9389
|
// decrypt init segment data
|
9512
|
-
return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer
|
9390
|
+
return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
|
9513
9391
|
hls.trigger(Events.ERROR, {
|
9514
9392
|
type: ErrorTypes.MEDIA_ERROR,
|
9515
9393
|
details: ErrorDetails.FRAG_DECRYPT_ERROR,
|
@@ -9621,7 +9499,7 @@ class BaseStreamController extends TaskLoop {
|
|
9621
9499
|
}
|
9622
9500
|
let keyLoadingPromise = null;
|
9623
9501
|
if (frag.encrypted && !((_frag$decryptdata = frag.decryptdata) != null && _frag$decryptdata.key)) {
|
9624
|
-
this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.
|
9502
|
+
this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.logPrefix === '[stream-controller]' ? 'level' : 'track'} ${frag.level}`);
|
9625
9503
|
this.state = State.KEY_LOADING;
|
9626
9504
|
this.fragCurrent = frag;
|
9627
9505
|
keyLoadingPromise = this.keyLoader.load(frag).then(keyLoadedData => {
|
@@ -9642,16 +9520,8 @@ class BaseStreamController extends TaskLoop {
|
|
9642
9520
|
} else if (!frag.encrypted && details.encryptedFragments.length) {
|
9643
9521
|
this.keyLoader.loadClear(frag, details.encryptedFragments);
|
9644
9522
|
}
|
9645
|
-
const fragPrevious = this.fragPrevious;
|
9646
|
-
if (frag.sn !== 'initSegment' && (!fragPrevious || frag.sn !== fragPrevious.sn)) {
|
9647
|
-
const shouldLoadParts = this.shouldLoadParts(level.details, frag.end);
|
9648
|
-
if (shouldLoadParts !== this.loadingParts) {
|
9649
|
-
this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} loading sn ${fragPrevious == null ? void 0 : fragPrevious.sn}->${frag.sn}`);
|
9650
|
-
this.loadingParts = shouldLoadParts;
|
9651
|
-
}
|
9652
|
-
}
|
9653
9523
|
targetBufferTime = Math.max(frag.start, targetBufferTime || 0);
|
9654
|
-
if (this.
|
9524
|
+
if (this.config.lowLatencyMode && frag.sn !== 'initSegment') {
|
9655
9525
|
const partList = details.partList;
|
9656
9526
|
if (partList && progressCallback) {
|
9657
9527
|
if (targetBufferTime > frag.end && details.fragmentHint) {
|
@@ -9660,7 +9530,7 @@ class BaseStreamController extends TaskLoop {
|
|
9660
9530
|
const partIndex = this.getNextPart(partList, frag, targetBufferTime);
|
9661
9531
|
if (partIndex > -1) {
|
9662
9532
|
const part = partList[partIndex];
|
9663
|
-
this.log(`Loading part sn: ${frag.sn} p: ${part.index} cc: ${frag.cc} of playlist [${details.startSN}-${details.endSN}] parts [0-${partIndex}-${partList.length - 1}] ${this.
|
9533
|
+
this.log(`Loading part sn: ${frag.sn} p: ${part.index} cc: ${frag.cc} of playlist [${details.startSN}-${details.endSN}] parts [0-${partIndex}-${partList.length - 1}] ${this.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
|
9664
9534
|
this.nextLoadPosition = part.start + part.duration;
|
9665
9535
|
this.state = State.FRAG_LOADING;
|
9666
9536
|
let _result;
|
@@ -9689,14 +9559,7 @@ class BaseStreamController extends TaskLoop {
|
|
9689
9559
|
}
|
9690
9560
|
}
|
9691
9561
|
}
|
9692
|
-
|
9693
|
-
this.log(`LL-Part loading OFF after next part miss @${targetBufferTime.toFixed(2)}`);
|
9694
|
-
this.loadingParts = false;
|
9695
|
-
} else if (!frag.url) {
|
9696
|
-
// Selected fragment hint for part but not loading parts
|
9697
|
-
return Promise.resolve(null);
|
9698
|
-
}
|
9699
|
-
this.log(`Loading fragment ${frag.sn} cc: ${frag.cc} ${details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''}${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
|
9562
|
+
this.log(`Loading fragment ${frag.sn} cc: ${frag.cc} ${details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''}${this.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
|
9700
9563
|
// Don't update nextLoadPosition for fragments which are not buffered
|
9701
9564
|
if (isFiniteNumber(frag.sn) && !this.bitrateTest) {
|
9702
9565
|
this.nextLoadPosition = frag.start + frag.duration;
|
@@ -9794,36 +9657,8 @@ class BaseStreamController extends TaskLoop {
|
|
9794
9657
|
if (part) {
|
9795
9658
|
part.stats.parsing.end = now;
|
9796
9659
|
}
|
9797
|
-
// See if part loading should be disabled/enabled based on buffer and playback position.
|
9798
|
-
if (frag.sn !== 'initSegment') {
|
9799
|
-
const levelDetails = this.getLevelDetails();
|
9800
|
-
const loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
|
9801
|
-
const shouldLoadParts = loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
|
9802
|
-
if (shouldLoadParts !== this.loadingParts) {
|
9803
|
-
this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} after parsing segment ending @${frag.end.toFixed(2)}`);
|
9804
|
-
this.loadingParts = shouldLoadParts;
|
9805
|
-
}
|
9806
|
-
}
|
9807
9660
|
this.updateLevelTiming(frag, part, level, chunkMeta.partial);
|
9808
9661
|
}
|
9809
|
-
shouldLoadParts(details, bufferEnd) {
|
9810
|
-
if (this.config.lowLatencyMode) {
|
9811
|
-
if (!details) {
|
9812
|
-
return this.loadingParts;
|
9813
|
-
}
|
9814
|
-
if (details != null && details.partList) {
|
9815
|
-
var _details$fragmentHint;
|
9816
|
-
// Buffer must be ahead of first part + duration of parts after last segment
|
9817
|
-
// and playback must be at or past segment adjacent to part list
|
9818
|
-
const firstPart = details.partList[0];
|
9819
|
-
const safePartStart = firstPart.end + (((_details$fragmentHint = details.fragmentHint) == null ? void 0 : _details$fragmentHint.duration) || 0);
|
9820
|
-
if (bufferEnd >= safePartStart && this.lastCurrentTime > firstPart.start - firstPart.fragment.duration) {
|
9821
|
-
return true;
|
9822
|
-
}
|
9823
|
-
}
|
9824
|
-
}
|
9825
|
-
return false;
|
9826
|
-
}
|
9827
9662
|
getCurrentContext(chunkMeta) {
|
9828
9663
|
const {
|
9829
9664
|
levels,
|
@@ -9972,8 +9807,7 @@ class BaseStreamController extends TaskLoop {
|
|
9972
9807
|
config
|
9973
9808
|
} = this;
|
9974
9809
|
const start = fragments[0].start;
|
9975
|
-
|
9976
|
-
let frag = null;
|
9810
|
+
let frag;
|
9977
9811
|
if (levelDetails.live) {
|
9978
9812
|
const initialLiveManifestSize = config.initialLiveManifestSize;
|
9979
9813
|
if (fragLen < initialLiveManifestSize) {
|
@@ -9985,10 +9819,6 @@ class BaseStreamController extends TaskLoop {
|
|
9985
9819
|
// Do not load using live logic if the starting frag is requested - we want to use getFragmentAtPosition() so that
|
9986
9820
|
// we get the fragment matching that start time
|
9987
9821
|
if (!levelDetails.PTSKnown && !this.startFragRequested && this.startPosition === -1 || pos < start) {
|
9988
|
-
if (canLoadParts && !this.loadingParts) {
|
9989
|
-
this.log(`LL-Part loading ON for initial live fragment`);
|
9990
|
-
this.loadingParts = true;
|
9991
|
-
}
|
9992
9822
|
frag = this.getInitialLiveFragment(levelDetails, fragments);
|
9993
9823
|
this.startPosition = this.nextLoadPosition = frag ? this.hls.liveSyncPosition || frag.start : pos;
|
9994
9824
|
}
|
@@ -9999,7 +9829,7 @@ class BaseStreamController extends TaskLoop {
|
|
9999
9829
|
|
10000
9830
|
// If we haven't run into any special cases already, just load the fragment most closely matching the requested position
|
10001
9831
|
if (!frag) {
|
10002
|
-
const end =
|
9832
|
+
const end = config.lowLatencyMode ? levelDetails.partEnd : levelDetails.fragmentEnd;
|
10003
9833
|
frag = this.getFragmentAtPosition(pos, end, levelDetails);
|
10004
9834
|
}
|
10005
9835
|
return this.mapToInitFragWhenRequired(frag);
|
@@ -10121,7 +9951,7 @@ class BaseStreamController extends TaskLoop {
|
|
10121
9951
|
} = levelDetails;
|
10122
9952
|
const tolerance = config.maxFragLookUpTolerance;
|
10123
9953
|
const partList = levelDetails.partList;
|
10124
|
-
const loadingParts = !!(
|
9954
|
+
const loadingParts = !!(config.lowLatencyMode && partList != null && partList.length && fragmentHint);
|
10125
9955
|
if (loadingParts && fragmentHint && !this.bitrateTest) {
|
10126
9956
|
// Include incomplete fragment with parts at end
|
10127
9957
|
fragments = fragments.concat(fragmentHint);
|
@@ -10314,7 +10144,7 @@ class BaseStreamController extends TaskLoop {
|
|
10314
10144
|
errorAction.resolved = true;
|
10315
10145
|
}
|
10316
10146
|
} else {
|
10317
|
-
|
10147
|
+
logger.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
|
10318
10148
|
return;
|
10319
10149
|
}
|
10320
10150
|
} else if ((errorAction == null ? void 0 : errorAction.action) === NetworkErrorAction.SendAlternateToPenaltyBox) {
|
@@ -10723,7 +10553,6 @@ const initPTSFn = (timestamp, timeOffset, initPTS) => {
|
|
10723
10553
|
*/
|
10724
10554
|
function getAudioConfig(observer, data, offset, audioCodec) {
|
10725
10555
|
let adtsObjectType;
|
10726
|
-
let originalAdtsObjectType;
|
10727
10556
|
let adtsExtensionSamplingIndex;
|
10728
10557
|
let adtsChannelConfig;
|
10729
10558
|
let config;
|
@@ -10731,7 +10560,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
10731
10560
|
const manifestCodec = audioCodec;
|
10732
10561
|
const adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
|
10733
10562
|
// byte 2
|
10734
|
-
adtsObjectType =
|
10563
|
+
adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
|
10735
10564
|
const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
|
10736
10565
|
if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
|
10737
10566
|
const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
|
@@ -10748,8 +10577,8 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
10748
10577
|
// byte 3
|
10749
10578
|
adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6;
|
10750
10579
|
logger.log(`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`);
|
10751
|
-
//
|
10752
|
-
if (/firefox
|
10580
|
+
// firefox: freq less than 24kHz = AAC SBR (HE-AAC)
|
10581
|
+
if (/firefox/i.test(userAgent)) {
|
10753
10582
|
if (adtsSamplingIndex >= 6) {
|
10754
10583
|
adtsObjectType = 5;
|
10755
10584
|
config = new Array(4);
|
@@ -10843,7 +10672,6 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
10843
10672
|
samplerate: adtsSamplingRates[adtsSamplingIndex],
|
10844
10673
|
channelCount: adtsChannelConfig,
|
10845
10674
|
codec: 'mp4a.40.' + adtsObjectType,
|
10846
|
-
parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
|
10847
10675
|
manifestCodec
|
10848
10676
|
};
|
10849
10677
|
}
|
@@ -10898,8 +10726,7 @@ function initTrackConfig(track, observer, data, offset, audioCodec) {
|
|
10898
10726
|
track.channelCount = config.channelCount;
|
10899
10727
|
track.codec = config.codec;
|
10900
10728
|
track.manifestCodec = config.manifestCodec;
|
10901
|
-
track.
|
10902
|
-
logger.log(`parsed codec:${track.parsedCodec}, codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
|
10729
|
+
logger.log(`parsed codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
|
10903
10730
|
}
|
10904
10731
|
}
|
10905
10732
|
function getFrameDuration(samplerate) {
|
@@ -11490,111 +11317,7 @@ class BaseVideoParser {
|
|
11490
11317
|
logger.log(VideoSample.pts + '/' + VideoSample.dts + ':' + VideoSample.debug);
|
11491
11318
|
}
|
11492
11319
|
}
|
11493
|
-
|
11494
|
-
const len = array.byteLength;
|
11495
|
-
let state = track.naluState || 0;
|
11496
|
-
const lastState = state;
|
11497
|
-
const units = [];
|
11498
|
-
let i = 0;
|
11499
|
-
let value;
|
11500
|
-
let overflow;
|
11501
|
-
let unitType;
|
11502
|
-
let lastUnitStart = -1;
|
11503
|
-
let lastUnitType = 0;
|
11504
|
-
// logger.log('PES:' + Hex.hexDump(array));
|
11505
|
-
|
11506
|
-
if (state === -1) {
|
11507
|
-
// special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
|
11508
|
-
lastUnitStart = 0;
|
11509
|
-
// NALu type is value read from offset 0
|
11510
|
-
lastUnitType = this.getNALuType(array, 0);
|
11511
|
-
state = 0;
|
11512
|
-
i = 1;
|
11513
|
-
}
|
11514
|
-
while (i < len) {
|
11515
|
-
value = array[i++];
|
11516
|
-
// optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
|
11517
|
-
if (!state) {
|
11518
|
-
state = value ? 0 : 1;
|
11519
|
-
continue;
|
11520
|
-
}
|
11521
|
-
if (state === 1) {
|
11522
|
-
state = value ? 0 : 2;
|
11523
|
-
continue;
|
11524
|
-
}
|
11525
|
-
// here we have state either equal to 2 or 3
|
11526
|
-
if (!value) {
|
11527
|
-
state = 3;
|
11528
|
-
} else if (value === 1) {
|
11529
|
-
overflow = i - state - 1;
|
11530
|
-
if (lastUnitStart >= 0) {
|
11531
|
-
const unit = {
|
11532
|
-
data: array.subarray(lastUnitStart, overflow),
|
11533
|
-
type: lastUnitType
|
11534
|
-
};
|
11535
|
-
// logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
|
11536
|
-
units.push(unit);
|
11537
|
-
} else {
|
11538
|
-
// lastUnitStart is undefined => this is the first start code found in this PES packet
|
11539
|
-
// first check if start code delimiter is overlapping between 2 PES packets,
|
11540
|
-
// ie it started in last packet (lastState not zero)
|
11541
|
-
// and ended at the beginning of this PES packet (i <= 4 - lastState)
|
11542
|
-
const lastUnit = this.getLastNalUnit(track.samples);
|
11543
|
-
if (lastUnit) {
|
11544
|
-
if (lastState && i <= 4 - lastState) {
|
11545
|
-
// start delimiter overlapping between PES packets
|
11546
|
-
// strip start delimiter bytes from the end of last NAL unit
|
11547
|
-
// check if lastUnit had a state different from zero
|
11548
|
-
if (lastUnit.state) {
|
11549
|
-
// strip last bytes
|
11550
|
-
lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
|
11551
|
-
}
|
11552
|
-
}
|
11553
|
-
// If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
|
11554
|
-
|
11555
|
-
if (overflow > 0) {
|
11556
|
-
// logger.log('first NALU found with overflow:' + overflow);
|
11557
|
-
lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
|
11558
|
-
lastUnit.state = 0;
|
11559
|
-
}
|
11560
|
-
}
|
11561
|
-
}
|
11562
|
-
// check if we can read unit type
|
11563
|
-
if (i < len) {
|
11564
|
-
unitType = this.getNALuType(array, i);
|
11565
|
-
// logger.log('find NALU @ offset:' + i + ',type:' + unitType);
|
11566
|
-
lastUnitStart = i;
|
11567
|
-
lastUnitType = unitType;
|
11568
|
-
state = 0;
|
11569
|
-
} else {
|
11570
|
-
// not enough byte to read unit type. let's read it on next PES parsing
|
11571
|
-
state = -1;
|
11572
|
-
}
|
11573
|
-
} else {
|
11574
|
-
state = 0;
|
11575
|
-
}
|
11576
|
-
}
|
11577
|
-
if (lastUnitStart >= 0 && state >= 0) {
|
11578
|
-
const unit = {
|
11579
|
-
data: array.subarray(lastUnitStart, len),
|
11580
|
-
type: lastUnitType,
|
11581
|
-
state: state
|
11582
|
-
};
|
11583
|
-
units.push(unit);
|
11584
|
-
// logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
|
11585
|
-
}
|
11586
|
-
// no NALu found
|
11587
|
-
if (units.length === 0) {
|
11588
|
-
// append pes.data to previous NAL unit
|
11589
|
-
const lastUnit = this.getLastNalUnit(track.samples);
|
11590
|
-
if (lastUnit) {
|
11591
|
-
lastUnit.data = appendUint8Array(lastUnit.data, array);
|
11592
|
-
}
|
11593
|
-
}
|
11594
|
-
track.naluState = state;
|
11595
|
-
return units;
|
11596
|
-
}
|
11597
|
-
}
|
11320
|
+
}
|
11598
11321
|
|
11599
11322
|
/**
|
11600
11323
|
* Parser for exponential Golomb codes, a variable-bitwidth number encoding scheme used by h264.
|
@@ -11736,171 +11459,21 @@ class ExpGolomb {
|
|
11736
11459
|
readUInt() {
|
11737
11460
|
return this.readBits(32);
|
11738
11461
|
}
|
11739
|
-
}
|
11740
|
-
|
11741
|
-
class AvcVideoParser extends BaseVideoParser {
|
11742
|
-
parsePES(track, textTrack, pes, last, duration) {
|
11743
|
-
const units = this.parseNALu(track, pes.data);
|
11744
|
-
let VideoSample = this.VideoSample;
|
11745
|
-
let push;
|
11746
|
-
let spsfound = false;
|
11747
|
-
// free pes.data to save up some memory
|
11748
|
-
pes.data = null;
|
11749
|
-
|
11750
|
-
// if new NAL units found and last sample still there, let's push ...
|
11751
|
-
// this helps parsing streams with missing AUD (only do this if AUD never found)
|
11752
|
-
if (VideoSample && units.length && !track.audFound) {
|
11753
|
-
this.pushAccessUnit(VideoSample, track);
|
11754
|
-
VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
|
11755
|
-
}
|
11756
|
-
units.forEach(unit => {
|
11757
|
-
var _VideoSample2;
|
11758
|
-
switch (unit.type) {
|
11759
|
-
// NDR
|
11760
|
-
case 1:
|
11761
|
-
{
|
11762
|
-
let iskey = false;
|
11763
|
-
push = true;
|
11764
|
-
const data = unit.data;
|
11765
|
-
// only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
|
11766
|
-
if (spsfound && data.length > 4) {
|
11767
|
-
// retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
|
11768
|
-
const sliceType = this.readSliceType(data);
|
11769
|
-
// 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
|
11770
|
-
// SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
|
11771
|
-
// An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
|
11772
|
-
// I slice: A slice that is not an SI slice that is decoded using intra prediction only.
|
11773
|
-
// if (sliceType === 2 || sliceType === 7) {
|
11774
|
-
if (sliceType === 2 || sliceType === 4 || sliceType === 7 || sliceType === 9) {
|
11775
|
-
iskey = true;
|
11776
|
-
}
|
11777
|
-
}
|
11778
|
-
if (iskey) {
|
11779
|
-
var _VideoSample;
|
11780
|
-
// if we have non-keyframe data already, that cannot belong to the same frame as a keyframe, so force a push
|
11781
|
-
if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
|
11782
|
-
this.pushAccessUnit(VideoSample, track);
|
11783
|
-
VideoSample = this.VideoSample = null;
|
11784
|
-
}
|
11785
|
-
}
|
11786
|
-
if (!VideoSample) {
|
11787
|
-
VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
|
11788
|
-
}
|
11789
|
-
VideoSample.frame = true;
|
11790
|
-
VideoSample.key = iskey;
|
11791
|
-
break;
|
11792
|
-
// IDR
|
11793
|
-
}
|
11794
|
-
case 5:
|
11795
|
-
push = true;
|
11796
|
-
// handle PES not starting with AUD
|
11797
|
-
// if we have frame data already, that cannot belong to the same frame, so force a push
|
11798
|
-
if ((_VideoSample2 = VideoSample) != null && _VideoSample2.frame && !VideoSample.key) {
|
11799
|
-
this.pushAccessUnit(VideoSample, track);
|
11800
|
-
VideoSample = this.VideoSample = null;
|
11801
|
-
}
|
11802
|
-
if (!VideoSample) {
|
11803
|
-
VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
|
11804
|
-
}
|
11805
|
-
VideoSample.key = true;
|
11806
|
-
VideoSample.frame = true;
|
11807
|
-
break;
|
11808
|
-
// SEI
|
11809
|
-
case 6:
|
11810
|
-
{
|
11811
|
-
push = true;
|
11812
|
-
parseSEIMessageFromNALu(unit.data, 1, pes.pts, textTrack.samples);
|
11813
|
-
break;
|
11814
|
-
// SPS
|
11815
|
-
}
|
11816
|
-
case 7:
|
11817
|
-
{
|
11818
|
-
var _track$pixelRatio, _track$pixelRatio2;
|
11819
|
-
push = true;
|
11820
|
-
spsfound = true;
|
11821
|
-
const sps = unit.data;
|
11822
|
-
const config = this.readSPS(sps);
|
11823
|
-
if (!track.sps || track.width !== config.width || track.height !== config.height || ((_track$pixelRatio = track.pixelRatio) == null ? void 0 : _track$pixelRatio[0]) !== config.pixelRatio[0] || ((_track$pixelRatio2 = track.pixelRatio) == null ? void 0 : _track$pixelRatio2[1]) !== config.pixelRatio[1]) {
|
11824
|
-
track.width = config.width;
|
11825
|
-
track.height = config.height;
|
11826
|
-
track.pixelRatio = config.pixelRatio;
|
11827
|
-
track.sps = [sps];
|
11828
|
-
track.duration = duration;
|
11829
|
-
const codecarray = sps.subarray(1, 4);
|
11830
|
-
let codecstring = 'avc1.';
|
11831
|
-
for (let i = 0; i < 3; i++) {
|
11832
|
-
let h = codecarray[i].toString(16);
|
11833
|
-
if (h.length < 2) {
|
11834
|
-
h = '0' + h;
|
11835
|
-
}
|
11836
|
-
codecstring += h;
|
11837
|
-
}
|
11838
|
-
track.codec = codecstring;
|
11839
|
-
}
|
11840
|
-
break;
|
11841
|
-
}
|
11842
|
-
// PPS
|
11843
|
-
case 8:
|
11844
|
-
push = true;
|
11845
|
-
track.pps = [unit.data];
|
11846
|
-
break;
|
11847
|
-
// AUD
|
11848
|
-
case 9:
|
11849
|
-
push = true;
|
11850
|
-
track.audFound = true;
|
11851
|
-
if (VideoSample) {
|
11852
|
-
this.pushAccessUnit(VideoSample, track);
|
11853
|
-
}
|
11854
|
-
VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
|
11855
|
-
break;
|
11856
|
-
// Filler Data
|
11857
|
-
case 12:
|
11858
|
-
push = true;
|
11859
|
-
break;
|
11860
|
-
default:
|
11861
|
-
push = false;
|
11862
|
-
if (VideoSample) {
|
11863
|
-
VideoSample.debug += 'unknown NAL ' + unit.type + ' ';
|
11864
|
-
}
|
11865
|
-
break;
|
11866
|
-
}
|
11867
|
-
if (VideoSample && push) {
|
11868
|
-
const units = VideoSample.units;
|
11869
|
-
units.push(unit);
|
11870
|
-
}
|
11871
|
-
});
|
11872
|
-
// if last PES packet, push samples
|
11873
|
-
if (last && VideoSample) {
|
11874
|
-
this.pushAccessUnit(VideoSample, track);
|
11875
|
-
this.VideoSample = null;
|
11876
|
-
}
|
11877
|
-
}
|
11878
|
-
getNALuType(data, offset) {
|
11879
|
-
return data[offset] & 0x1f;
|
11880
|
-
}
|
11881
|
-
readSliceType(data) {
|
11882
|
-
const eg = new ExpGolomb(data);
|
11883
|
-
// skip NALu type
|
11884
|
-
eg.readUByte();
|
11885
|
-
// discard first_mb_in_slice
|
11886
|
-
eg.readUEG();
|
11887
|
-
// return slice_type
|
11888
|
-
return eg.readUEG();
|
11889
|
-
}
|
11890
11462
|
|
11891
11463
|
/**
|
11892
|
-
*
|
11464
|
+
* Advance the ExpGolomb decoder past a scaling list. The scaling
|
11465
|
+
* list is optionally transmitted as part of a sequence parameter
|
11893
11466
|
* set and is not relevant to transmuxing.
|
11894
11467
|
* @param count the number of entries in this scaling list
|
11895
11468
|
* @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
|
11896
11469
|
*/
|
11897
|
-
skipScalingList(count
|
11470
|
+
skipScalingList(count) {
|
11898
11471
|
let lastScale = 8;
|
11899
11472
|
let nextScale = 8;
|
11900
11473
|
let deltaScale;
|
11901
11474
|
for (let j = 0; j < count; j++) {
|
11902
11475
|
if (nextScale !== 0) {
|
11903
|
-
deltaScale =
|
11476
|
+
deltaScale = this.readEG();
|
11904
11477
|
nextScale = (lastScale + deltaScale + 256) % 256;
|
11905
11478
|
}
|
11906
11479
|
lastScale = nextScale === 0 ? lastScale : nextScale;
|
@@ -11915,8 +11488,7 @@ class AvcVideoParser extends BaseVideoParser {
|
|
11915
11488
|
* sequence parameter set, including the dimensions of the
|
11916
11489
|
* associated video frames.
|
11917
11490
|
*/
|
11918
|
-
readSPS(
|
11919
|
-
const eg = new ExpGolomb(sps);
|
11491
|
+
readSPS() {
|
11920
11492
|
let frameCropLeftOffset = 0;
|
11921
11493
|
let frameCropRightOffset = 0;
|
11922
11494
|
let frameCropTopOffset = 0;
|
@@ -11924,13 +11496,13 @@ class AvcVideoParser extends BaseVideoParser {
|
|
11924
11496
|
let numRefFramesInPicOrderCntCycle;
|
11925
11497
|
let scalingListCount;
|
11926
11498
|
let i;
|
11927
|
-
const readUByte =
|
11928
|
-
const readBits =
|
11929
|
-
const readUEG =
|
11930
|
-
const readBoolean =
|
11931
|
-
const skipBits =
|
11932
|
-
const skipEG =
|
11933
|
-
const skipUEG =
|
11499
|
+
const readUByte = this.readUByte.bind(this);
|
11500
|
+
const readBits = this.readBits.bind(this);
|
11501
|
+
const readUEG = this.readUEG.bind(this);
|
11502
|
+
const readBoolean = this.readBoolean.bind(this);
|
11503
|
+
const skipBits = this.skipBits.bind(this);
|
11504
|
+
const skipEG = this.skipEG.bind(this);
|
11505
|
+
const skipUEG = this.skipUEG.bind(this);
|
11934
11506
|
const skipScalingList = this.skipScalingList.bind(this);
|
11935
11507
|
readUByte();
|
11936
11508
|
const profileIdc = readUByte(); // profile_idc
|
@@ -11955,9 +11527,9 @@ class AvcVideoParser extends BaseVideoParser {
|
|
11955
11527
|
if (readBoolean()) {
|
11956
11528
|
// seq_scaling_list_present_flag[ i ]
|
11957
11529
|
if (i < 6) {
|
11958
|
-
skipScalingList(16
|
11530
|
+
skipScalingList(16);
|
11959
11531
|
} else {
|
11960
|
-
skipScalingList(64
|
11532
|
+
skipScalingList(64);
|
11961
11533
|
}
|
11962
11534
|
}
|
11963
11535
|
}
|
@@ -12062,15 +11634,19 @@ class AvcVideoParser extends BaseVideoParser {
|
|
12062
11634
|
pixelRatio: pixelRatio
|
12063
11635
|
};
|
12064
11636
|
}
|
11637
|
+
readSliceType() {
|
11638
|
+
// skip NALu type
|
11639
|
+
this.readUByte();
|
11640
|
+
// discard first_mb_in_slice
|
11641
|
+
this.readUEG();
|
11642
|
+
// return slice_type
|
11643
|
+
return this.readUEG();
|
11644
|
+
}
|
12065
11645
|
}
|
12066
11646
|
|
12067
|
-
class
|
12068
|
-
|
12069
|
-
|
12070
|
-
this.initVPS = null;
|
12071
|
-
}
|
12072
|
-
parsePES(track, textTrack, pes, last, duration) {
|
12073
|
-
const units = this.parseNALu(track, pes.data);
|
11647
|
+
class AvcVideoParser extends BaseVideoParser {
|
11648
|
+
parseAVCPES(track, textTrack, pes, last, duration) {
|
11649
|
+
const units = this.parseAVCNALu(track, pes.data);
|
12074
11650
|
let VideoSample = this.VideoSample;
|
12075
11651
|
let push;
|
12076
11652
|
let spsfound = false;
|
@@ -12086,49 +11662,42 @@ class HevcVideoParser extends BaseVideoParser {
|
|
12086
11662
|
units.forEach(unit => {
|
12087
11663
|
var _VideoSample2;
|
12088
11664
|
switch (unit.type) {
|
12089
|
-
//
|
12090
|
-
case 0:
|
11665
|
+
// NDR
|
12091
11666
|
case 1:
|
12092
|
-
|
12093
|
-
|
12094
|
-
|
12095
|
-
|
12096
|
-
|
12097
|
-
|
12098
|
-
|
12099
|
-
|
12100
|
-
|
12101
|
-
|
12102
|
-
|
12103
|
-
|
12104
|
-
|
12105
|
-
|
12106
|
-
|
12107
|
-
|
12108
|
-
case 16:
|
12109
|
-
case 17:
|
12110
|
-
case 18:
|
12111
|
-
case 21:
|
12112
|
-
push = true;
|
12113
|
-
if (spsfound) {
|
12114
|
-
var _VideoSample;
|
12115
|
-
// handle PES not starting with AUD
|
12116
|
-
// if we have frame data already, that cannot belong to the same frame, so force a push
|
12117
|
-
if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
|
12118
|
-
this.pushAccessUnit(VideoSample, track);
|
12119
|
-
VideoSample = this.VideoSample = null;
|
11667
|
+
{
|
11668
|
+
let iskey = false;
|
11669
|
+
push = true;
|
11670
|
+
const data = unit.data;
|
11671
|
+
// only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
|
11672
|
+
if (spsfound && data.length > 4) {
|
11673
|
+
// retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
|
11674
|
+
const sliceType = new ExpGolomb(data).readSliceType();
|
11675
|
+
// 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
|
11676
|
+
// SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
|
11677
|
+
// An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
|
11678
|
+
// I slice: A slice that is not an SI slice that is decoded using intra prediction only.
|
11679
|
+
// if (sliceType === 2 || sliceType === 7) {
|
11680
|
+
if (sliceType === 2 || sliceType === 4 || sliceType === 7 || sliceType === 9) {
|
11681
|
+
iskey = true;
|
11682
|
+
}
|
12120
11683
|
}
|
11684
|
+
if (iskey) {
|
11685
|
+
var _VideoSample;
|
11686
|
+
// if we have non-keyframe data already, that cannot belong to the same frame as a keyframe, so force a push
|
11687
|
+
if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
|
11688
|
+
this.pushAccessUnit(VideoSample, track);
|
11689
|
+
VideoSample = this.VideoSample = null;
|
11690
|
+
}
|
11691
|
+
}
|
11692
|
+
if (!VideoSample) {
|
11693
|
+
VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
|
11694
|
+
}
|
11695
|
+
VideoSample.frame = true;
|
11696
|
+
VideoSample.key = iskey;
|
11697
|
+
break;
|
11698
|
+
// IDR
|
12121
11699
|
}
|
12122
|
-
|
12123
|
-
VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
|
12124
|
-
}
|
12125
|
-
VideoSample.key = true;
|
12126
|
-
VideoSample.frame = true;
|
12127
|
-
break;
|
12128
|
-
|
12129
|
-
// IDR
|
12130
|
-
case 19:
|
12131
|
-
case 20:
|
11700
|
+
case 5:
|
12132
11701
|
push = true;
|
12133
11702
|
// handle PES not starting with AUD
|
12134
11703
|
// if we have frame data already, that cannot belong to the same frame, so force a push
|
@@ -12142,76 +11711,48 @@ class HevcVideoParser extends BaseVideoParser {
|
|
12142
11711
|
VideoSample.key = true;
|
12143
11712
|
VideoSample.frame = true;
|
12144
11713
|
break;
|
12145
|
-
|
12146
11714
|
// SEI
|
12147
|
-
case
|
12148
|
-
|
12149
|
-
|
12150
|
-
|
12151
|
-
|
12152
|
-
|
12153
|
-
|
12154
|
-
// VPS
|
12155
|
-
case 32:
|
12156
|
-
push = true;
|
12157
|
-
if (!track.vps) {
|
12158
|
-
const config = this.readVPS(unit.data);
|
12159
|
-
track.params = _objectSpread2({}, config);
|
12160
|
-
this.initVPS = unit.data;
|
11715
|
+
case 6:
|
11716
|
+
{
|
11717
|
+
push = true;
|
11718
|
+
parseSEIMessageFromNALu(unit.data, 1, pes.pts, textTrack.samples);
|
11719
|
+
break;
|
11720
|
+
// SPS
|
12161
11721
|
}
|
12162
|
-
|
12163
|
-
|
12164
|
-
|
12165
|
-
|
12166
|
-
|
12167
|
-
|
12168
|
-
|
12169
|
-
|
12170
|
-
if (track.
|
12171
|
-
this.initVPS = track.vps[0];
|
12172
|
-
track.sps = track.pps = undefined;
|
12173
|
-
}
|
12174
|
-
if (!track.sps) {
|
12175
|
-
const config = this.readSPS(unit.data);
|
11722
|
+
case 7:
|
11723
|
+
{
|
11724
|
+
var _track$pixelRatio, _track$pixelRatio2;
|
11725
|
+
push = true;
|
11726
|
+
spsfound = true;
|
11727
|
+
const sps = unit.data;
|
11728
|
+
const expGolombDecoder = new ExpGolomb(sps);
|
11729
|
+
const config = expGolombDecoder.readSPS();
|
11730
|
+
if (!track.sps || track.width !== config.width || track.height !== config.height || ((_track$pixelRatio = track.pixelRatio) == null ? void 0 : _track$pixelRatio[0]) !== config.pixelRatio[0] || ((_track$pixelRatio2 = track.pixelRatio) == null ? void 0 : _track$pixelRatio2[1]) !== config.pixelRatio[1]) {
|
12176
11731
|
track.width = config.width;
|
12177
11732
|
track.height = config.height;
|
12178
11733
|
track.pixelRatio = config.pixelRatio;
|
11734
|
+
track.sps = [sps];
|
12179
11735
|
track.duration = duration;
|
12180
|
-
|
12181
|
-
|
12182
|
-
for (
|
12183
|
-
|
11736
|
+
const codecarray = sps.subarray(1, 4);
|
11737
|
+
let codecstring = 'avc1.';
|
11738
|
+
for (let i = 0; i < 3; i++) {
|
11739
|
+
let h = codecarray[i].toString(16);
|
11740
|
+
if (h.length < 2) {
|
11741
|
+
h = '0' + h;
|
11742
|
+
}
|
11743
|
+
codecstring += h;
|
12184
11744
|
}
|
11745
|
+
track.codec = codecstring;
|
12185
11746
|
}
|
12186
|
-
|
12187
|
-
track.sps.push(unit.data);
|
12188
|
-
}
|
12189
|
-
}
|
12190
|
-
if (!VideoSample) {
|
12191
|
-
VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
|
11747
|
+
break;
|
12192
11748
|
}
|
12193
|
-
VideoSample.key = true;
|
12194
|
-
break;
|
12195
|
-
|
12196
11749
|
// PPS
|
12197
|
-
case
|
11750
|
+
case 8:
|
12198
11751
|
push = true;
|
12199
|
-
|
12200
|
-
if (!track.pps) {
|
12201
|
-
track.pps = [];
|
12202
|
-
const config = this.readPPS(unit.data);
|
12203
|
-
for (const prop in config) {
|
12204
|
-
track.params[prop] = config[prop];
|
12205
|
-
}
|
12206
|
-
}
|
12207
|
-
if (this.initVPS !== null || track.pps.length === 0) {
|
12208
|
-
track.pps.push(unit.data);
|
12209
|
-
}
|
12210
|
-
}
|
11752
|
+
track.pps = [unit.data];
|
12211
11753
|
break;
|
12212
|
-
|
12213
|
-
|
12214
|
-
case 35:
|
11754
|
+
// AUD
|
11755
|
+
case 9:
|
12215
11756
|
push = true;
|
12216
11757
|
track.audFound = true;
|
12217
11758
|
if (VideoSample) {
|
@@ -12219,10 +11760,14 @@ class HevcVideoParser extends BaseVideoParser {
|
|
12219
11760
|
}
|
12220
11761
|
VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
|
12221
11762
|
break;
|
11763
|
+
// Filler Data
|
11764
|
+
case 12:
|
11765
|
+
push = true;
|
11766
|
+
break;
|
12222
11767
|
default:
|
12223
11768
|
push = false;
|
12224
11769
|
if (VideoSample) {
|
12225
|
-
VideoSample.debug += 'unknown
|
11770
|
+
VideoSample.debug += 'unknown NAL ' + unit.type + ' ';
|
12226
11771
|
}
|
12227
11772
|
break;
|
12228
11773
|
}
|
@@ -12237,423 +11782,109 @@ class HevcVideoParser extends BaseVideoParser {
|
|
12237
11782
|
this.VideoSample = null;
|
12238
11783
|
}
|
12239
11784
|
}
|
12240
|
-
|
12241
|
-
|
12242
|
-
|
12243
|
-
|
12244
|
-
const
|
12245
|
-
let
|
12246
|
-
|
12247
|
-
|
12248
|
-
|
12249
|
-
|
12250
|
-
|
12251
|
-
|
12252
|
-
}
|
12253
|
-
dst[dstIdx] = arr[i];
|
12254
|
-
dstIdx++;
|
12255
|
-
}
|
12256
|
-
return new Uint8Array(dst.buffer, 0, dstIdx);
|
12257
|
-
}
|
12258
|
-
readVPS(vps) {
|
12259
|
-
const eg = new ExpGolomb(vps);
|
12260
|
-
// remove header
|
12261
|
-
eg.readUByte();
|
12262
|
-
eg.readUByte();
|
12263
|
-
eg.readBits(4); // video_parameter_set_id
|
12264
|
-
eg.skipBits(2);
|
12265
|
-
eg.readBits(6); // max_layers_minus1
|
12266
|
-
const max_sub_layers_minus1 = eg.readBits(3);
|
12267
|
-
const temporal_id_nesting_flag = eg.readBoolean();
|
12268
|
-
// ...vui fps can be here, but empty fps value is not critical for metadata
|
11785
|
+
parseAVCNALu(track, array) {
|
11786
|
+
const len = array.byteLength;
|
11787
|
+
let state = track.naluState || 0;
|
11788
|
+
const lastState = state;
|
11789
|
+
const units = [];
|
11790
|
+
let i = 0;
|
11791
|
+
let value;
|
11792
|
+
let overflow;
|
11793
|
+
let unitType;
|
11794
|
+
let lastUnitStart = -1;
|
11795
|
+
let lastUnitType = 0;
|
11796
|
+
// logger.log('PES:' + Hex.hexDump(array));
|
12269
11797
|
|
12270
|
-
|
12271
|
-
|
12272
|
-
|
12273
|
-
|
12274
|
-
|
12275
|
-
|
12276
|
-
|
12277
|
-
|
12278
|
-
|
12279
|
-
|
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
|
-
eg.readUByte(); // sub_layer_profile_compatibility_flag
|
12316
|
-
eg.readUByte();
|
12317
|
-
eg.readUByte();
|
12318
|
-
eg.readUByte();
|
12319
|
-
eg.readUByte();
|
12320
|
-
eg.readUByte();
|
12321
|
-
eg.readUByte();
|
12322
|
-
}
|
12323
|
-
if (sub_layer_level_present_flags[i]) {
|
12324
|
-
eg.readUByte();
|
12325
|
-
}
|
12326
|
-
}
|
12327
|
-
eg.readUEG(); // seq_parameter_set_id
|
12328
|
-
const chroma_format_idc = eg.readUEG();
|
12329
|
-
if (chroma_format_idc == 3) {
|
12330
|
-
eg.skipBits(1); //separate_colour_plane_flag
|
12331
|
-
}
|
12332
|
-
const pic_width_in_luma_samples = eg.readUEG();
|
12333
|
-
const pic_height_in_luma_samples = eg.readUEG();
|
12334
|
-
const conformance_window_flag = eg.readBoolean();
|
12335
|
-
let pic_left_offset = 0,
|
12336
|
-
pic_right_offset = 0,
|
12337
|
-
pic_top_offset = 0,
|
12338
|
-
pic_bottom_offset = 0;
|
12339
|
-
if (conformance_window_flag) {
|
12340
|
-
pic_left_offset += eg.readUEG();
|
12341
|
-
pic_right_offset += eg.readUEG();
|
12342
|
-
pic_top_offset += eg.readUEG();
|
12343
|
-
pic_bottom_offset += eg.readUEG();
|
12344
|
-
}
|
12345
|
-
const bit_depth_luma_minus8 = eg.readUEG();
|
12346
|
-
const bit_depth_chroma_minus8 = eg.readUEG();
|
12347
|
-
const log2_max_pic_order_cnt_lsb_minus4 = eg.readUEG();
|
12348
|
-
const sub_layer_ordering_info_present_flag = eg.readBoolean();
|
12349
|
-
for (let i = sub_layer_ordering_info_present_flag ? 0 : max_sub_layers_minus1; i <= max_sub_layers_minus1; i++) {
|
12350
|
-
eg.skipUEG(); // max_dec_pic_buffering_minus1[i]
|
12351
|
-
eg.skipUEG(); // max_num_reorder_pics[i]
|
12352
|
-
eg.skipUEG(); // max_latency_increase_plus1[i]
|
12353
|
-
}
|
12354
|
-
eg.skipUEG(); // log2_min_luma_coding_block_size_minus3
|
12355
|
-
eg.skipUEG(); // log2_diff_max_min_luma_coding_block_size
|
12356
|
-
eg.skipUEG(); // log2_min_transform_block_size_minus2
|
12357
|
-
eg.skipUEG(); // log2_diff_max_min_transform_block_size
|
12358
|
-
eg.skipUEG(); // max_transform_hierarchy_depth_inter
|
12359
|
-
eg.skipUEG(); // max_transform_hierarchy_depth_intra
|
12360
|
-
const scaling_list_enabled_flag = eg.readBoolean();
|
12361
|
-
if (scaling_list_enabled_flag) {
|
12362
|
-
const sps_scaling_list_data_present_flag = eg.readBoolean();
|
12363
|
-
if (sps_scaling_list_data_present_flag) {
|
12364
|
-
for (let sizeId = 0; sizeId < 4; sizeId++) {
|
12365
|
-
for (let matrixId = 0; matrixId < (sizeId === 3 ? 2 : 6); matrixId++) {
|
12366
|
-
const scaling_list_pred_mode_flag = eg.readBoolean();
|
12367
|
-
if (!scaling_list_pred_mode_flag) {
|
12368
|
-
eg.readUEG(); // scaling_list_pred_matrix_id_delta
|
12369
|
-
} else {
|
12370
|
-
const coefNum = Math.min(64, 1 << 4 + (sizeId << 1));
|
12371
|
-
if (sizeId > 1) {
|
12372
|
-
eg.readEG();
|
12373
|
-
}
|
12374
|
-
for (let i = 0; i < coefNum; i++) {
|
12375
|
-
eg.readEG();
|
11798
|
+
if (state === -1) {
|
11799
|
+
// special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
|
11800
|
+
lastUnitStart = 0;
|
11801
|
+
// NALu type is value read from offset 0
|
11802
|
+
lastUnitType = array[0] & 0x1f;
|
11803
|
+
state = 0;
|
11804
|
+
i = 1;
|
11805
|
+
}
|
11806
|
+
while (i < len) {
|
11807
|
+
value = array[i++];
|
11808
|
+
// optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
|
11809
|
+
if (!state) {
|
11810
|
+
state = value ? 0 : 1;
|
11811
|
+
continue;
|
11812
|
+
}
|
11813
|
+
if (state === 1) {
|
11814
|
+
state = value ? 0 : 2;
|
11815
|
+
continue;
|
11816
|
+
}
|
11817
|
+
// here we have state either equal to 2 or 3
|
11818
|
+
if (!value) {
|
11819
|
+
state = 3;
|
11820
|
+
} else if (value === 1) {
|
11821
|
+
overflow = i - state - 1;
|
11822
|
+
if (lastUnitStart >= 0) {
|
11823
|
+
const unit = {
|
11824
|
+
data: array.subarray(lastUnitStart, overflow),
|
11825
|
+
type: lastUnitType
|
11826
|
+
};
|
11827
|
+
// logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
|
11828
|
+
units.push(unit);
|
11829
|
+
} else {
|
11830
|
+
// lastUnitStart is undefined => this is the first start code found in this PES packet
|
11831
|
+
// first check if start code delimiter is overlapping between 2 PES packets,
|
11832
|
+
// ie it started in last packet (lastState not zero)
|
11833
|
+
// and ended at the beginning of this PES packet (i <= 4 - lastState)
|
11834
|
+
const lastUnit = this.getLastNalUnit(track.samples);
|
11835
|
+
if (lastUnit) {
|
11836
|
+
if (lastState && i <= 4 - lastState) {
|
11837
|
+
// start delimiter overlapping between PES packets
|
11838
|
+
// strip start delimiter bytes from the end of last NAL unit
|
11839
|
+
// check if lastUnit had a state different from zero
|
11840
|
+
if (lastUnit.state) {
|
11841
|
+
// strip last bytes
|
11842
|
+
lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
|
12376
11843
|
}
|
12377
11844
|
}
|
11845
|
+
// If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
|
11846
|
+
|
11847
|
+
if (overflow > 0) {
|
11848
|
+
// logger.log('first NALU found with overflow:' + overflow);
|
11849
|
+
lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
|
11850
|
+
lastUnit.state = 0;
|
11851
|
+
}
|
12378
11852
|
}
|
12379
11853
|
}
|
12380
|
-
|
12381
|
-
|
12382
|
-
|
12383
|
-
|
12384
|
-
|
12385
|
-
|
12386
|
-
|
12387
|
-
|
12388
|
-
|
12389
|
-
|
12390
|
-
}
|
12391
|
-
const num_short_term_ref_pic_sets = eg.readUEG();
|
12392
|
-
let num_delta_pocs = 0;
|
12393
|
-
for (let i = 0; i < num_short_term_ref_pic_sets; i++) {
|
12394
|
-
let inter_ref_pic_set_prediction_flag = false;
|
12395
|
-
if (i !== 0) {
|
12396
|
-
inter_ref_pic_set_prediction_flag = eg.readBoolean();
|
12397
|
-
}
|
12398
|
-
if (inter_ref_pic_set_prediction_flag) {
|
12399
|
-
if (i === num_short_term_ref_pic_sets) {
|
12400
|
-
eg.readUEG();
|
12401
|
-
}
|
12402
|
-
eg.readBoolean();
|
12403
|
-
eg.readUEG();
|
12404
|
-
let next_num_delta_pocs = 0;
|
12405
|
-
for (let j = 0; j <= num_delta_pocs; j++) {
|
12406
|
-
const used_by_curr_pic_flag = eg.readBoolean();
|
12407
|
-
let use_delta_flag = false;
|
12408
|
-
if (!used_by_curr_pic_flag) {
|
12409
|
-
use_delta_flag = eg.readBoolean();
|
12410
|
-
}
|
12411
|
-
if (used_by_curr_pic_flag || use_delta_flag) {
|
12412
|
-
next_num_delta_pocs++;
|
12413
|
-
}
|
11854
|
+
// check if we can read unit type
|
11855
|
+
if (i < len) {
|
11856
|
+
unitType = array[i] & 0x1f;
|
11857
|
+
// logger.log('find NALU @ offset:' + i + ',type:' + unitType);
|
11858
|
+
lastUnitStart = i;
|
11859
|
+
lastUnitType = unitType;
|
11860
|
+
state = 0;
|
11861
|
+
} else {
|
11862
|
+
// not enough byte to read unit type. let's read it on next PES parsing
|
11863
|
+
state = -1;
|
12414
11864
|
}
|
12415
|
-
num_delta_pocs = next_num_delta_pocs;
|
12416
11865
|
} else {
|
12417
|
-
|
12418
|
-
const num_positive_pics = eg.readUEG();
|
12419
|
-
num_delta_pocs = num_negative_pics + num_positive_pics;
|
12420
|
-
for (let j = 0; j < num_negative_pics; j++) {
|
12421
|
-
eg.readUEG();
|
12422
|
-
eg.readBoolean();
|
12423
|
-
}
|
12424
|
-
for (let j = 0; j < num_positive_pics; j++) {
|
12425
|
-
eg.readUEG();
|
12426
|
-
eg.readBoolean();
|
12427
|
-
}
|
12428
|
-
}
|
12429
|
-
}
|
12430
|
-
const long_term_ref_pics_present_flag = eg.readBoolean();
|
12431
|
-
if (long_term_ref_pics_present_flag) {
|
12432
|
-
const num_long_term_ref_pics_sps = eg.readUEG();
|
12433
|
-
for (let i = 0; i < num_long_term_ref_pics_sps; i++) {
|
12434
|
-
for (let j = 0; j < log2_max_pic_order_cnt_lsb_minus4 + 4; j++) {
|
12435
|
-
eg.readBits(1);
|
12436
|
-
}
|
12437
|
-
eg.readBits(1);
|
12438
|
-
}
|
12439
|
-
}
|
12440
|
-
let min_spatial_segmentation_idc = 0;
|
12441
|
-
let sar_width = 1,
|
12442
|
-
sar_height = 1;
|
12443
|
-
let fps_fixed = true,
|
12444
|
-
fps_den = 1,
|
12445
|
-
fps_num = 0;
|
12446
|
-
eg.readBoolean(); // sps_temporal_mvp_enabled_flag
|
12447
|
-
eg.readBoolean(); // strong_intra_smoothing_enabled_flag
|
12448
|
-
let default_display_window_flag = false;
|
12449
|
-
const vui_parameters_present_flag = eg.readBoolean();
|
12450
|
-
if (vui_parameters_present_flag) {
|
12451
|
-
const aspect_ratio_info_present_flag = eg.readBoolean();
|
12452
|
-
if (aspect_ratio_info_present_flag) {
|
12453
|
-
const aspect_ratio_idc = eg.readUByte();
|
12454
|
-
const sar_width_table = [1, 12, 10, 16, 40, 24, 20, 32, 80, 18, 15, 64, 160, 4, 3, 2];
|
12455
|
-
const sar_height_table = [1, 11, 11, 11, 33, 11, 11, 11, 33, 11, 11, 33, 99, 3, 2, 1];
|
12456
|
-
if (aspect_ratio_idc > 0 && aspect_ratio_idc < 16) {
|
12457
|
-
sar_width = sar_width_table[aspect_ratio_idc - 1];
|
12458
|
-
sar_height = sar_height_table[aspect_ratio_idc - 1];
|
12459
|
-
} else if (aspect_ratio_idc === 255) {
|
12460
|
-
sar_width = eg.readBits(16);
|
12461
|
-
sar_height = eg.readBits(16);
|
12462
|
-
}
|
12463
|
-
}
|
12464
|
-
const overscan_info_present_flag = eg.readBoolean();
|
12465
|
-
if (overscan_info_present_flag) {
|
12466
|
-
eg.readBoolean();
|
12467
|
-
}
|
12468
|
-
const video_signal_type_present_flag = eg.readBoolean();
|
12469
|
-
if (video_signal_type_present_flag) {
|
12470
|
-
eg.readBits(3);
|
12471
|
-
eg.readBoolean();
|
12472
|
-
const colour_description_present_flag = eg.readBoolean();
|
12473
|
-
if (colour_description_present_flag) {
|
12474
|
-
eg.readUByte();
|
12475
|
-
eg.readUByte();
|
12476
|
-
eg.readUByte();
|
12477
|
-
}
|
12478
|
-
}
|
12479
|
-
const chroma_loc_info_present_flag = eg.readBoolean();
|
12480
|
-
if (chroma_loc_info_present_flag) {
|
12481
|
-
eg.readUEG();
|
12482
|
-
eg.readUEG();
|
12483
|
-
}
|
12484
|
-
eg.readBoolean(); // neutral_chroma_indication_flag
|
12485
|
-
eg.readBoolean(); // field_seq_flag
|
12486
|
-
eg.readBoolean(); // frame_field_info_present_flag
|
12487
|
-
default_display_window_flag = eg.readBoolean();
|
12488
|
-
if (default_display_window_flag) {
|
12489
|
-
pic_left_offset += eg.readUEG();
|
12490
|
-
pic_right_offset += eg.readUEG();
|
12491
|
-
pic_top_offset += eg.readUEG();
|
12492
|
-
pic_bottom_offset += eg.readUEG();
|
12493
|
-
}
|
12494
|
-
const vui_timing_info_present_flag = eg.readBoolean();
|
12495
|
-
if (vui_timing_info_present_flag) {
|
12496
|
-
fps_den = eg.readBits(32);
|
12497
|
-
fps_num = eg.readBits(32);
|
12498
|
-
const vui_poc_proportional_to_timing_flag = eg.readBoolean();
|
12499
|
-
if (vui_poc_proportional_to_timing_flag) {
|
12500
|
-
eg.readUEG();
|
12501
|
-
}
|
12502
|
-
const vui_hrd_parameters_present_flag = eg.readBoolean();
|
12503
|
-
if (vui_hrd_parameters_present_flag) {
|
12504
|
-
//const commonInfPresentFlag = true;
|
12505
|
-
//if (commonInfPresentFlag) {
|
12506
|
-
const nal_hrd_parameters_present_flag = eg.readBoolean();
|
12507
|
-
const vcl_hrd_parameters_present_flag = eg.readBoolean();
|
12508
|
-
let sub_pic_hrd_params_present_flag = false;
|
12509
|
-
if (nal_hrd_parameters_present_flag || vcl_hrd_parameters_present_flag) {
|
12510
|
-
sub_pic_hrd_params_present_flag = eg.readBoolean();
|
12511
|
-
if (sub_pic_hrd_params_present_flag) {
|
12512
|
-
eg.readUByte();
|
12513
|
-
eg.readBits(5);
|
12514
|
-
eg.readBoolean();
|
12515
|
-
eg.readBits(5);
|
12516
|
-
}
|
12517
|
-
eg.readBits(4); // bit_rate_scale
|
12518
|
-
eg.readBits(4); // cpb_size_scale
|
12519
|
-
if (sub_pic_hrd_params_present_flag) {
|
12520
|
-
eg.readBits(4);
|
12521
|
-
}
|
12522
|
-
eg.readBits(5);
|
12523
|
-
eg.readBits(5);
|
12524
|
-
eg.readBits(5);
|
12525
|
-
}
|
12526
|
-
//}
|
12527
|
-
for (let i = 0; i <= max_sub_layers_minus1; i++) {
|
12528
|
-
fps_fixed = eg.readBoolean(); // fixed_pic_rate_general_flag
|
12529
|
-
const fixed_pic_rate_within_cvs_flag = fps_fixed || eg.readBoolean();
|
12530
|
-
let low_delay_hrd_flag = false;
|
12531
|
-
if (fixed_pic_rate_within_cvs_flag) {
|
12532
|
-
eg.readEG();
|
12533
|
-
} else {
|
12534
|
-
low_delay_hrd_flag = eg.readBoolean();
|
12535
|
-
}
|
12536
|
-
const cpb_cnt = low_delay_hrd_flag ? 1 : eg.readUEG() + 1;
|
12537
|
-
if (nal_hrd_parameters_present_flag) {
|
12538
|
-
for (let j = 0; j < cpb_cnt; j++) {
|
12539
|
-
eg.readUEG();
|
12540
|
-
eg.readUEG();
|
12541
|
-
if (sub_pic_hrd_params_present_flag) {
|
12542
|
-
eg.readUEG();
|
12543
|
-
eg.readUEG();
|
12544
|
-
}
|
12545
|
-
eg.skipBits(1);
|
12546
|
-
}
|
12547
|
-
}
|
12548
|
-
if (vcl_hrd_parameters_present_flag) {
|
12549
|
-
for (let j = 0; j < cpb_cnt; j++) {
|
12550
|
-
eg.readUEG();
|
12551
|
-
eg.readUEG();
|
12552
|
-
if (sub_pic_hrd_params_present_flag) {
|
12553
|
-
eg.readUEG();
|
12554
|
-
eg.readUEG();
|
12555
|
-
}
|
12556
|
-
eg.skipBits(1);
|
12557
|
-
}
|
12558
|
-
}
|
12559
|
-
}
|
12560
|
-
}
|
11866
|
+
state = 0;
|
12561
11867
|
}
|
12562
|
-
const bitstream_restriction_flag = eg.readBoolean();
|
12563
|
-
if (bitstream_restriction_flag) {
|
12564
|
-
eg.readBoolean(); // tiles_fixed_structure_flag
|
12565
|
-
eg.readBoolean(); // motion_vectors_over_pic_boundaries_flag
|
12566
|
-
eg.readBoolean(); // restricted_ref_pic_lists_flag
|
12567
|
-
min_spatial_segmentation_idc = eg.readUEG();
|
12568
|
-
}
|
12569
|
-
}
|
12570
|
-
let width = pic_width_in_luma_samples,
|
12571
|
-
height = pic_height_in_luma_samples;
|
12572
|
-
if (conformance_window_flag || default_display_window_flag) {
|
12573
|
-
let chroma_scale_w = 1,
|
12574
|
-
chroma_scale_h = 1;
|
12575
|
-
if (chroma_format_idc === 1) {
|
12576
|
-
// YUV 420
|
12577
|
-
chroma_scale_w = chroma_scale_h = 2;
|
12578
|
-
} else if (chroma_format_idc == 2) {
|
12579
|
-
// YUV 422
|
12580
|
-
chroma_scale_w = 2;
|
12581
|
-
}
|
12582
|
-
width = pic_width_in_luma_samples - chroma_scale_w * pic_right_offset - chroma_scale_w * pic_left_offset;
|
12583
|
-
height = pic_height_in_luma_samples - chroma_scale_h * pic_bottom_offset - chroma_scale_h * pic_top_offset;
|
12584
|
-
}
|
12585
|
-
const profile_space_string = general_profile_space ? ['A', 'B', 'C'][general_profile_space] : '';
|
12586
|
-
const profile_compatibility_buf = general_profile_compatibility_flags_1 << 24 | general_profile_compatibility_flags_2 << 16 | general_profile_compatibility_flags_3 << 8 | general_profile_compatibility_flags_4;
|
12587
|
-
let profile_compatibility_rev = 0;
|
12588
|
-
for (let i = 0; i < 32; i++) {
|
12589
|
-
profile_compatibility_rev = (profile_compatibility_rev | (profile_compatibility_buf >> i & 1) << 31 - i) >>> 0; // reverse bit position (and cast as UInt32)
|
12590
|
-
}
|
12591
|
-
let profile_compatibility_flags_string = profile_compatibility_rev.toString(16);
|
12592
|
-
if (general_profile_idc === 1 && profile_compatibility_flags_string === '2') {
|
12593
|
-
profile_compatibility_flags_string = '6';
|
12594
|
-
}
|
12595
|
-
const tier_flag_string = general_tier_flag ? 'H' : 'L';
|
12596
|
-
return {
|
12597
|
-
codecString: `hvc1.${profile_space_string}${general_profile_idc}.${profile_compatibility_flags_string}.${tier_flag_string}${general_level_idc}.B0`,
|
12598
|
-
params: {
|
12599
|
-
general_tier_flag,
|
12600
|
-
general_profile_idc,
|
12601
|
-
general_profile_space,
|
12602
|
-
general_profile_compatibility_flags: [general_profile_compatibility_flags_1, general_profile_compatibility_flags_2, general_profile_compatibility_flags_3, general_profile_compatibility_flags_4],
|
12603
|
-
general_constraint_indicator_flags: [general_constraint_indicator_flags_1, general_constraint_indicator_flags_2, general_constraint_indicator_flags_3, general_constraint_indicator_flags_4, general_constraint_indicator_flags_5, general_constraint_indicator_flags_6],
|
12604
|
-
general_level_idc,
|
12605
|
-
bit_depth: bit_depth_luma_minus8 + 8,
|
12606
|
-
bit_depth_luma_minus8,
|
12607
|
-
bit_depth_chroma_minus8,
|
12608
|
-
min_spatial_segmentation_idc,
|
12609
|
-
chroma_format_idc: chroma_format_idc,
|
12610
|
-
frame_rate: {
|
12611
|
-
fixed: fps_fixed,
|
12612
|
-
fps: fps_num / fps_den
|
12613
|
-
}
|
12614
|
-
},
|
12615
|
-
width,
|
12616
|
-
height,
|
12617
|
-
pixelRatio: [sar_width, sar_height]
|
12618
|
-
};
|
12619
|
-
}
|
12620
|
-
readPPS(pps) {
|
12621
|
-
const eg = new ExpGolomb(this.ebsp2rbsp(pps));
|
12622
|
-
eg.readUByte();
|
12623
|
-
eg.readUByte();
|
12624
|
-
eg.skipUEG(); // pic_parameter_set_id
|
12625
|
-
eg.skipUEG(); // seq_parameter_set_id
|
12626
|
-
eg.skipBits(2); // dependent_slice_segments_enabled_flag, output_flag_present_flag
|
12627
|
-
eg.skipBits(3); // num_extra_slice_header_bits
|
12628
|
-
eg.skipBits(2); // sign_data_hiding_enabled_flag, cabac_init_present_flag
|
12629
|
-
eg.skipUEG();
|
12630
|
-
eg.skipUEG();
|
12631
|
-
eg.skipEG(); // init_qp_minus26
|
12632
|
-
eg.skipBits(2); // constrained_intra_pred_flag, transform_skip_enabled_flag
|
12633
|
-
const cu_qp_delta_enabled_flag = eg.readBoolean();
|
12634
|
-
if (cu_qp_delta_enabled_flag) {
|
12635
|
-
eg.skipUEG();
|
12636
|
-
}
|
12637
|
-
eg.skipEG(); // cb_qp_offset
|
12638
|
-
eg.skipEG(); // cr_qp_offset
|
12639
|
-
eg.skipBits(4); // pps_slice_chroma_qp_offsets_present_flag, weighted_pred_flag, weighted_bipred_flag, transquant_bypass_enabled_flag
|
12640
|
-
const tiles_enabled_flag = eg.readBoolean();
|
12641
|
-
const entropy_coding_sync_enabled_flag = eg.readBoolean();
|
12642
|
-
let parallelismType = 1; // slice-based parallel decoding
|
12643
|
-
if (entropy_coding_sync_enabled_flag && tiles_enabled_flag) {
|
12644
|
-
parallelismType = 0; // mixed-type parallel decoding
|
12645
|
-
} else if (entropy_coding_sync_enabled_flag) {
|
12646
|
-
parallelismType = 3; // wavefront-based parallel decoding
|
12647
|
-
} else if (tiles_enabled_flag) {
|
12648
|
-
parallelismType = 2; // tile-based parallel decoding
|
12649
11868
|
}
|
12650
|
-
|
12651
|
-
|
12652
|
-
|
12653
|
-
|
12654
|
-
|
12655
|
-
|
12656
|
-
|
11869
|
+
if (lastUnitStart >= 0 && state >= 0) {
|
11870
|
+
const unit = {
|
11871
|
+
data: array.subarray(lastUnitStart, len),
|
11872
|
+
type: lastUnitType,
|
11873
|
+
state: state
|
11874
|
+
};
|
11875
|
+
units.push(unit);
|
11876
|
+
// logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
|
11877
|
+
}
|
11878
|
+
// no NALu found
|
11879
|
+
if (units.length === 0) {
|
11880
|
+
// append pes.data to previous NAL unit
|
11881
|
+
const lastUnit = this.getLastNalUnit(track.samples);
|
11882
|
+
if (lastUnit) {
|
11883
|
+
lastUnit.data = appendUint8Array(lastUnit.data, array);
|
11884
|
+
}
|
11885
|
+
}
|
11886
|
+
track.naluState = state;
|
11887
|
+
return units;
|
12657
11888
|
}
|
12658
11889
|
}
|
12659
11890
|
|
@@ -12671,7 +11902,7 @@ class SampleAesDecrypter {
|
|
12671
11902
|
});
|
12672
11903
|
}
|
12673
11904
|
decryptBuffer(encryptedData) {
|
12674
|
-
return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer
|
11905
|
+
return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer);
|
12675
11906
|
}
|
12676
11907
|
|
12677
11908
|
// AAC - encrypt all full 16 bytes blocks starting from offset 16
|
@@ -12785,7 +12016,7 @@ class TSDemuxer {
|
|
12785
12016
|
this.observer = observer;
|
12786
12017
|
this.config = config;
|
12787
12018
|
this.typeSupported = typeSupported;
|
12788
|
-
this.videoParser =
|
12019
|
+
this.videoParser = new AvcVideoParser();
|
12789
12020
|
}
|
12790
12021
|
static probe(data) {
|
12791
12022
|
const syncOffset = TSDemuxer.syncOffset(data);
|
@@ -12950,21 +12181,7 @@ class TSDemuxer {
|
|
12950
12181
|
case videoPid:
|
12951
12182
|
if (stt) {
|
12952
12183
|
if (videoData && (pes = parsePES(videoData))) {
|
12953
|
-
|
12954
|
-
switch (videoTrack.segmentCodec) {
|
12955
|
-
case 'avc':
|
12956
|
-
this.videoParser = new AvcVideoParser();
|
12957
|
-
break;
|
12958
|
-
case 'hevc':
|
12959
|
-
{
|
12960
|
-
this.videoParser = new HevcVideoParser();
|
12961
|
-
}
|
12962
|
-
break;
|
12963
|
-
}
|
12964
|
-
}
|
12965
|
-
if (this.videoParser !== null) {
|
12966
|
-
this.videoParser.parsePES(videoTrack, textTrack, pes, false, this._duration);
|
12967
|
-
}
|
12184
|
+
this.videoParser.parseAVCPES(videoTrack, textTrack, pes, false, this._duration);
|
12968
12185
|
}
|
12969
12186
|
videoData = {
|
12970
12187
|
data: [],
|
@@ -13131,22 +12348,8 @@ class TSDemuxer {
|
|
13131
12348
|
// try to parse last PES packets
|
13132
12349
|
let pes;
|
13133
12350
|
if (videoData && (pes = parsePES(videoData))) {
|
13134
|
-
|
13135
|
-
|
13136
|
-
case 'avc':
|
13137
|
-
this.videoParser = new AvcVideoParser();
|
13138
|
-
break;
|
13139
|
-
case 'hevc':
|
13140
|
-
{
|
13141
|
-
this.videoParser = new HevcVideoParser();
|
13142
|
-
}
|
13143
|
-
break;
|
13144
|
-
}
|
13145
|
-
}
|
13146
|
-
if (this.videoParser !== null) {
|
13147
|
-
this.videoParser.parsePES(videoTrack, textTrack, pes, true, this._duration);
|
13148
|
-
videoTrack.pesData = null;
|
13149
|
-
}
|
12351
|
+
this.videoParser.parseAVCPES(videoTrack, textTrack, pes, true, this._duration);
|
12352
|
+
videoTrack.pesData = null;
|
13150
12353
|
} else {
|
13151
12354
|
// either avcData null or PES truncated, keep it for next frag parsing
|
13152
12355
|
videoTrack.pesData = videoData;
|
@@ -13479,14 +12682,7 @@ function parsePMT(data, offset, typeSupported, isSampleAes) {
|
|
13479
12682
|
logger.warn('Unsupported EC-3 in M2TS found');
|
13480
12683
|
break;
|
13481
12684
|
case 0x24:
|
13482
|
-
|
13483
|
-
{
|
13484
|
-
if (result.videoPid === -1) {
|
13485
|
-
result.videoPid = pid;
|
13486
|
-
result.segmentVideoCodec = 'hevc';
|
13487
|
-
logger.log('HEVC in M2TS found');
|
13488
|
-
}
|
13489
|
-
}
|
12685
|
+
logger.warn('Unsupported HEVC in M2TS found');
|
13490
12686
|
break;
|
13491
12687
|
}
|
13492
12688
|
// move to the next table entry
|
@@ -13709,8 +12905,6 @@ class MP4 {
|
|
13709
12905
|
avc1: [],
|
13710
12906
|
// codingname
|
13711
12907
|
avcC: [],
|
13712
|
-
hvc1: [],
|
13713
|
-
hvcC: [],
|
13714
12908
|
btrt: [],
|
13715
12909
|
dinf: [],
|
13716
12910
|
dref: [],
|
@@ -14135,10 +13329,8 @@ class MP4 {
|
|
14135
13329
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.ac3(track));
|
14136
13330
|
}
|
14137
13331
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track));
|
14138
|
-
} else if (track.segmentCodec === 'avc') {
|
14139
|
-
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
|
14140
13332
|
} else {
|
14141
|
-
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.
|
13333
|
+
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
|
14142
13334
|
}
|
14143
13335
|
}
|
14144
13336
|
static tkhd(track) {
|
@@ -14276,84 +13468,6 @@ class MP4 {
|
|
14276
13468
|
const result = appendUint8Array(MP4.FTYP, movie);
|
14277
13469
|
return result;
|
14278
13470
|
}
|
14279
|
-
static hvc1(track) {
|
14280
|
-
const ps = track.params;
|
14281
|
-
const units = [track.vps, track.sps, track.pps];
|
14282
|
-
const NALuLengthSize = 4;
|
14283
|
-
const config = new Uint8Array([0x01, ps.general_profile_space << 6 | (ps.general_tier_flag ? 32 : 0) | ps.general_profile_idc, ps.general_profile_compatibility_flags[0], ps.general_profile_compatibility_flags[1], ps.general_profile_compatibility_flags[2], ps.general_profile_compatibility_flags[3], ps.general_constraint_indicator_flags[0], ps.general_constraint_indicator_flags[1], ps.general_constraint_indicator_flags[2], ps.general_constraint_indicator_flags[3], ps.general_constraint_indicator_flags[4], ps.general_constraint_indicator_flags[5], ps.general_level_idc, 240 | ps.min_spatial_segmentation_idc >> 8, 255 & ps.min_spatial_segmentation_idc, 252 | ps.parallelismType, 252 | ps.chroma_format_idc, 248 | ps.bit_depth_luma_minus8, 248 | ps.bit_depth_chroma_minus8, 0x00, parseInt(ps.frame_rate.fps), NALuLengthSize - 1 | ps.temporal_id_nested << 2 | ps.num_temporal_layers << 3 | (ps.frame_rate.fixed ? 64 : 0), units.length]);
|
14284
|
-
|
14285
|
-
// compute hvcC size in bytes
|
14286
|
-
let length = config.length;
|
14287
|
-
for (let i = 0; i < units.length; i += 1) {
|
14288
|
-
length += 3;
|
14289
|
-
for (let j = 0; j < units[i].length; j += 1) {
|
14290
|
-
length += 2 + units[i][j].length;
|
14291
|
-
}
|
14292
|
-
}
|
14293
|
-
const hvcC = new Uint8Array(length);
|
14294
|
-
hvcC.set(config, 0);
|
14295
|
-
length = config.length;
|
14296
|
-
// append parameter set units: one vps, one or more sps and pps
|
14297
|
-
const iMax = units.length - 1;
|
14298
|
-
for (let i = 0; i < units.length; i += 1) {
|
14299
|
-
hvcC.set(new Uint8Array([32 + i | (i === iMax ? 128 : 0), 0x00, units[i].length]), length);
|
14300
|
-
length += 3;
|
14301
|
-
for (let j = 0; j < units[i].length; j += 1) {
|
14302
|
-
hvcC.set(new Uint8Array([units[i][j].length >> 8, units[i][j].length & 255]), length);
|
14303
|
-
length += 2;
|
14304
|
-
hvcC.set(units[i][j], length);
|
14305
|
-
length += units[i][j].length;
|
14306
|
-
}
|
14307
|
-
}
|
14308
|
-
const hvcc = MP4.box(MP4.types.hvcC, hvcC);
|
14309
|
-
const width = track.width;
|
14310
|
-
const height = track.height;
|
14311
|
-
const hSpacing = track.pixelRatio[0];
|
14312
|
-
const vSpacing = track.pixelRatio[1];
|
14313
|
-
return MP4.box(MP4.types.hvc1, new Uint8Array([0x00, 0x00, 0x00,
|
14314
|
-
// reserved
|
14315
|
-
0x00, 0x00, 0x00,
|
14316
|
-
// reserved
|
14317
|
-
0x00, 0x01,
|
14318
|
-
// data_reference_index
|
14319
|
-
0x00, 0x00,
|
14320
|
-
// pre_defined
|
14321
|
-
0x00, 0x00,
|
14322
|
-
// reserved
|
14323
|
-
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
14324
|
-
// pre_defined
|
14325
|
-
width >> 8 & 0xff, width & 0xff,
|
14326
|
-
// width
|
14327
|
-
height >> 8 & 0xff, height & 0xff,
|
14328
|
-
// height
|
14329
|
-
0x00, 0x48, 0x00, 0x00,
|
14330
|
-
// horizresolution
|
14331
|
-
0x00, 0x48, 0x00, 0x00,
|
14332
|
-
// vertresolution
|
14333
|
-
0x00, 0x00, 0x00, 0x00,
|
14334
|
-
// reserved
|
14335
|
-
0x00, 0x01,
|
14336
|
-
// frame_count
|
14337
|
-
0x12, 0x64, 0x61, 0x69, 0x6c,
|
14338
|
-
// dailymotion/hls.js
|
14339
|
-
0x79, 0x6d, 0x6f, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x68, 0x6c, 0x73, 0x2e, 0x6a, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
14340
|
-
// compressorname
|
14341
|
-
0x00, 0x18,
|
14342
|
-
// depth = 24
|
14343
|
-
0x11, 0x11]),
|
14344
|
-
// pre_defined = -1
|
14345
|
-
hvcc, MP4.box(MP4.types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80,
|
14346
|
-
// bufferSizeDB
|
14347
|
-
0x00, 0x2d, 0xc6, 0xc0,
|
14348
|
-
// maxBitrate
|
14349
|
-
0x00, 0x2d, 0xc6, 0xc0])),
|
14350
|
-
// avgBitrate
|
14351
|
-
MP4.box(MP4.types.pasp, new Uint8Array([hSpacing >> 24,
|
14352
|
-
// hSpacing
|
14353
|
-
hSpacing >> 16 & 0xff, hSpacing >> 8 & 0xff, hSpacing & 0xff, vSpacing >> 24,
|
14354
|
-
// vSpacing
|
14355
|
-
vSpacing >> 16 & 0xff, vSpacing >> 8 & 0xff, vSpacing & 0xff])));
|
14356
|
-
}
|
14357
13471
|
}
|
14358
13472
|
MP4.types = void 0;
|
14359
13473
|
MP4.HDLR_TYPES = void 0;
|
@@ -14735,9 +13849,9 @@ class MP4Remuxer {
|
|
14735
13849
|
const foundOverlap = delta < -1;
|
14736
13850
|
if (foundHole || foundOverlap) {
|
14737
13851
|
if (foundHole) {
|
14738
|
-
logger.warn(
|
13852
|
+
logger.warn(`AVC: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
|
14739
13853
|
} else {
|
14740
|
-
logger.warn(
|
13854
|
+
logger.warn(`AVC: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
|
14741
13855
|
}
|
14742
13856
|
if (!foundOverlap || nextAvcDts >= inputSamples[0].pts || chromeVersion) {
|
14743
13857
|
firstDTS = nextAvcDts;
|
@@ -14746,24 +13860,12 @@ class MP4Remuxer {
|
|
14746
13860
|
inputSamples[0].dts = firstDTS;
|
14747
13861
|
inputSamples[0].pts = firstPTS;
|
14748
13862
|
} else {
|
14749
|
-
let isPTSOrderRetained = true;
|
14750
13863
|
for (let i = 0; i < inputSamples.length; i++) {
|
14751
|
-
if (inputSamples[i].dts > firstPTS
|
13864
|
+
if (inputSamples[i].dts > firstPTS) {
|
14752
13865
|
break;
|
14753
13866
|
}
|
14754
|
-
const prevPTS = inputSamples[i].pts;
|
14755
13867
|
inputSamples[i].dts -= delta;
|
14756
13868
|
inputSamples[i].pts -= delta;
|
14757
|
-
|
14758
|
-
// check to see if this sample's PTS order has changed
|
14759
|
-
// relative to the next one
|
14760
|
-
if (i < inputSamples.length - 1) {
|
14761
|
-
const nextSamplePTS = inputSamples[i + 1].pts;
|
14762
|
-
const currentSamplePTS = inputSamples[i].pts;
|
14763
|
-
const currentOrder = nextSamplePTS <= currentSamplePTS;
|
14764
|
-
const prevOrder = nextSamplePTS <= prevPTS;
|
14765
|
-
isPTSOrderRetained = currentOrder == prevOrder;
|
14766
|
-
}
|
14767
13869
|
}
|
14768
13870
|
}
|
14769
13871
|
logger.log(`Video: Initial PTS/DTS adjusted: ${toMsFromMpegTsClock(firstPTS, true)}/${toMsFromMpegTsClock(firstDTS, true)}, delta: ${toMsFromMpegTsClock(delta, true)} ms`);
|
@@ -14911,7 +14013,7 @@ class MP4Remuxer {
|
|
14911
14013
|
}
|
14912
14014
|
}
|
14913
14015
|
}
|
14914
|
-
// next AVC
|
14016
|
+
// next AVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
|
14915
14017
|
mp4SampleDuration = stretchedLastFrame || !mp4SampleDuration ? averageSampleDuration : mp4SampleDuration;
|
14916
14018
|
this.nextAvcDts = nextAvcDts = lastDTS + mp4SampleDuration;
|
14917
14019
|
this.videoSampleDuration = mp4SampleDuration;
|
@@ -15044,7 +14146,7 @@ class MP4Remuxer {
|
|
15044
14146
|
logger.warn(`[mp4-remuxer]: Injecting ${missing} audio frame @ ${(nextPts / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`);
|
15045
14147
|
for (let j = 0; j < missing; j++) {
|
15046
14148
|
const newStamp = Math.max(nextPts, 0);
|
15047
|
-
let fillFrame = AAC.getSilentFrame(track.
|
14149
|
+
let fillFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
|
15048
14150
|
if (!fillFrame) {
|
15049
14151
|
logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.');
|
15050
14152
|
fillFrame = sample.unit.subarray();
|
@@ -15172,7 +14274,7 @@ class MP4Remuxer {
|
|
15172
14274
|
// samples count of this segment's duration
|
15173
14275
|
const nbSamples = Math.ceil((endDTS - startDTS) / frameDuration);
|
15174
14276
|
// silent frame
|
15175
|
-
const silentFrame = AAC.getSilentFrame(track.
|
14277
|
+
const silentFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
|
15176
14278
|
logger.warn('[mp4-remuxer]: remux empty Audio');
|
15177
14279
|
// Can't remux if we can't generate a silent frame...
|
15178
14280
|
if (!silentFrame) {
|
@@ -15566,15 +14668,13 @@ class Transmuxer {
|
|
15566
14668
|
initSegmentData
|
15567
14669
|
} = transmuxConfig;
|
15568
14670
|
const keyData = getEncryptionType(uintData, decryptdata);
|
15569
|
-
if (keyData &&
|
14671
|
+
if (keyData && keyData.method === 'AES-128') {
|
15570
14672
|
const decrypter = this.getDecrypter();
|
15571
|
-
const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
|
15572
|
-
|
15573
14673
|
// Software decryption is synchronous; webCrypto is not
|
15574
14674
|
if (decrypter.isSync()) {
|
15575
14675
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
15576
14676
|
// data is handled in the flush() call
|
15577
|
-
let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer
|
14677
|
+
let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer);
|
15578
14678
|
// For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
|
15579
14679
|
const loadingParts = chunkMeta.part > -1;
|
15580
14680
|
if (loadingParts) {
|
@@ -15586,7 +14686,7 @@ class Transmuxer {
|
|
15586
14686
|
}
|
15587
14687
|
uintData = new Uint8Array(decryptedData);
|
15588
14688
|
} else {
|
15589
|
-
this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer
|
14689
|
+
this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer).then(decryptedData => {
|
15590
14690
|
// Calling push here is important; if flush() is called while this is still resolving, this ensures that
|
15591
14691
|
// the decrypted data has been transmuxed
|
15592
14692
|
const result = this.push(decryptedData, null, chunkMeta);
|
@@ -16240,7 +15340,14 @@ class TransmuxerInterface {
|
|
16240
15340
|
this.observer = new EventEmitter();
|
16241
15341
|
this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
|
16242
15342
|
this.observer.on(Events.ERROR, forwardMessage);
|
16243
|
-
const
|
15343
|
+
const MediaSource = getMediaSource(config.preferManagedMediaSource) || {
|
15344
|
+
isTypeSupported: () => false
|
15345
|
+
};
|
15346
|
+
const m2tsTypeSupported = {
|
15347
|
+
mpeg: MediaSource.isTypeSupported('audio/mpeg'),
|
15348
|
+
mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
|
15349
|
+
ac3: MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
|
15350
|
+
};
|
16244
15351
|
|
16245
15352
|
// navigator.vendor is not always available in Web Worker
|
16246
15353
|
// refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
|
@@ -16528,7 +15635,7 @@ const TICK_INTERVAL$2 = 100; // how often to tick in ms
|
|
16528
15635
|
|
16529
15636
|
class AudioStreamController extends BaseStreamController {
|
16530
15637
|
constructor(hls, fragmentTracker, keyLoader) {
|
16531
|
-
super(hls, fragmentTracker, keyLoader, 'audio-stream-controller', PlaylistLevelType.AUDIO);
|
15638
|
+
super(hls, fragmentTracker, keyLoader, '[audio-stream-controller]', PlaylistLevelType.AUDIO);
|
16532
15639
|
this.videoBuffer = null;
|
16533
15640
|
this.videoTrackCC = -1;
|
16534
15641
|
this.waitingVideoCC = -1;
|
@@ -16540,24 +15647,27 @@ class AudioStreamController extends BaseStreamController {
|
|
16540
15647
|
this.flushing = false;
|
16541
15648
|
this.bufferFlushed = false;
|
16542
15649
|
this.cachedTrackLoadedData = null;
|
16543
|
-
this.
|
15650
|
+
this._registerListeners();
|
16544
15651
|
}
|
16545
15652
|
onHandlerDestroying() {
|
16546
|
-
this.
|
15653
|
+
this._unregisterListeners();
|
16547
15654
|
super.onHandlerDestroying();
|
16548
15655
|
this.mainDetails = null;
|
16549
15656
|
this.bufferedTrack = null;
|
16550
15657
|
this.switchingTrack = null;
|
16551
15658
|
}
|
16552
|
-
|
16553
|
-
super.registerListeners();
|
15659
|
+
_registerListeners() {
|
16554
15660
|
const {
|
16555
15661
|
hls
|
16556
15662
|
} = this;
|
15663
|
+
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
15664
|
+
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
15665
|
+
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
16557
15666
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
16558
15667
|
hls.on(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
|
16559
15668
|
hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
16560
15669
|
hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
|
15670
|
+
hls.on(Events.ERROR, this.onError, this);
|
16561
15671
|
hls.on(Events.BUFFER_RESET, this.onBufferReset, this);
|
16562
15672
|
hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
16563
15673
|
hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
@@ -16565,18 +15675,18 @@ class AudioStreamController extends BaseStreamController {
|
|
16565
15675
|
hls.on(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);
|
16566
15676
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
16567
15677
|
}
|
16568
|
-
|
15678
|
+
_unregisterListeners() {
|
16569
15679
|
const {
|
16570
15680
|
hls
|
16571
15681
|
} = this;
|
16572
|
-
|
16573
|
-
|
16574
|
-
|
16575
|
-
super.unregisterListeners();
|
15682
|
+
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
15683
|
+
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
15684
|
+
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
16576
15685
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
16577
15686
|
hls.off(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
|
16578
15687
|
hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
16579
15688
|
hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
|
15689
|
+
hls.off(Events.ERROR, this.onError, this);
|
16580
15690
|
hls.off(Events.BUFFER_RESET, this.onBufferReset, this);
|
16581
15691
|
hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
16582
15692
|
hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
@@ -16745,13 +15855,12 @@ class AudioStreamController extends BaseStreamController {
|
|
16745
15855
|
} = this;
|
16746
15856
|
const config = hls.config;
|
16747
15857
|
|
16748
|
-
// 1. if
|
16749
|
-
// 2. if video not attached AND
|
15858
|
+
// 1. if video not attached AND
|
16750
15859
|
// start fragment already requested OR start frag prefetch not enabled
|
16751
|
-
//
|
15860
|
+
// 2. if tracks or track not loaded and selected
|
16752
15861
|
// then exit loop
|
16753
15862
|
// => if media not attached but start frag prefetch is enabled and start frag not requested yet, we will not exit loop
|
16754
|
-
if (!
|
15863
|
+
if (!media && (this.startFragRequested || !config.startFragPrefetch) || !(levels != null && levels[trackId])) {
|
16755
15864
|
return;
|
16756
15865
|
}
|
16757
15866
|
const levelInfo = levels[trackId];
|
@@ -17309,7 +16418,7 @@ class AudioStreamController extends BaseStreamController {
|
|
17309
16418
|
|
17310
16419
|
class AudioTrackController extends BasePlaylistController {
|
17311
16420
|
constructor(hls) {
|
17312
|
-
super(hls, 'audio-track-controller');
|
16421
|
+
super(hls, '[audio-track-controller]');
|
17313
16422
|
this.tracks = [];
|
17314
16423
|
this.groupIds = null;
|
17315
16424
|
this.tracksInGroup = [];
|
@@ -17628,23 +16737,26 @@ const TICK_INTERVAL$1 = 500; // how often to tick in ms
|
|
17628
16737
|
|
17629
16738
|
class SubtitleStreamController extends BaseStreamController {
|
17630
16739
|
constructor(hls, fragmentTracker, keyLoader) {
|
17631
|
-
super(hls, fragmentTracker, keyLoader, 'subtitle-stream-controller', PlaylistLevelType.SUBTITLE);
|
16740
|
+
super(hls, fragmentTracker, keyLoader, '[subtitle-stream-controller]', PlaylistLevelType.SUBTITLE);
|
17632
16741
|
this.currentTrackId = -1;
|
17633
16742
|
this.tracksBuffered = [];
|
17634
16743
|
this.mainDetails = null;
|
17635
|
-
this.
|
16744
|
+
this._registerListeners();
|
17636
16745
|
}
|
17637
16746
|
onHandlerDestroying() {
|
17638
|
-
this.
|
16747
|
+
this._unregisterListeners();
|
17639
16748
|
super.onHandlerDestroying();
|
17640
16749
|
this.mainDetails = null;
|
17641
16750
|
}
|
17642
|
-
|
17643
|
-
super.registerListeners();
|
16751
|
+
_registerListeners() {
|
17644
16752
|
const {
|
17645
16753
|
hls
|
17646
16754
|
} = this;
|
16755
|
+
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
16756
|
+
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
16757
|
+
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
17647
16758
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
16759
|
+
hls.on(Events.ERROR, this.onError, this);
|
17648
16760
|
hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
|
17649
16761
|
hls.on(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
|
17650
16762
|
hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
|
@@ -17652,12 +16764,15 @@ class SubtitleStreamController extends BaseStreamController {
|
|
17652
16764
|
hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
17653
16765
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
17654
16766
|
}
|
17655
|
-
|
17656
|
-
super.unregisterListeners();
|
16767
|
+
_unregisterListeners() {
|
17657
16768
|
const {
|
17658
16769
|
hls
|
17659
16770
|
} = this;
|
16771
|
+
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
16772
|
+
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
16773
|
+
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
17660
16774
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
16775
|
+
hls.off(Events.ERROR, this.onError, this);
|
17661
16776
|
hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
|
17662
16777
|
hls.off(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
|
17663
16778
|
hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
|
@@ -17884,10 +16999,10 @@ class SubtitleStreamController extends BaseStreamController {
|
|
17884
16999
|
return;
|
17885
17000
|
}
|
17886
17001
|
// check to see if the payload needs to be decrypted
|
17887
|
-
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv &&
|
17002
|
+
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
|
17888
17003
|
const startTime = performance.now();
|
17889
17004
|
// decrypt the subtitles
|
17890
|
-
this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer
|
17005
|
+
this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
|
17891
17006
|
hls.trigger(Events.ERROR, {
|
17892
17007
|
type: ErrorTypes.MEDIA_ERROR,
|
17893
17008
|
details: ErrorDetails.FRAG_DECRYPT_ERROR,
|
@@ -18021,7 +17136,7 @@ class BufferableInstance {
|
|
18021
17136
|
|
18022
17137
|
class SubtitleTrackController extends BasePlaylistController {
|
18023
17138
|
constructor(hls) {
|
18024
|
-
super(hls, 'subtitle-track-controller');
|
17139
|
+
super(hls, '[subtitle-track-controller]');
|
18025
17140
|
this.media = null;
|
18026
17141
|
this.tracks = [];
|
18027
17142
|
this.groupIds = null;
|
@@ -18030,10 +17145,10 @@ class SubtitleTrackController extends BasePlaylistController {
|
|
18030
17145
|
this.currentTrack = null;
|
18031
17146
|
this.selectDefaultTrack = true;
|
18032
17147
|
this.queuedDefaultTrack = -1;
|
17148
|
+
this.asyncPollTrackChange = () => this.pollTrackChange(0);
|
18033
17149
|
this.useTextTrackPolling = false;
|
18034
17150
|
this.subtitlePollingInterval = -1;
|
18035
17151
|
this._subtitleDisplay = true;
|
18036
|
-
this.asyncPollTrackChange = () => this.pollTrackChange(0);
|
18037
17152
|
this.onTextTracksChanged = () => {
|
18038
17153
|
if (!this.useTextTrackPolling) {
|
18039
17154
|
self.clearInterval(this.subtitlePollingInterval);
|
@@ -18067,7 +17182,6 @@ class SubtitleTrackController extends BasePlaylistController {
|
|
18067
17182
|
this.tracks.length = 0;
|
18068
17183
|
this.tracksInGroup.length = 0;
|
18069
17184
|
this.currentTrack = null;
|
18070
|
-
// @ts-ignore
|
18071
17185
|
this.onTextTracksChanged = this.asyncPollTrackChange = null;
|
18072
17186
|
super.destroy();
|
18073
17187
|
}
|
@@ -18528,9 +17642,8 @@ class BufferOperationQueue {
|
|
18528
17642
|
}
|
18529
17643
|
|
18530
17644
|
const VIDEO_CODEC_PROFILE_REPLACE = /(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\.[^.,]+)+/;
|
18531
|
-
class BufferController
|
17645
|
+
class BufferController {
|
18532
17646
|
constructor(hls) {
|
18533
|
-
super('buffer-controller', hls.logger);
|
18534
17647
|
// The level details used to determine duration, target-duration and live
|
18535
17648
|
this.details = null;
|
18536
17649
|
// cache the self generated object url to detect hijack of video tag
|
@@ -18560,6 +17673,9 @@ class BufferController extends Logger {
|
|
18560
17673
|
this.tracks = {};
|
18561
17674
|
this.pendingTracks = {};
|
18562
17675
|
this.sourceBuffer = void 0;
|
17676
|
+
this.log = void 0;
|
17677
|
+
this.warn = void 0;
|
17678
|
+
this.error = void 0;
|
18563
17679
|
this._onEndStreaming = event => {
|
18564
17680
|
if (!this.hls) {
|
18565
17681
|
return;
|
@@ -18605,11 +17721,15 @@ class BufferController extends Logger {
|
|
18605
17721
|
_objectUrl
|
18606
17722
|
} = this;
|
18607
17723
|
if (mediaSrc !== _objectUrl) {
|
18608
|
-
|
17724
|
+
logger.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
|
18609
17725
|
}
|
18610
17726
|
};
|
18611
17727
|
this.hls = hls;
|
17728
|
+
const logPrefix = '[buffer-controller]';
|
18612
17729
|
this.appendSource = hls.config.preferManagedMediaSource;
|
17730
|
+
this.log = logger.log.bind(logger, logPrefix);
|
17731
|
+
this.warn = logger.warn.bind(logger, logPrefix);
|
17732
|
+
this.error = logger.error.bind(logger, logPrefix);
|
18613
17733
|
this._initSourceBuffer();
|
18614
17734
|
this.registerListeners();
|
18615
17735
|
}
|
@@ -18622,12 +17742,6 @@ class BufferController extends Logger {
|
|
18622
17742
|
this.lastMpegAudioChunk = null;
|
18623
17743
|
// @ts-ignore
|
18624
17744
|
this.hls = null;
|
18625
|
-
// @ts-ignore
|
18626
|
-
this._onMediaSourceOpen = this._onMediaSourceClose = null;
|
18627
|
-
// @ts-ignore
|
18628
|
-
this._onMediaSourceEnded = null;
|
18629
|
-
// @ts-ignore
|
18630
|
-
this._onStartStreaming = this._onEndStreaming = null;
|
18631
17745
|
}
|
18632
17746
|
registerListeners() {
|
18633
17747
|
const {
|
@@ -18790,7 +17904,6 @@ class BufferController extends Logger {
|
|
18790
17904
|
this.resetBuffer(type);
|
18791
17905
|
});
|
18792
17906
|
this._initSourceBuffer();
|
18793
|
-
this.hls.resumeBuffering();
|
18794
17907
|
}
|
18795
17908
|
resetBuffer(type) {
|
18796
17909
|
const sb = this.sourceBuffer[type];
|
@@ -21893,12 +21006,14 @@ class TimelineController {
|
|
21893
21006
|
this.cea608Parser1 = this.cea608Parser2 = undefined;
|
21894
21007
|
}
|
21895
21008
|
initCea608Parsers() {
|
21896
|
-
|
21897
|
-
|
21898
|
-
|
21899
|
-
|
21900
|
-
|
21901
|
-
|
21009
|
+
if (this.config.enableCEA708Captions && (!this.cea608Parser1 || !this.cea608Parser2)) {
|
21010
|
+
const channel1 = new OutputFilter(this, 'textTrack1');
|
21011
|
+
const channel2 = new OutputFilter(this, 'textTrack2');
|
21012
|
+
const channel3 = new OutputFilter(this, 'textTrack3');
|
21013
|
+
const channel4 = new OutputFilter(this, 'textTrack4');
|
21014
|
+
this.cea608Parser1 = new Cea608Parser(1, channel1, channel2);
|
21015
|
+
this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
|
21016
|
+
}
|
21902
21017
|
}
|
21903
21018
|
addCues(trackName, startTime, endTime, screen, cueRanges) {
|
21904
21019
|
// skip cues which overlap more than 50% with previously parsed time ranges
|
@@ -22136,7 +21251,7 @@ class TimelineController {
|
|
22136
21251
|
if (inUseTracks != null && inUseTracks.length) {
|
22137
21252
|
const unusedTextTracks = inUseTracks.filter(t => t !== null).map(t => t.label);
|
22138
21253
|
if (unusedTextTracks.length) {
|
22139
|
-
|
21254
|
+
logger.warn(`Media element contains unused subtitle tracks: ${unusedTextTracks.join(', ')}. Replace media element for each source to clear TextTracks and captions menu.`);
|
22140
21255
|
}
|
22141
21256
|
}
|
22142
21257
|
} else if (this.tracks.length) {
|
@@ -22181,23 +21296,26 @@ class TimelineController {
|
|
22181
21296
|
return level == null ? void 0 : level.attrs['CLOSED-CAPTIONS'];
|
22182
21297
|
}
|
22183
21298
|
onFragLoading(event, data) {
|
21299
|
+
this.initCea608Parsers();
|
21300
|
+
const {
|
21301
|
+
cea608Parser1,
|
21302
|
+
cea608Parser2,
|
21303
|
+
lastCc,
|
21304
|
+
lastSn,
|
21305
|
+
lastPartIndex
|
21306
|
+
} = this;
|
21307
|
+
if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
|
21308
|
+
return;
|
21309
|
+
}
|
22184
21310
|
// if this frag isn't contiguous, clear the parser so cues with bad start/end times aren't added to the textTrack
|
22185
|
-
if (
|
21311
|
+
if (data.frag.type === PlaylistLevelType.MAIN) {
|
22186
21312
|
var _data$part$index, _data$part;
|
22187
|
-
const {
|
22188
|
-
cea608Parser1,
|
22189
|
-
cea608Parser2,
|
22190
|
-
lastSn
|
22191
|
-
} = this;
|
22192
|
-
if (!cea608Parser1 || !cea608Parser2) {
|
22193
|
-
return;
|
22194
|
-
}
|
22195
21313
|
const {
|
22196
21314
|
cc,
|
22197
21315
|
sn
|
22198
21316
|
} = data.frag;
|
22199
|
-
const partIndex = (_data$part$index = (_data$part = data.part) == null ? void 0 : _data$part.index) != null ? _data$part$index : -1;
|
22200
|
-
if (!(sn === lastSn + 1 || sn === lastSn && partIndex ===
|
21317
|
+
const partIndex = (_data$part$index = data == null ? void 0 : (_data$part = data.part) == null ? void 0 : _data$part.index) != null ? _data$part$index : -1;
|
21318
|
+
if (!(sn === lastSn + 1 || sn === lastSn && partIndex === lastPartIndex + 1 || cc === lastCc)) {
|
22201
21319
|
cea608Parser1.reset();
|
22202
21320
|
cea608Parser2.reset();
|
22203
21321
|
}
|
@@ -22254,7 +21372,7 @@ class TimelineController {
|
|
22254
21372
|
frag: frag
|
22255
21373
|
});
|
22256
21374
|
}, error => {
|
22257
|
-
|
21375
|
+
logger.log(`Failed to parse IMSC1: ${error}`);
|
22258
21376
|
hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
|
22259
21377
|
success: false,
|
22260
21378
|
frag: frag,
|
@@ -22295,7 +21413,7 @@ class TimelineController {
|
|
22295
21413
|
this._fallbackToIMSC1(frag, payload);
|
22296
21414
|
}
|
22297
21415
|
// Something went wrong while parsing. Trigger event with success false.
|
22298
|
-
|
21416
|
+
logger.log(`Failed to parse VTT cue: ${error}`);
|
22299
21417
|
if (missingInitPTS && maxAvCC > frag.cc) {
|
22300
21418
|
return;
|
22301
21419
|
}
|
@@ -22356,7 +21474,12 @@ class TimelineController {
|
|
22356
21474
|
this.captionsTracks = {};
|
22357
21475
|
}
|
22358
21476
|
onFragParsingUserdata(event, data) {
|
22359
|
-
|
21477
|
+
this.initCea608Parsers();
|
21478
|
+
const {
|
21479
|
+
cea608Parser1,
|
21480
|
+
cea608Parser2
|
21481
|
+
} = this;
|
21482
|
+
if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
|
22360
21483
|
return;
|
22361
21484
|
}
|
22362
21485
|
const {
|
@@ -22371,12 +21494,9 @@ class TimelineController {
|
|
22371
21494
|
for (let i = 0; i < samples.length; i++) {
|
22372
21495
|
const ccBytes = samples[i].bytes;
|
22373
21496
|
if (ccBytes) {
|
22374
|
-
if (!this.cea608Parser1) {
|
22375
|
-
this.initCea608Parsers();
|
22376
|
-
}
|
22377
21497
|
const ccdatas = this.extractCea608Data(ccBytes);
|
22378
|
-
|
22379
|
-
|
21498
|
+
cea608Parser1.addData(samples[i].pts, ccdatas[0]);
|
21499
|
+
cea608Parser2.addData(samples[i].pts, ccdatas[1]);
|
22380
21500
|
}
|
22381
21501
|
}
|
22382
21502
|
}
|
@@ -22572,7 +21692,7 @@ class CapLevelController {
|
|
22572
21692
|
const hls = this.hls;
|
22573
21693
|
const maxLevel = this.getMaxLevel(levels.length - 1);
|
22574
21694
|
if (maxLevel !== this.autoLevelCapping) {
|
22575
|
-
|
21695
|
+
logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
|
22576
21696
|
}
|
22577
21697
|
hls.autoLevelCapping = maxLevel;
|
22578
21698
|
if (hls.autoLevelCapping > this.autoLevelCapping && this.streamController) {
|
@@ -22750,10 +21870,10 @@ class FPSController {
|
|
22750
21870
|
totalDroppedFrames: droppedFrames
|
22751
21871
|
});
|
22752
21872
|
if (droppedFPS > 0) {
|
22753
|
-
//
|
21873
|
+
// logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
|
22754
21874
|
if (currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
|
22755
21875
|
let currentLevel = hls.currentLevel;
|
22756
|
-
|
21876
|
+
logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
|
22757
21877
|
if (currentLevel > 0 && (hls.autoLevelCapping === -1 || hls.autoLevelCapping >= currentLevel)) {
|
22758
21878
|
currentLevel = currentLevel - 1;
|
22759
21879
|
hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, {
|
@@ -22785,119 +21905,31 @@ class FPSController {
|
|
22785
21905
|
}
|
22786
21906
|
}
|
22787
21907
|
|
21908
|
+
const LOGGER_PREFIX = '[eme]';
|
22788
21909
|
/**
|
22789
21910
|
* Controller to deal with encrypted media extensions (EME)
|
22790
|
-
* @see https://developer.mozilla.org/en-US/docs/Web/API/Encrypted_Media_Extensions_API
|
22791
|
-
*
|
22792
|
-
* @class
|
22793
|
-
* @constructor
|
22794
|
-
*/
|
22795
|
-
class EMEController
|
22796
|
-
constructor(hls) {
|
22797
|
-
|
22798
|
-
this.
|
22799
|
-
this.
|
22800
|
-
this.
|
22801
|
-
this.
|
22802
|
-
this.
|
22803
|
-
this.
|
22804
|
-
this.
|
22805
|
-
this.
|
22806
|
-
this.
|
22807
|
-
this.
|
22808
|
-
|
22809
|
-
|
22810
|
-
|
22811
|
-
|
22812
|
-
this.debug(`"${event.type}" event: init data type: "${initDataType}"`);
|
22813
|
-
|
22814
|
-
// Ignore event when initData is null
|
22815
|
-
if (initData === null) {
|
22816
|
-
return;
|
22817
|
-
}
|
22818
|
-
let keyId;
|
22819
|
-
let keySystemDomain;
|
22820
|
-
if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) {
|
22821
|
-
// Match sinf keyId to playlist skd://keyId=
|
22822
|
-
const json = bin2str(new Uint8Array(initData));
|
22823
|
-
try {
|
22824
|
-
const sinf = base64Decode(JSON.parse(json).sinf);
|
22825
|
-
const tenc = parseSinf(new Uint8Array(sinf));
|
22826
|
-
if (!tenc) {
|
22827
|
-
return;
|
22828
|
-
}
|
22829
|
-
keyId = tenc.subarray(8, 24);
|
22830
|
-
keySystemDomain = KeySystems.FAIRPLAY;
|
22831
|
-
} catch (error) {
|
22832
|
-
this.warn('Failed to parse sinf "encrypted" event message initData');
|
22833
|
-
return;
|
22834
|
-
}
|
22835
|
-
} else {
|
22836
|
-
// Support clear-lead key-session creation (otherwise depend on playlist keys)
|
22837
|
-
const psshInfo = parsePssh(initData);
|
22838
|
-
if (psshInfo === null) {
|
22839
|
-
return;
|
22840
|
-
}
|
22841
|
-
if (psshInfo.version === 0 && psshInfo.systemId === KeySystemIds.WIDEVINE && psshInfo.data) {
|
22842
|
-
keyId = psshInfo.data.subarray(8, 24);
|
22843
|
-
}
|
22844
|
-
keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
|
22845
|
-
}
|
22846
|
-
if (!keySystemDomain || !keyId) {
|
22847
|
-
return;
|
22848
|
-
}
|
22849
|
-
const keyIdHex = Hex.hexDump(keyId);
|
22850
|
-
const {
|
22851
|
-
keyIdToKeySessionPromise,
|
22852
|
-
mediaKeySessions
|
22853
|
-
} = this;
|
22854
|
-
let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
|
22855
|
-
for (let i = 0; i < mediaKeySessions.length; i++) {
|
22856
|
-
// Match playlist key
|
22857
|
-
const keyContext = mediaKeySessions[i];
|
22858
|
-
const decryptdata = keyContext.decryptdata;
|
22859
|
-
if (decryptdata.pssh || !decryptdata.keyId) {
|
22860
|
-
continue;
|
22861
|
-
}
|
22862
|
-
const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
|
22863
|
-
if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
|
22864
|
-
keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
|
22865
|
-
delete keyIdToKeySessionPromise[oldKeyIdHex];
|
22866
|
-
decryptdata.pssh = new Uint8Array(initData);
|
22867
|
-
decryptdata.keyId = keyId;
|
22868
|
-
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
|
22869
|
-
return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
|
22870
|
-
});
|
22871
|
-
break;
|
22872
|
-
}
|
22873
|
-
}
|
22874
|
-
if (!keySessionContextPromise) {
|
22875
|
-
// Clear-lead key (not encountered in playlist)
|
22876
|
-
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
|
22877
|
-
keySystem,
|
22878
|
-
mediaKeys
|
22879
|
-
}) => {
|
22880
|
-
var _keySystemToKeySystem;
|
22881
|
-
this.throwIfDestroyed();
|
22882
|
-
const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
|
22883
|
-
decryptdata.pssh = new Uint8Array(initData);
|
22884
|
-
decryptdata.keyId = keyId;
|
22885
|
-
return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
|
22886
|
-
this.throwIfDestroyed();
|
22887
|
-
const keySessionContext = this.createMediaKeySessionContext({
|
22888
|
-
decryptdata,
|
22889
|
-
keySystem,
|
22890
|
-
mediaKeys
|
22891
|
-
});
|
22892
|
-
return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
|
22893
|
-
});
|
22894
|
-
});
|
22895
|
-
}
|
22896
|
-
keySessionContextPromise.catch(error => this.handleError(error));
|
22897
|
-
};
|
22898
|
-
this.onWaitingForKey = event => {
|
22899
|
-
this.log(`"${event.type}" event`);
|
22900
|
-
};
|
21911
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/Encrypted_Media_Extensions_API
|
21912
|
+
*
|
21913
|
+
* @class
|
21914
|
+
* @constructor
|
21915
|
+
*/
|
21916
|
+
class EMEController {
|
21917
|
+
constructor(hls) {
|
21918
|
+
this.hls = void 0;
|
21919
|
+
this.config = void 0;
|
21920
|
+
this.media = null;
|
21921
|
+
this.keyFormatPromise = null;
|
21922
|
+
this.keySystemAccessPromises = {};
|
21923
|
+
this._requestLicenseFailureCount = 0;
|
21924
|
+
this.mediaKeySessions = [];
|
21925
|
+
this.keyIdToKeySessionPromise = {};
|
21926
|
+
this.setMediaKeysQueue = EMEController.CDMCleanupPromise ? [EMEController.CDMCleanupPromise] : [];
|
21927
|
+
this.onMediaEncrypted = this._onMediaEncrypted.bind(this);
|
21928
|
+
this.onWaitingForKey = this._onWaitingForKey.bind(this);
|
21929
|
+
this.debug = logger.debug.bind(logger, LOGGER_PREFIX);
|
21930
|
+
this.log = logger.log.bind(logger, LOGGER_PREFIX);
|
21931
|
+
this.warn = logger.warn.bind(logger, LOGGER_PREFIX);
|
21932
|
+
this.error = logger.error.bind(logger, LOGGER_PREFIX);
|
22901
21933
|
this.hls = hls;
|
22902
21934
|
this.config = hls.config;
|
22903
21935
|
this.registerListeners();
|
@@ -22911,9 +21943,9 @@ class EMEController extends Logger {
|
|
22911
21943
|
config.licenseXhrSetup = config.licenseResponseCallback = undefined;
|
22912
21944
|
config.drmSystems = config.drmSystemOptions = {};
|
22913
21945
|
// @ts-ignore
|
22914
|
-
this.hls = this.
|
21946
|
+
this.hls = this.onMediaEncrypted = this.onWaitingForKey = this.keyIdToKeySessionPromise = null;
|
22915
21947
|
// @ts-ignore
|
22916
|
-
this.
|
21948
|
+
this.config = null;
|
22917
21949
|
}
|
22918
21950
|
registerListeners() {
|
22919
21951
|
this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
@@ -23177,6 +22209,100 @@ class EMEController extends Logger {
|
|
23177
22209
|
}
|
23178
22210
|
return this.attemptKeySystemAccess(keySystemsToAttempt);
|
23179
22211
|
}
|
22212
|
+
_onMediaEncrypted(event) {
|
22213
|
+
const {
|
22214
|
+
initDataType,
|
22215
|
+
initData
|
22216
|
+
} = event;
|
22217
|
+
this.debug(`"${event.type}" event: init data type: "${initDataType}"`);
|
22218
|
+
|
22219
|
+
// Ignore event when initData is null
|
22220
|
+
if (initData === null) {
|
22221
|
+
return;
|
22222
|
+
}
|
22223
|
+
let keyId;
|
22224
|
+
let keySystemDomain;
|
22225
|
+
if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) {
|
22226
|
+
// Match sinf keyId to playlist skd://keyId=
|
22227
|
+
const json = bin2str(new Uint8Array(initData));
|
22228
|
+
try {
|
22229
|
+
const sinf = base64Decode(JSON.parse(json).sinf);
|
22230
|
+
const tenc = parseSinf(new Uint8Array(sinf));
|
22231
|
+
if (!tenc) {
|
22232
|
+
return;
|
22233
|
+
}
|
22234
|
+
keyId = tenc.subarray(8, 24);
|
22235
|
+
keySystemDomain = KeySystems.FAIRPLAY;
|
22236
|
+
} catch (error) {
|
22237
|
+
this.warn('Failed to parse sinf "encrypted" event message initData');
|
22238
|
+
return;
|
22239
|
+
}
|
22240
|
+
} else {
|
22241
|
+
// Support clear-lead key-session creation (otherwise depend on playlist keys)
|
22242
|
+
const psshInfo = parsePssh(initData);
|
22243
|
+
if (psshInfo === null) {
|
22244
|
+
return;
|
22245
|
+
}
|
22246
|
+
if (psshInfo.version === 0 && psshInfo.systemId === KeySystemIds.WIDEVINE && psshInfo.data) {
|
22247
|
+
keyId = psshInfo.data.subarray(8, 24);
|
22248
|
+
}
|
22249
|
+
keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
|
22250
|
+
}
|
22251
|
+
if (!keySystemDomain || !keyId) {
|
22252
|
+
return;
|
22253
|
+
}
|
22254
|
+
const keyIdHex = Hex.hexDump(keyId);
|
22255
|
+
const {
|
22256
|
+
keyIdToKeySessionPromise,
|
22257
|
+
mediaKeySessions
|
22258
|
+
} = this;
|
22259
|
+
let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
|
22260
|
+
for (let i = 0; i < mediaKeySessions.length; i++) {
|
22261
|
+
// Match playlist key
|
22262
|
+
const keyContext = mediaKeySessions[i];
|
22263
|
+
const decryptdata = keyContext.decryptdata;
|
22264
|
+
if (decryptdata.pssh || !decryptdata.keyId) {
|
22265
|
+
continue;
|
22266
|
+
}
|
22267
|
+
const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
|
22268
|
+
if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
|
22269
|
+
keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
|
22270
|
+
delete keyIdToKeySessionPromise[oldKeyIdHex];
|
22271
|
+
decryptdata.pssh = new Uint8Array(initData);
|
22272
|
+
decryptdata.keyId = keyId;
|
22273
|
+
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
|
22274
|
+
return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
|
22275
|
+
});
|
22276
|
+
break;
|
22277
|
+
}
|
22278
|
+
}
|
22279
|
+
if (!keySessionContextPromise) {
|
22280
|
+
// Clear-lead key (not encountered in playlist)
|
22281
|
+
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
|
22282
|
+
keySystem,
|
22283
|
+
mediaKeys
|
22284
|
+
}) => {
|
22285
|
+
var _keySystemToKeySystem;
|
22286
|
+
this.throwIfDestroyed();
|
22287
|
+
const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
|
22288
|
+
decryptdata.pssh = new Uint8Array(initData);
|
22289
|
+
decryptdata.keyId = keyId;
|
22290
|
+
return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
|
22291
|
+
this.throwIfDestroyed();
|
22292
|
+
const keySessionContext = this.createMediaKeySessionContext({
|
22293
|
+
decryptdata,
|
22294
|
+
keySystem,
|
22295
|
+
mediaKeys
|
22296
|
+
});
|
22297
|
+
return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
|
22298
|
+
});
|
22299
|
+
});
|
22300
|
+
}
|
22301
|
+
keySessionContextPromise.catch(error => this.handleError(error));
|
22302
|
+
}
|
22303
|
+
_onWaitingForKey(event) {
|
22304
|
+
this.log(`"${event.type}" event`);
|
22305
|
+
}
|
23180
22306
|
attemptSetMediaKeys(keySystem, mediaKeys) {
|
23181
22307
|
const queue = this.setMediaKeysQueue.slice();
|
23182
22308
|
this.log(`Setting media-keys for "${keySystem}"`);
|
@@ -23769,6 +22895,20 @@ class SfItem {
|
|
23769
22895
|
}
|
23770
22896
|
}
|
23771
22897
|
|
22898
|
+
/**
|
22899
|
+
* A class to represent structured field tokens when `Symbol` is not available.
|
22900
|
+
*
|
22901
|
+
* @group Structured Field
|
22902
|
+
*
|
22903
|
+
* @beta
|
22904
|
+
*/
|
22905
|
+
class SfToken {
|
22906
|
+
constructor(description) {
|
22907
|
+
this.description = void 0;
|
22908
|
+
this.description = description;
|
22909
|
+
}
|
22910
|
+
}
|
22911
|
+
|
23772
22912
|
const DICT = 'Dict';
|
23773
22913
|
|
23774
22914
|
function format(value) {
|
@@ -23792,27 +22932,29 @@ function throwError(action, src, type, cause) {
|
|
23792
22932
|
});
|
23793
22933
|
}
|
23794
22934
|
|
23795
|
-
|
23796
|
-
return throwError('serialize', src, type, cause);
|
23797
|
-
}
|
22935
|
+
const BARE_ITEM = 'Bare Item';
|
23798
22936
|
|
23799
|
-
|
23800
|
-
|
23801
|
-
|
23802
|
-
|
23803
|
-
|
23804
|
-
|
23805
|
-
|
23806
|
-
|
23807
|
-
|
23808
|
-
|
23809
|
-
this.description = description;
|
23810
|
-
}
|
22937
|
+
const BOOLEAN = 'Boolean';
|
22938
|
+
|
22939
|
+
const BYTES = 'Byte Sequence';
|
22940
|
+
|
22941
|
+
const DECIMAL = 'Decimal';
|
22942
|
+
|
22943
|
+
const INTEGER = 'Integer';
|
22944
|
+
|
22945
|
+
function isInvalidInt(value) {
|
22946
|
+
return value < -999999999999999 || 999999999999999 < value;
|
23811
22947
|
}
|
23812
22948
|
|
23813
|
-
const
|
22949
|
+
const STRING_REGEX = /[\x00-\x1f\x7f]+/; // eslint-disable-line no-control-regex
|
23814
22950
|
|
23815
|
-
const
|
22951
|
+
const TOKEN = 'Token';
|
22952
|
+
|
22953
|
+
const KEY = 'Key';
|
22954
|
+
|
22955
|
+
function serializeError(src, type, cause) {
|
22956
|
+
return throwError('serialize', src, type, cause);
|
22957
|
+
}
|
23816
22958
|
|
23817
22959
|
// 4.1.9. Serializing a Boolean
|
23818
22960
|
//
|
@@ -23851,8 +22993,6 @@ function base64encode(binary) {
|
|
23851
22993
|
return btoa(String.fromCharCode(...binary));
|
23852
22994
|
}
|
23853
22995
|
|
23854
|
-
const BYTES = 'Byte Sequence';
|
23855
|
-
|
23856
22996
|
// 4.1.8. Serializing a Byte Sequence
|
23857
22997
|
//
|
23858
22998
|
// Given a Byte Sequence as input_bytes, return an ASCII string suitable
|
@@ -23884,12 +23024,6 @@ function serializeByteSequence(value) {
|
|
23884
23024
|
return `:${base64encode(value)}:`;
|
23885
23025
|
}
|
23886
23026
|
|
23887
|
-
const INTEGER = 'Integer';
|
23888
|
-
|
23889
|
-
function isInvalidInt(value) {
|
23890
|
-
return value < -999999999999999 || 999999999999999 < value;
|
23891
|
-
}
|
23892
|
-
|
23893
23027
|
// 4.1.4. Serializing an Integer
|
23894
23028
|
//
|
23895
23029
|
// Given an Integer as input_integer, return an ASCII string suitable
|
@@ -23955,8 +23089,6 @@ function roundToEven(value, precision) {
|
|
23955
23089
|
}
|
23956
23090
|
}
|
23957
23091
|
|
23958
|
-
const DECIMAL = 'Decimal';
|
23959
|
-
|
23960
23092
|
// 4.1.5. Serializing a Decimal
|
23961
23093
|
//
|
23962
23094
|
// Given a decimal number as input_decimal, return an ASCII string
|
@@ -24002,8 +23134,6 @@ function serializeDecimal(value) {
|
|
24002
23134
|
|
24003
23135
|
const STRING = 'String';
|
24004
23136
|
|
24005
|
-
const STRING_REGEX = /[\x00-\x1f\x7f]+/; // eslint-disable-line no-control-regex
|
24006
|
-
|
24007
23137
|
// 4.1.6. Serializing a String
|
24008
23138
|
//
|
24009
23139
|
// Given a String as input_string, return an ASCII string suitable for
|
@@ -24039,8 +23169,6 @@ function symbolToStr(symbol) {
|
|
24039
23169
|
return symbol.description || symbol.toString().slice(7, -1);
|
24040
23170
|
}
|
24041
23171
|
|
24042
|
-
const TOKEN = 'Token';
|
24043
|
-
|
24044
23172
|
function serializeToken(token) {
|
24045
23173
|
const value = symbolToStr(token);
|
24046
23174
|
if (/^([a-zA-Z*])([!#$%&'*+\-.^_`|~\w:/]*)$/.test(value) === false) {
|
@@ -24108,8 +23236,6 @@ function serializeBareItem(value) {
|
|
24108
23236
|
}
|
24109
23237
|
}
|
24110
23238
|
|
24111
|
-
const KEY = 'Key';
|
24112
|
-
|
24113
23239
|
// 4.1.1.3. Serializing a Key
|
24114
23240
|
//
|
24115
23241
|
// Given a key as input_key, return an ASCII string suitable for use in
|
@@ -24351,6 +23477,36 @@ function urlToRelativePath(url, base) {
|
|
24351
23477
|
return toPath.join('/');
|
24352
23478
|
}
|
24353
23479
|
|
23480
|
+
/**
|
23481
|
+
* Generate a random v4 UUID
|
23482
|
+
*
|
23483
|
+
* @returns A random v4 UUID
|
23484
|
+
*
|
23485
|
+
* @group Utils
|
23486
|
+
*
|
23487
|
+
* @beta
|
23488
|
+
*/
|
23489
|
+
function uuid() {
|
23490
|
+
try {
|
23491
|
+
return crypto.randomUUID();
|
23492
|
+
} catch (error) {
|
23493
|
+
try {
|
23494
|
+
const url = URL.createObjectURL(new Blob());
|
23495
|
+
const uuid = url.toString();
|
23496
|
+
URL.revokeObjectURL(url);
|
23497
|
+
return uuid.slice(uuid.lastIndexOf('/') + 1);
|
23498
|
+
} catch (error) {
|
23499
|
+
let dt = new Date().getTime();
|
23500
|
+
const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
23501
|
+
const r = (dt + Math.random() * 16) % 16 | 0;
|
23502
|
+
dt = Math.floor(dt / 16);
|
23503
|
+
return (c == 'x' ? r : r & 0x3 | 0x8).toString(16);
|
23504
|
+
});
|
23505
|
+
return uuid;
|
23506
|
+
}
|
23507
|
+
}
|
23508
|
+
}
|
23509
|
+
|
24354
23510
|
const toRounded = value => Math.round(value);
|
24355
23511
|
const toUrlSafe = (value, options) => {
|
24356
23512
|
if (options != null && options.baseUrl) {
|
@@ -24576,36 +23732,6 @@ function appendCmcdQuery(url, cmcd, options) {
|
|
24576
23732
|
return `${url}${separator}${query}`;
|
24577
23733
|
}
|
24578
23734
|
|
24579
|
-
/**
|
24580
|
-
* Generate a random v4 UUID
|
24581
|
-
*
|
24582
|
-
* @returns A random v4 UUID
|
24583
|
-
*
|
24584
|
-
* @group Utils
|
24585
|
-
*
|
24586
|
-
* @beta
|
24587
|
-
*/
|
24588
|
-
function uuid() {
|
24589
|
-
try {
|
24590
|
-
return crypto.randomUUID();
|
24591
|
-
} catch (error) {
|
24592
|
-
try {
|
24593
|
-
const url = URL.createObjectURL(new Blob());
|
24594
|
-
const uuid = url.toString();
|
24595
|
-
URL.revokeObjectURL(url);
|
24596
|
-
return uuid.slice(uuid.lastIndexOf('/') + 1);
|
24597
|
-
} catch (error) {
|
24598
|
-
let dt = new Date().getTime();
|
24599
|
-
const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
24600
|
-
const r = (dt + Math.random() * 16) % 16 | 0;
|
24601
|
-
dt = Math.floor(dt / 16);
|
24602
|
-
return (c == 'x' ? r : r & 0x3 | 0x8).toString(16);
|
24603
|
-
});
|
24604
|
-
return uuid;
|
24605
|
-
}
|
24606
|
-
}
|
24607
|
-
}
|
24608
|
-
|
24609
23735
|
/**
|
24610
23736
|
* Controller to deal with Common Media Client Data (CMCD)
|
24611
23737
|
* @see https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf
|
@@ -24649,7 +23775,7 @@ class CMCDController {
|
|
24649
23775
|
su: !this.initialized
|
24650
23776
|
});
|
24651
23777
|
} catch (error) {
|
24652
|
-
|
23778
|
+
logger.warn('Could not generate manifest CMCD data.', error);
|
24653
23779
|
}
|
24654
23780
|
};
|
24655
23781
|
/**
|
@@ -24669,15 +23795,9 @@ class CMCDController {
|
|
24669
23795
|
data.tb = this.getTopBandwidth(ot) / 1000;
|
24670
23796
|
data.bl = this.getBufferLength(ot);
|
24671
23797
|
}
|
24672
|
-
const next = this.getNextFrag(fragment);
|
24673
|
-
if (next) {
|
24674
|
-
if (next.url && next.url !== fragment.url) {
|
24675
|
-
data.nor = next.url;
|
24676
|
-
}
|
24677
|
-
}
|
24678
23798
|
this.apply(context, data);
|
24679
23799
|
} catch (error) {
|
24680
|
-
|
23800
|
+
logger.warn('Could not generate segment CMCD data.', error);
|
24681
23801
|
}
|
24682
23802
|
};
|
24683
23803
|
this.hls = hls;
|
@@ -24767,7 +23887,7 @@ class CMCDController {
|
|
24767
23887
|
data.su = this.buffering;
|
24768
23888
|
}
|
24769
23889
|
|
24770
|
-
// TODO: Implement rtp, nrr, dl
|
23890
|
+
// TODO: Implement rtp, nrr, nor, dl
|
24771
23891
|
|
24772
23892
|
const {
|
24773
23893
|
includeKeys
|
@@ -24778,28 +23898,15 @@ class CMCDController {
|
|
24778
23898
|
return acc;
|
24779
23899
|
}, {});
|
24780
23900
|
}
|
24781
|
-
const options = {
|
24782
|
-
baseUrl: context.url
|
24783
|
-
};
|
24784
23901
|
if (this.useHeaders) {
|
24785
23902
|
if (!context.headers) {
|
24786
23903
|
context.headers = {};
|
24787
23904
|
}
|
24788
|
-
appendCmcdHeaders(context.headers, data
|
23905
|
+
appendCmcdHeaders(context.headers, data);
|
24789
23906
|
} else {
|
24790
|
-
context.url = appendCmcdQuery(context.url, data
|
24791
|
-
}
|
24792
|
-
}
|
24793
|
-
getNextFrag(fragment) {
|
24794
|
-
var _this$hls$levels$frag;
|
24795
|
-
const levelDetails = (_this$hls$levels$frag = this.hls.levels[fragment.level]) == null ? void 0 : _this$hls$levels$frag.details;
|
24796
|
-
if (levelDetails) {
|
24797
|
-
const index = fragment.sn - levelDetails.startSN;
|
24798
|
-
return levelDetails.fragments[index + 1];
|
23907
|
+
context.url = appendCmcdQuery(context.url, data);
|
24799
23908
|
}
|
24800
|
-
return undefined;
|
24801
23909
|
}
|
24802
|
-
|
24803
23910
|
/**
|
24804
23911
|
* The CMCD object type.
|
24805
23912
|
*/
|
@@ -24928,10 +24035,10 @@ class CMCDController {
|
|
24928
24035
|
}
|
24929
24036
|
|
24930
24037
|
const PATHWAY_PENALTY_DURATION_MS = 300000;
|
24931
|
-
class ContentSteeringController
|
24038
|
+
class ContentSteeringController {
|
24932
24039
|
constructor(hls) {
|
24933
|
-
super('content-steering', hls.logger);
|
24934
24040
|
this.hls = void 0;
|
24041
|
+
this.log = void 0;
|
24935
24042
|
this.loader = null;
|
24936
24043
|
this.uri = null;
|
24937
24044
|
this.pathwayId = '.';
|
@@ -24946,6 +24053,7 @@ class ContentSteeringController extends Logger {
|
|
24946
24053
|
this.subtitleTracks = null;
|
24947
24054
|
this.penalizedPathways = {};
|
24948
24055
|
this.hls = hls;
|
24056
|
+
this.log = logger.log.bind(logger, `[content-steering]:`);
|
24949
24057
|
this.registerListeners();
|
24950
24058
|
}
|
24951
24059
|
registerListeners() {
|
@@ -25069,7 +24177,7 @@ class ContentSteeringController extends Logger {
|
|
25069
24177
|
errorAction.resolved = this.pathwayId !== errorPathway;
|
25070
24178
|
}
|
25071
24179
|
if (!errorAction.resolved) {
|
25072
|
-
|
24180
|
+
logger.warn(`Could not resolve ${data.details} ("${data.error.message}") with content-steering for Pathway: ${errorPathway} levels: ${levels ? levels.length : levels} priorities: ${JSON.stringify(pathwayPriority)} penalized: ${JSON.stringify(this.penalizedPathways)}`);
|
25073
24181
|
}
|
25074
24182
|
}
|
25075
24183
|
}
|
@@ -25240,7 +24348,7 @@ class ContentSteeringController extends Logger {
|
|
25240
24348
|
onSuccess: (response, stats, context, networkDetails) => {
|
25241
24349
|
this.log(`Loaded steering manifest: "${url}"`);
|
25242
24350
|
const steeringData = response.data;
|
25243
|
-
if (
|
24351
|
+
if (steeringData.VERSION !== 1) {
|
25244
24352
|
this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
|
25245
24353
|
return;
|
25246
24354
|
}
|
@@ -26210,7 +25318,7 @@ function timelineConfig() {
|
|
26210
25318
|
/**
|
26211
25319
|
* @ignore
|
26212
25320
|
*/
|
26213
|
-
function mergeConfig(defaultConfig, userConfig
|
25321
|
+
function mergeConfig(defaultConfig, userConfig) {
|
26214
25322
|
if ((userConfig.liveSyncDurationCount || userConfig.liveMaxLatencyDurationCount) && (userConfig.liveSyncDuration || userConfig.liveMaxLatencyDuration)) {
|
26215
25323
|
throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration");
|
26216
25324
|
}
|
@@ -26280,7 +25388,7 @@ function deepCpy(obj) {
|
|
26280
25388
|
/**
|
26281
25389
|
* @ignore
|
26282
25390
|
*/
|
26283
|
-
function enableStreamingMode(config
|
25391
|
+
function enableStreamingMode(config) {
|
26284
25392
|
const currentLoader = config.loader;
|
26285
25393
|
if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
|
26286
25394
|
// If a developer has configured their own loader, respect that choice
|
@@ -26297,9 +25405,10 @@ function enableStreamingMode(config, logger) {
|
|
26297
25405
|
}
|
26298
25406
|
}
|
26299
25407
|
|
25408
|
+
let chromeOrFirefox;
|
26300
25409
|
class LevelController extends BasePlaylistController {
|
26301
25410
|
constructor(hls, contentSteeringController) {
|
26302
|
-
super(hls, 'level-controller');
|
25411
|
+
super(hls, '[level-controller]');
|
26303
25412
|
this._levels = [];
|
26304
25413
|
this._firstLevel = -1;
|
26305
25414
|
this._maxAutoLevel = -1;
|
@@ -26370,15 +25479,23 @@ class LevelController extends BasePlaylistController {
|
|
26370
25479
|
let videoCodecFound = false;
|
26371
25480
|
let audioCodecFound = false;
|
26372
25481
|
data.levels.forEach(levelParsed => {
|
26373
|
-
var _videoCodec;
|
25482
|
+
var _audioCodec, _videoCodec;
|
26374
25483
|
const attributes = levelParsed.attrs;
|
25484
|
+
|
25485
|
+
// erase audio codec info if browser does not support mp4a.40.34.
|
25486
|
+
// demuxer will autodetect codec and fallback to mpeg/audio
|
26375
25487
|
let {
|
26376
25488
|
audioCodec,
|
26377
25489
|
videoCodec
|
26378
25490
|
} = levelParsed;
|
25491
|
+
if (((_audioCodec = audioCodec) == null ? void 0 : _audioCodec.indexOf('mp4a.40.34')) !== -1) {
|
25492
|
+
chromeOrFirefox || (chromeOrFirefox = /chrome|firefox/i.test(navigator.userAgent));
|
25493
|
+
if (chromeOrFirefox) {
|
25494
|
+
levelParsed.audioCodec = audioCodec = undefined;
|
25495
|
+
}
|
25496
|
+
}
|
26379
25497
|
if (audioCodec) {
|
26380
|
-
|
26381
|
-
levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource) || undefined;
|
25498
|
+
levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource);
|
26382
25499
|
}
|
26383
25500
|
if (((_videoCodec = videoCodec) == null ? void 0 : _videoCodec.indexOf('avc1')) === 0) {
|
26384
25501
|
videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
|
@@ -26720,12 +25837,7 @@ class LevelController extends BasePlaylistController {
|
|
26720
25837
|
if (curLevel.fragmentError === 0) {
|
26721
25838
|
curLevel.loadError = 0;
|
26722
25839
|
}
|
26723
|
-
|
26724
|
-
let previousDetails = curLevel.details;
|
26725
|
-
if (previousDetails === data.details && previousDetails.advanced) {
|
26726
|
-
previousDetails = undefined;
|
26727
|
-
}
|
26728
|
-
this.playlistLoaded(level, data, previousDetails);
|
25840
|
+
this.playlistLoaded(level, data, curLevel.details);
|
26729
25841
|
} else if ((_data$deliveryDirecti2 = data.deliveryDirectives) != null && _data$deliveryDirecti2.skip) {
|
26730
25842
|
// received a delta playlist update that cannot be merged
|
26731
25843
|
details.deltaUpdateFailed = true;
|
@@ -26969,8 +26081,6 @@ class KeyLoader {
|
|
26969
26081
|
}
|
26970
26082
|
return this.loadKeyEME(keyInfo, frag);
|
26971
26083
|
case 'AES-128':
|
26972
|
-
case 'AES-256':
|
26973
|
-
case 'AES-256-CTR':
|
26974
26084
|
return this.loadKeyHTTP(keyInfo, frag);
|
26975
26085
|
default:
|
26976
26086
|
return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`)));
|
@@ -27108,9 +26218,8 @@ const STALL_MINIMUM_DURATION_MS = 250;
|
|
27108
26218
|
const MAX_START_GAP_JUMP = 2.0;
|
27109
26219
|
const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
|
27110
26220
|
const SKIP_BUFFER_RANGE_START = 0.05;
|
27111
|
-
class GapController
|
26221
|
+
class GapController {
|
27112
26222
|
constructor(config, media, fragmentTracker, hls) {
|
27113
|
-
super('gap-controller', hls.logger);
|
27114
26223
|
this.config = void 0;
|
27115
26224
|
this.media = null;
|
27116
26225
|
this.fragmentTracker = void 0;
|
@@ -27120,7 +26229,6 @@ class GapController extends Logger {
|
|
27120
26229
|
this.stalled = null;
|
27121
26230
|
this.moved = false;
|
27122
26231
|
this.seeking = false;
|
27123
|
-
this.ended = 0;
|
27124
26232
|
this.config = config;
|
27125
26233
|
this.media = media;
|
27126
26234
|
this.fragmentTracker = fragmentTracker;
|
@@ -27138,7 +26246,7 @@ class GapController extends Logger {
|
|
27138
26246
|
*
|
27139
26247
|
* @param lastCurrentTime - Previously read playhead position
|
27140
26248
|
*/
|
27141
|
-
poll(lastCurrentTime, activeFrag
|
26249
|
+
poll(lastCurrentTime, activeFrag) {
|
27142
26250
|
const {
|
27143
26251
|
config,
|
27144
26252
|
media,
|
@@ -27157,7 +26265,6 @@ class GapController extends Logger {
|
|
27157
26265
|
|
27158
26266
|
// The playhead is moving, no-op
|
27159
26267
|
if (currentTime !== lastCurrentTime) {
|
27160
|
-
this.ended = 0;
|
27161
26268
|
this.moved = true;
|
27162
26269
|
if (!seeking) {
|
27163
26270
|
this.nudgeRetry = 0;
|
@@ -27166,7 +26273,7 @@ class GapController extends Logger {
|
|
27166
26273
|
// The playhead is now moving, but was previously stalled
|
27167
26274
|
if (this.stallReported) {
|
27168
26275
|
const _stalledDuration = self.performance.now() - stalled;
|
27169
|
-
|
26276
|
+
logger.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
|
27170
26277
|
this.stallReported = false;
|
27171
26278
|
}
|
27172
26279
|
this.stalled = null;
|
@@ -27202,6 +26309,7 @@ class GapController extends Logger {
|
|
27202
26309
|
// Skip start gaps if we haven't played, but the last poll detected the start of a stall
|
27203
26310
|
// The addition poll gives the browser a chance to jump the gap for us
|
27204
26311
|
if (!this.moved && this.stalled !== null) {
|
26312
|
+
var _level$details;
|
27205
26313
|
// There is no playable buffer (seeked, waiting for buffer)
|
27206
26314
|
const isBuffered = bufferInfo.len > 0;
|
27207
26315
|
if (!isBuffered && !nextStart) {
|
@@ -27213,8 +26321,9 @@ class GapController extends Logger {
|
|
27213
26321
|
// When joining a live stream with audio tracks, account for live playlist window sliding by allowing
|
27214
26322
|
// a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
|
27215
26323
|
// that begins over 1 target duration after the video start position.
|
27216
|
-
const
|
27217
|
-
const
|
26324
|
+
const level = this.hls.levels ? this.hls.levels[this.hls.currentLevel] : null;
|
26325
|
+
const isLive = level == null ? void 0 : (_level$details = level.details) == null ? void 0 : _level$details.live;
|
26326
|
+
const maxStartGapJump = isLive ? level.details.targetduration * 2 : MAX_START_GAP_JUMP;
|
27218
26327
|
const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
|
27219
26328
|
if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
|
27220
26329
|
if (!media.paused) {
|
@@ -27232,17 +26341,6 @@ class GapController extends Logger {
|
|
27232
26341
|
}
|
27233
26342
|
const stalledDuration = tnow - stalled;
|
27234
26343
|
if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) {
|
27235
|
-
// Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
|
27236
|
-
if (state === State.ENDED && !(levelDetails && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? void 0 : levelDetails.edge) || 0)) < 1) {
|
27237
|
-
if (stalledDuration < 1000 || this.ended) {
|
27238
|
-
return;
|
27239
|
-
}
|
27240
|
-
this.ended = currentTime;
|
27241
|
-
this.hls.trigger(Events.MEDIA_ENDED, {
|
27242
|
-
stalled: true
|
27243
|
-
});
|
27244
|
-
return;
|
27245
|
-
}
|
27246
26344
|
// Report stalling after trying to fix
|
27247
26345
|
this._reportStall(bufferInfo);
|
27248
26346
|
if (!this.media) {
|
@@ -27286,7 +26384,7 @@ class GapController extends Logger {
|
|
27286
26384
|
// needs to cross some sort of threshold covering all source-buffers content
|
27287
26385
|
// to start playing properly.
|
27288
26386
|
if ((bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
|
27289
|
-
|
26387
|
+
logger.warn('Trying to nudge playhead over buffer-hole');
|
27290
26388
|
// Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
|
27291
26389
|
// We only try to jump the hole if it's under the configured size
|
27292
26390
|
// Reset stalled so to rearm watchdog timer
|
@@ -27310,7 +26408,7 @@ class GapController extends Logger {
|
|
27310
26408
|
// Report stalled error once
|
27311
26409
|
this.stallReported = true;
|
27312
26410
|
const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${JSON.stringify(bufferInfo)})`);
|
27313
|
-
|
26411
|
+
logger.warn(error.message);
|
27314
26412
|
hls.trigger(Events.ERROR, {
|
27315
26413
|
type: ErrorTypes.MEDIA_ERROR,
|
27316
26414
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
@@ -27378,7 +26476,7 @@ class GapController extends Logger {
|
|
27378
26476
|
}
|
27379
26477
|
}
|
27380
26478
|
const targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
|
27381
|
-
|
26479
|
+
logger.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
|
27382
26480
|
this.moved = true;
|
27383
26481
|
this.stalled = null;
|
27384
26482
|
media.currentTime = targetTime;
|
@@ -27419,7 +26517,7 @@ class GapController extends Logger {
|
|
27419
26517
|
const targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset;
|
27420
26518
|
// playback stalled in buffered area ... let's nudge currentTime to try to overcome this
|
27421
26519
|
const error = new Error(`Nudging 'currentTime' from ${currentTime} to ${targetTime}`);
|
27422
|
-
|
26520
|
+
logger.warn(error.message);
|
27423
26521
|
media.currentTime = targetTime;
|
27424
26522
|
hls.trigger(Events.ERROR, {
|
27425
26523
|
type: ErrorTypes.MEDIA_ERROR,
|
@@ -27429,7 +26527,7 @@ class GapController extends Logger {
|
|
27429
26527
|
});
|
27430
26528
|
} else {
|
27431
26529
|
const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`);
|
27432
|
-
|
26530
|
+
logger.error(error.message);
|
27433
26531
|
hls.trigger(Events.ERROR, {
|
27434
26532
|
type: ErrorTypes.MEDIA_ERROR,
|
27435
26533
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
@@ -27444,7 +26542,7 @@ const TICK_INTERVAL = 100; // how often to tick in ms
|
|
27444
26542
|
|
27445
26543
|
class StreamController extends BaseStreamController {
|
27446
26544
|
constructor(hls, fragmentTracker, keyLoader) {
|
27447
|
-
super(hls, fragmentTracker, keyLoader, 'stream-controller', PlaylistLevelType.MAIN);
|
26545
|
+
super(hls, fragmentTracker, keyLoader, '[stream-controller]', PlaylistLevelType.MAIN);
|
27448
26546
|
this.audioCodecSwap = false;
|
27449
26547
|
this.gapController = null;
|
27450
26548
|
this.level = -1;
|
@@ -27452,43 +26550,27 @@ class StreamController extends BaseStreamController {
|
|
27452
26550
|
this.altAudio = false;
|
27453
26551
|
this.audioOnly = false;
|
27454
26552
|
this.fragPlaying = null;
|
26553
|
+
this.onvplaying = null;
|
26554
|
+
this.onvseeked = null;
|
27455
26555
|
this.fragLastKbps = 0;
|
27456
26556
|
this.couldBacktrack = false;
|
27457
26557
|
this.backtrackFragment = null;
|
27458
26558
|
this.audioCodecSwitch = false;
|
27459
26559
|
this.videoBuffer = null;
|
27460
|
-
this.
|
27461
|
-
// tick to speed up FRAG_CHANGED triggering
|
27462
|
-
this.tick();
|
27463
|
-
};
|
27464
|
-
this.onMediaSeeked = () => {
|
27465
|
-
const media = this.media;
|
27466
|
-
const currentTime = media ? media.currentTime : null;
|
27467
|
-
if (isFiniteNumber(currentTime)) {
|
27468
|
-
this.log(`Media seeked to ${currentTime.toFixed(3)}`);
|
27469
|
-
}
|
27470
|
-
|
27471
|
-
// If seeked was issued before buffer was appended do not tick immediately
|
27472
|
-
const bufferInfo = this.getMainFwdBufferInfo();
|
27473
|
-
if (bufferInfo === null || bufferInfo.len === 0) {
|
27474
|
-
this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
|
27475
|
-
return;
|
27476
|
-
}
|
27477
|
-
|
27478
|
-
// tick to speed up FRAG_CHANGED triggering
|
27479
|
-
this.tick();
|
27480
|
-
};
|
27481
|
-
this.registerListeners();
|
26560
|
+
this._registerListeners();
|
27482
26561
|
}
|
27483
|
-
|
27484
|
-
super.registerListeners();
|
26562
|
+
_registerListeners() {
|
27485
26563
|
const {
|
27486
26564
|
hls
|
27487
26565
|
} = this;
|
26566
|
+
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
26567
|
+
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
26568
|
+
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
27488
26569
|
hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
27489
26570
|
hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
|
27490
26571
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
27491
26572
|
hls.on(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
|
26573
|
+
hls.on(Events.ERROR, this.onError, this);
|
27492
26574
|
hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
27493
26575
|
hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
27494
26576
|
hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -27496,14 +26578,17 @@ class StreamController extends BaseStreamController {
|
|
27496
26578
|
hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
|
27497
26579
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
27498
26580
|
}
|
27499
|
-
|
27500
|
-
super.unregisterListeners();
|
26581
|
+
_unregisterListeners() {
|
27501
26582
|
const {
|
27502
26583
|
hls
|
27503
26584
|
} = this;
|
26585
|
+
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
26586
|
+
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
26587
|
+
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
27504
26588
|
hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
27505
26589
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
27506
26590
|
hls.off(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
|
26591
|
+
hls.off(Events.ERROR, this.onError, this);
|
27507
26592
|
hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
27508
26593
|
hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
27509
26594
|
hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -27512,9 +26597,7 @@ class StreamController extends BaseStreamController {
|
|
27512
26597
|
hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
27513
26598
|
}
|
27514
26599
|
onHandlerDestroying() {
|
27515
|
-
|
27516
|
-
this.onMediaPlaying = this.onMediaSeeked = null;
|
27517
|
-
this.unregisterListeners();
|
26600
|
+
this._unregisterListeners();
|
27518
26601
|
super.onHandlerDestroying();
|
27519
26602
|
}
|
27520
26603
|
startLoad(startPosition) {
|
@@ -27634,7 +26717,7 @@ class StreamController extends BaseStreamController {
|
|
27634
26717
|
if (this.altAudio && this.audioOnly) {
|
27635
26718
|
return;
|
27636
26719
|
}
|
27637
|
-
if (!
|
26720
|
+
if (!(levels != null && levels[level])) {
|
27638
26721
|
return;
|
27639
26722
|
}
|
27640
26723
|
const levelInfo = levels[level];
|
@@ -27842,17 +26925,20 @@ class StreamController extends BaseStreamController {
|
|
27842
26925
|
onMediaAttached(event, data) {
|
27843
26926
|
super.onMediaAttached(event, data);
|
27844
26927
|
const media = data.media;
|
27845
|
-
|
27846
|
-
|
26928
|
+
this.onvplaying = this.onMediaPlaying.bind(this);
|
26929
|
+
this.onvseeked = this.onMediaSeeked.bind(this);
|
26930
|
+
media.addEventListener('playing', this.onvplaying);
|
26931
|
+
media.addEventListener('seeked', this.onvseeked);
|
27847
26932
|
this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
|
27848
26933
|
}
|
27849
26934
|
onMediaDetaching() {
|
27850
26935
|
const {
|
27851
26936
|
media
|
27852
26937
|
} = this;
|
27853
|
-
if (media) {
|
27854
|
-
media.removeEventListener('playing', this.
|
27855
|
-
media.removeEventListener('seeked', this.
|
26938
|
+
if (media && this.onvplaying && this.onvseeked) {
|
26939
|
+
media.removeEventListener('playing', this.onvplaying);
|
26940
|
+
media.removeEventListener('seeked', this.onvseeked);
|
26941
|
+
this.onvplaying = this.onvseeked = null;
|
27856
26942
|
this.videoBuffer = null;
|
27857
26943
|
}
|
27858
26944
|
this.fragPlaying = null;
|
@@ -27862,6 +26948,27 @@ class StreamController extends BaseStreamController {
|
|
27862
26948
|
}
|
27863
26949
|
super.onMediaDetaching();
|
27864
26950
|
}
|
26951
|
+
onMediaPlaying() {
|
26952
|
+
// tick to speed up FRAG_CHANGED triggering
|
26953
|
+
this.tick();
|
26954
|
+
}
|
26955
|
+
onMediaSeeked() {
|
26956
|
+
const media = this.media;
|
26957
|
+
const currentTime = media ? media.currentTime : null;
|
26958
|
+
if (isFiniteNumber(currentTime)) {
|
26959
|
+
this.log(`Media seeked to ${currentTime.toFixed(3)}`);
|
26960
|
+
}
|
26961
|
+
|
26962
|
+
// If seeked was issued before buffer was appended do not tick immediately
|
26963
|
+
const bufferInfo = this.getMainFwdBufferInfo();
|
26964
|
+
if (bufferInfo === null || bufferInfo.len === 0) {
|
26965
|
+
this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
|
26966
|
+
return;
|
26967
|
+
}
|
26968
|
+
|
26969
|
+
// tick to speed up FRAG_CHANGED triggering
|
26970
|
+
this.tick();
|
26971
|
+
}
|
27865
26972
|
onManifestLoading() {
|
27866
26973
|
// reset buffer on manifest loading
|
27867
26974
|
this.log('Trigger BUFFER_RESET');
|
@@ -28153,10 +27260,8 @@ class StreamController extends BaseStreamController {
|
|
28153
27260
|
}
|
28154
27261
|
if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) {
|
28155
27262
|
// Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
|
28156
|
-
const
|
28157
|
-
|
28158
|
-
const levelDetails = this.getLevelDetails();
|
28159
|
-
gapController.poll(this.lastCurrentTime, activeFrag, levelDetails, state);
|
27263
|
+
const activeFrag = this.state !== State.IDLE ? this.fragCurrent : null;
|
27264
|
+
gapController.poll(this.lastCurrentTime, activeFrag);
|
28160
27265
|
}
|
28161
27266
|
this.lastCurrentTime = media.currentTime;
|
28162
27267
|
}
|
@@ -28594,7 +27699,7 @@ class Hls {
|
|
28594
27699
|
* Get the video-dev/hls.js package version.
|
28595
27700
|
*/
|
28596
27701
|
static get version() {
|
28597
|
-
return "1.5.5
|
27702
|
+
return "1.5.5";
|
28598
27703
|
}
|
28599
27704
|
|
28600
27705
|
/**
|
@@ -28657,12 +27762,9 @@ class Hls {
|
|
28657
27762
|
* The configuration object provided on player instantiation.
|
28658
27763
|
*/
|
28659
27764
|
this.userConfig = void 0;
|
28660
|
-
/**
|
28661
|
-
* The logger functions used by this player instance, configured on player instantiation.
|
28662
|
-
*/
|
28663
|
-
this.logger = void 0;
|
28664
27765
|
this.coreComponents = void 0;
|
28665
27766
|
this.networkControllers = void 0;
|
27767
|
+
this.started = false;
|
28666
27768
|
this._emitter = new EventEmitter();
|
28667
27769
|
this._autoLevelCapping = -1;
|
28668
27770
|
this._maxHdcpLevel = null;
|
@@ -28679,11 +27781,11 @@ class Hls {
|
|
28679
27781
|
this._media = null;
|
28680
27782
|
this.url = null;
|
28681
27783
|
this.triggeringException = void 0;
|
28682
|
-
|
28683
|
-
const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig
|
27784
|
+
enableLogs(userConfig.debug || false, 'Hls instance');
|
27785
|
+
const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig);
|
28684
27786
|
this.userConfig = userConfig;
|
28685
27787
|
if (config.progressive) {
|
28686
|
-
enableStreamingMode(config
|
27788
|
+
enableStreamingMode(config);
|
28687
27789
|
}
|
28688
27790
|
|
28689
27791
|
// core controllers and network loaders
|
@@ -28782,7 +27884,7 @@ class Hls {
|
|
28782
27884
|
try {
|
28783
27885
|
return this.emit(event, event, eventObject);
|
28784
27886
|
} catch (error) {
|
28785
|
-
|
27887
|
+
logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
|
28786
27888
|
// Prevent recursion in error event handlers that throw #5497
|
28787
27889
|
if (!this.triggeringException) {
|
28788
27890
|
this.triggeringException = true;
|
@@ -28808,7 +27910,7 @@ class Hls {
|
|
28808
27910
|
* Dispose of the instance
|
28809
27911
|
*/
|
28810
27912
|
destroy() {
|
28811
|
-
|
27913
|
+
logger.log('destroy');
|
28812
27914
|
this.trigger(Events.DESTROYING, undefined);
|
28813
27915
|
this.detachMedia();
|
28814
27916
|
this.removeAllListeners();
|
@@ -28829,7 +27931,7 @@ class Hls {
|
|
28829
27931
|
* Attaches Hls.js to a media element
|
28830
27932
|
*/
|
28831
27933
|
attachMedia(media) {
|
28832
|
-
|
27934
|
+
logger.log('attachMedia');
|
28833
27935
|
this._media = media;
|
28834
27936
|
this.trigger(Events.MEDIA_ATTACHING, {
|
28835
27937
|
media: media
|
@@ -28840,7 +27942,7 @@ class Hls {
|
|
28840
27942
|
* Detach Hls.js from the media
|
28841
27943
|
*/
|
28842
27944
|
detachMedia() {
|
28843
|
-
|
27945
|
+
logger.log('detachMedia');
|
28844
27946
|
this.trigger(Events.MEDIA_DETACHING, undefined);
|
28845
27947
|
this._media = null;
|
28846
27948
|
}
|
@@ -28857,7 +27959,7 @@ class Hls {
|
|
28857
27959
|
});
|
28858
27960
|
this._autoLevelCapping = -1;
|
28859
27961
|
this._maxHdcpLevel = null;
|
28860
|
-
|
27962
|
+
logger.log(`loadSource:${loadingSource}`);
|
28861
27963
|
if (media && loadedSource && (loadedSource !== loadingSource || this.bufferController.hasSourceTypes())) {
|
28862
27964
|
this.detachMedia();
|
28863
27965
|
this.attachMedia(media);
|
@@ -28876,7 +27978,8 @@ class Hls {
|
|
28876
27978
|
* Defaults to -1 (None: starts from earliest point)
|
28877
27979
|
*/
|
28878
27980
|
startLoad(startPosition = -1) {
|
28879
|
-
|
27981
|
+
logger.log(`startLoad(${startPosition})`);
|
27982
|
+
this.started = true;
|
28880
27983
|
this.networkControllers.forEach(controller => {
|
28881
27984
|
controller.startLoad(startPosition);
|
28882
27985
|
});
|
@@ -28886,31 +27989,34 @@ class Hls {
|
|
28886
27989
|
* Stop loading of any stream data.
|
28887
27990
|
*/
|
28888
27991
|
stopLoad() {
|
28889
|
-
|
27992
|
+
logger.log('stopLoad');
|
27993
|
+
this.started = false;
|
28890
27994
|
this.networkControllers.forEach(controller => {
|
28891
27995
|
controller.stopLoad();
|
28892
27996
|
});
|
28893
27997
|
}
|
28894
27998
|
|
28895
27999
|
/**
|
28896
|
-
* Resumes stream controller segment loading
|
28000
|
+
* Resumes stream controller segment loading if previously started.
|
28897
28001
|
*/
|
28898
28002
|
resumeBuffering() {
|
28899
|
-
this.
|
28900
|
-
|
28901
|
-
controller
|
28902
|
-
|
28903
|
-
|
28003
|
+
if (this.started) {
|
28004
|
+
this.networkControllers.forEach(controller => {
|
28005
|
+
if ('fragmentLoader' in controller) {
|
28006
|
+
controller.startLoad(-1);
|
28007
|
+
}
|
28008
|
+
});
|
28009
|
+
}
|
28904
28010
|
}
|
28905
28011
|
|
28906
28012
|
/**
|
28907
|
-
*
|
28013
|
+
* Stops stream controller segment loading without changing 'started' state like stopLoad().
|
28908
28014
|
* This allows for media buffering to be paused without interupting playlist loading.
|
28909
28015
|
*/
|
28910
28016
|
pauseBuffering() {
|
28911
28017
|
this.networkControllers.forEach(controller => {
|
28912
|
-
if (controller
|
28913
|
-
controller.
|
28018
|
+
if ('fragmentLoader' in controller) {
|
28019
|
+
controller.stopLoad();
|
28914
28020
|
}
|
28915
28021
|
});
|
28916
28022
|
}
|
@@ -28919,7 +28025,7 @@ class Hls {
|
|
28919
28025
|
* Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
|
28920
28026
|
*/
|
28921
28027
|
swapAudioCodec() {
|
28922
|
-
|
28028
|
+
logger.log('swapAudioCodec');
|
28923
28029
|
this.streamController.swapAudioCodec();
|
28924
28030
|
}
|
28925
28031
|
|
@@ -28930,7 +28036,7 @@ class Hls {
|
|
28930
28036
|
* Automatic recovery of media-errors by this process is configurable.
|
28931
28037
|
*/
|
28932
28038
|
recoverMediaError() {
|
28933
|
-
|
28039
|
+
logger.log('recoverMediaError');
|
28934
28040
|
const media = this._media;
|
28935
28041
|
this.detachMedia();
|
28936
28042
|
if (media) {
|
@@ -28960,7 +28066,7 @@ class Hls {
|
|
28960
28066
|
* Set quality level index immediately. This will flush the current buffer to replace the quality asap. That means playback will interrupt at least shortly to re-buffer and re-sync eventually. Set to -1 for automatic level selection.
|
28961
28067
|
*/
|
28962
28068
|
set currentLevel(newLevel) {
|
28963
|
-
|
28069
|
+
logger.log(`set currentLevel:${newLevel}`);
|
28964
28070
|
this.levelController.manualLevel = newLevel;
|
28965
28071
|
this.streamController.immediateLevelSwitch();
|
28966
28072
|
}
|
@@ -28979,7 +28085,7 @@ class Hls {
|
|
28979
28085
|
* @param newLevel - Pass -1 for automatic level selection
|
28980
28086
|
*/
|
28981
28087
|
set nextLevel(newLevel) {
|
28982
|
-
|
28088
|
+
logger.log(`set nextLevel:${newLevel}`);
|
28983
28089
|
this.levelController.manualLevel = newLevel;
|
28984
28090
|
this.streamController.nextLevelSwitch();
|
28985
28091
|
}
|
@@ -28998,7 +28104,7 @@ class Hls {
|
|
28998
28104
|
* @param newLevel - Pass -1 for automatic level selection
|
28999
28105
|
*/
|
29000
28106
|
set loadLevel(newLevel) {
|
29001
|
-
|
28107
|
+
logger.log(`set loadLevel:${newLevel}`);
|
29002
28108
|
this.levelController.manualLevel = newLevel;
|
29003
28109
|
}
|
29004
28110
|
|
@@ -29029,7 +28135,7 @@ class Hls {
|
|
29029
28135
|
* Sets "first-level", see getter.
|
29030
28136
|
*/
|
29031
28137
|
set firstLevel(newLevel) {
|
29032
|
-
|
28138
|
+
logger.log(`set firstLevel:${newLevel}`);
|
29033
28139
|
this.levelController.firstLevel = newLevel;
|
29034
28140
|
}
|
29035
28141
|
|
@@ -29054,7 +28160,7 @@ class Hls {
|
|
29054
28160
|
* (determined from download of first segment)
|
29055
28161
|
*/
|
29056
28162
|
set startLevel(newLevel) {
|
29057
|
-
|
28163
|
+
logger.log(`set startLevel:${newLevel}`);
|
29058
28164
|
// if not in automatic start level detection, ensure startLevel is greater than minAutoLevel
|
29059
28165
|
if (newLevel !== -1) {
|
29060
28166
|
newLevel = Math.max(newLevel, this.minAutoLevel);
|
@@ -29129,7 +28235,7 @@ class Hls {
|
|
29129
28235
|
*/
|
29130
28236
|
set autoLevelCapping(newLevel) {
|
29131
28237
|
if (this._autoLevelCapping !== newLevel) {
|
29132
|
-
|
28238
|
+
logger.log(`set autoLevelCapping:${newLevel}`);
|
29133
28239
|
this._autoLevelCapping = newLevel;
|
29134
28240
|
this.levelController.checkMaxAutoUpdated();
|
29135
28241
|
}
|