hls.js 1.5.4 → 1.5.5-0.canary.9978
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/hls-demo.js +10 -0
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +1935 -1094
- package/dist/hls.js.d.ts +63 -50
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +1059 -838
- 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 +846 -626
- 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 +1640 -814
- 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 +18 -18
- package/src/config.ts +3 -2
- package/src/controller/abr-controller.ts +21 -20
- package/src/controller/audio-stream-controller.ts +15 -16
- package/src/controller/audio-track-controller.ts +1 -1
- package/src/controller/base-playlist-controller.ts +7 -7
- package/src/controller/base-stream-controller.ts +56 -29
- package/src/controller/buffer-controller.ts +11 -11
- package/src/controller/cap-level-controller.ts +1 -2
- package/src/controller/cmcd-controller.ts +25 -3
- package/src/controller/content-steering-controller.ts +8 -6
- package/src/controller/eme-controller.ts +9 -22
- package/src/controller/error-controller.ts +6 -8
- package/src/controller/fps-controller.ts +2 -3
- package/src/controller/gap-controller.ts +43 -16
- package/src/controller/latency-controller.ts +9 -11
- package/src/controller/level-controller.ts +5 -17
- package/src/controller/stream-controller.ts +25 -32
- package/src/controller/subtitle-stream-controller.ts +13 -14
- package/src/controller/subtitle-track-controller.ts +5 -3
- package/src/controller/timeline-controller.ts +23 -30
- package/src/crypt/aes-crypto.ts +21 -2
- package/src/crypt/decrypter-aes-mode.ts +4 -0
- package/src/crypt/decrypter.ts +32 -18
- package/src/crypt/fast-aes-key.ts +24 -5
- package/src/demux/audio/adts.ts +9 -4
- package/src/demux/sample-aes.ts +2 -0
- package/src/demux/transmuxer-interface.ts +4 -12
- package/src/demux/transmuxer-worker.ts +4 -4
- package/src/demux/transmuxer.ts +16 -3
- package/src/demux/tsdemuxer.ts +71 -37
- package/src/demux/video/avc-video-parser.ts +208 -119
- package/src/demux/video/base-video-parser.ts +134 -2
- package/src/demux/video/exp-golomb.ts +0 -208
- package/src/demux/video/hevc-video-parser.ts +746 -0
- package/src/events.ts +7 -0
- package/src/hls.ts +42 -34
- package/src/loader/fragment-loader.ts +9 -2
- package/src/loader/key-loader.ts +2 -0
- package/src/loader/level-key.ts +10 -9
- package/src/remux/mp4-generator.ts +196 -1
- package/src/remux/mp4-remuxer.ts +23 -7
- package/src/task-loop.ts +5 -2
- package/src/types/component-api.ts +2 -0
- package/src/types/demuxer.ts +3 -0
- package/src/types/events.ts +4 -0
- package/src/utils/codecs.ts +33 -4
- package/src/utils/encryption-methods-util.ts +21 -0
- package/src/utils/logger.ts +53 -24
package/dist/hls.mjs
CHANGED
@@ -256,6 +256,7 @@ let Events = /*#__PURE__*/function (Events) {
|
|
256
256
|
Events["MEDIA_ATTACHED"] = "hlsMediaAttached";
|
257
257
|
Events["MEDIA_DETACHING"] = "hlsMediaDetaching";
|
258
258
|
Events["MEDIA_DETACHED"] = "hlsMediaDetached";
|
259
|
+
Events["MEDIA_ENDED"] = "hlsMediaEnded";
|
259
260
|
Events["BUFFER_RESET"] = "hlsBufferReset";
|
260
261
|
Events["BUFFER_CODECS"] = "hlsBufferCodecs";
|
261
262
|
Events["BUFFER_CREATED"] = "hlsBufferCreated";
|
@@ -369,6 +370,23 @@ let ErrorDetails = /*#__PURE__*/function (ErrorDetails) {
|
|
369
370
|
return ErrorDetails;
|
370
371
|
}({});
|
371
372
|
|
373
|
+
class Logger {
|
374
|
+
constructor(label, logger) {
|
375
|
+
this.trace = void 0;
|
376
|
+
this.debug = void 0;
|
377
|
+
this.log = void 0;
|
378
|
+
this.warn = void 0;
|
379
|
+
this.info = void 0;
|
380
|
+
this.error = void 0;
|
381
|
+
const lb = `[${label}]:`;
|
382
|
+
this.trace = noop;
|
383
|
+
this.debug = logger.debug.bind(null, lb);
|
384
|
+
this.log = logger.log.bind(null, lb);
|
385
|
+
this.warn = logger.warn.bind(null, lb);
|
386
|
+
this.info = logger.info.bind(null, lb);
|
387
|
+
this.error = logger.error.bind(null, lb);
|
388
|
+
}
|
389
|
+
}
|
372
390
|
const noop = function noop() {};
|
373
391
|
const fakeLogger = {
|
374
392
|
trace: noop,
|
@@ -378,7 +396,9 @@ const fakeLogger = {
|
|
378
396
|
info: noop,
|
379
397
|
error: noop
|
380
398
|
};
|
381
|
-
|
399
|
+
function createLogger() {
|
400
|
+
return _extends({}, fakeLogger);
|
401
|
+
}
|
382
402
|
|
383
403
|
// let lastCallTime;
|
384
404
|
// function formatMsgWithTimeInfo(type, msg) {
|
@@ -389,35 +409,36 @@ let exportedLogger = fakeLogger;
|
|
389
409
|
// return msg;
|
390
410
|
// }
|
391
411
|
|
392
|
-
function consolePrintFn(type) {
|
412
|
+
function consolePrintFn(type, id) {
|
393
413
|
const func = self.console[type];
|
394
|
-
|
395
|
-
return func.bind(self.console, `[${type}] >`);
|
396
|
-
}
|
397
|
-
return noop;
|
414
|
+
return func ? func.bind(self.console, `${id ? '[' + id + '] ' : ''}[${type}] >`) : noop;
|
398
415
|
}
|
399
|
-
function
|
400
|
-
|
401
|
-
exportedLogger[type] = debugConfig[type] ? debugConfig[type].bind(debugConfig) : consolePrintFn(type);
|
402
|
-
});
|
416
|
+
function getLoggerFn(key, debugConfig, id) {
|
417
|
+
return debugConfig[key] ? debugConfig[key].bind(debugConfig) : consolePrintFn(key, id);
|
403
418
|
}
|
404
|
-
|
419
|
+
let exportedLogger = createLogger();
|
420
|
+
function enableLogs(debugConfig, context, id) {
|
405
421
|
// check that console is available
|
422
|
+
const newLogger = createLogger();
|
406
423
|
if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
|
407
|
-
|
424
|
+
const keys = [
|
408
425
|
// Remove out from list here to hard-disable a log-level
|
409
426
|
// 'trace',
|
410
|
-
'debug', 'log', 'info', 'warn', 'error'
|
427
|
+
'debug', 'log', 'info', 'warn', 'error'];
|
428
|
+
keys.forEach(key => {
|
429
|
+
newLogger[key] = getLoggerFn(key, debugConfig, id);
|
430
|
+
});
|
411
431
|
// Some browsers don't allow to use bind on console object anyway
|
412
432
|
// fallback to default if needed
|
413
433
|
try {
|
414
|
-
|
434
|
+
newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.5.5-0.canary.9978"}`);
|
415
435
|
} catch (e) {
|
416
|
-
|
436
|
+
/* log fn threw an exception. All logger methods are no-ops. */
|
437
|
+
return createLogger();
|
417
438
|
}
|
418
|
-
} else {
|
419
|
-
exportedLogger = fakeLogger;
|
420
439
|
}
|
440
|
+
exportedLogger = newLogger;
|
441
|
+
return newLogger;
|
421
442
|
}
|
422
443
|
const logger = exportedLogger;
|
423
444
|
|
@@ -1036,6 +1057,26 @@ function strToUtf8array(str) {
|
|
1036
1057
|
return Uint8Array.from(unescape(encodeURIComponent(str)), c => c.charCodeAt(0));
|
1037
1058
|
}
|
1038
1059
|
|
1060
|
+
var DecrypterAesMode = {
|
1061
|
+
cbc: 0,
|
1062
|
+
ctr: 1
|
1063
|
+
};
|
1064
|
+
|
1065
|
+
function isFullSegmentEncryption(method) {
|
1066
|
+
return method === 'AES-128' || method === 'AES-256' || method === 'AES-256-CTR';
|
1067
|
+
}
|
1068
|
+
function getAesModeFromFullSegmentMethod(method) {
|
1069
|
+
switch (method) {
|
1070
|
+
case 'AES-128':
|
1071
|
+
case 'AES-256':
|
1072
|
+
return DecrypterAesMode.cbc;
|
1073
|
+
case 'AES-256-CTR':
|
1074
|
+
return DecrypterAesMode.ctr;
|
1075
|
+
default:
|
1076
|
+
throw new Error(`invalid full segment method ${method}`);
|
1077
|
+
}
|
1078
|
+
}
|
1079
|
+
|
1039
1080
|
/** returns `undefined` is `self` is missing, e.g. in node */
|
1040
1081
|
const optionalSelf = typeof self !== 'undefined' ? self : undefined;
|
1041
1082
|
|
@@ -2686,12 +2727,12 @@ class LevelKey {
|
|
2686
2727
|
this.keyFormatVersions = formatversions;
|
2687
2728
|
this.iv = iv;
|
2688
2729
|
this.encrypted = method ? method !== 'NONE' : false;
|
2689
|
-
this.isCommonEncryption = this.encrypted && method
|
2730
|
+
this.isCommonEncryption = this.encrypted && !isFullSegmentEncryption(method);
|
2690
2731
|
}
|
2691
2732
|
isSupported() {
|
2692
2733
|
// If it's Segment encryption or No encryption, just select that key system
|
2693
2734
|
if (this.method) {
|
2694
|
-
if (this.method
|
2735
|
+
if (isFullSegmentEncryption(this.method) || this.method === 'NONE') {
|
2695
2736
|
return true;
|
2696
2737
|
}
|
2697
2738
|
if (this.keyFormat === 'identity') {
|
@@ -2713,14 +2754,13 @@ class LevelKey {
|
|
2713
2754
|
if (!this.encrypted || !this.uri) {
|
2714
2755
|
return null;
|
2715
2756
|
}
|
2716
|
-
if (this.method
|
2757
|
+
if (isFullSegmentEncryption(this.method) && this.uri && !this.iv) {
|
2717
2758
|
if (typeof sn !== 'number') {
|
2718
2759
|
// We are fetching decryption data for a initialization segment
|
2719
|
-
// If the segment was encrypted with AES-128
|
2760
|
+
// If the segment was encrypted with AES-128/256
|
2720
2761
|
// It must have an IV defined. We cannot substitute the Segment Number in.
|
2721
|
-
|
2722
|
-
|
2723
|
-
}
|
2762
|
+
logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
|
2763
|
+
|
2724
2764
|
// Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
|
2725
2765
|
sn = 0;
|
2726
2766
|
}
|
@@ -2999,23 +3039,28 @@ function getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource =
|
|
2999
3039
|
if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) {
|
3000
3040
|
return CODEC_COMPATIBLE_NAMES[lowerCaseCodec];
|
3001
3041
|
}
|
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
|
3006
3042
|
const codecsToCheck = {
|
3043
|
+
// Idealy fLaC and Opus would be first (spec-compliant) but
|
3044
|
+
// some browsers will report that fLaC is supported then fail.
|
3045
|
+
// see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
|
3007
3046
|
flac: ['flac', 'fLaC', 'FLAC'],
|
3008
|
-
opus: ['opus', 'Opus']
|
3047
|
+
opus: ['opus', 'Opus'],
|
3048
|
+
// Replace audio codec info if browser does not support mp4a.40.34,
|
3049
|
+
// and demuxer can fallback to 'audio/mpeg' or 'audio/mp4;codecs="mp3"'
|
3050
|
+
'mp4a.40.34': ['mp3']
|
3009
3051
|
}[lowerCaseCodec];
|
3010
3052
|
for (let i = 0; i < codecsToCheck.length; i++) {
|
3053
|
+
var _getMediaSource;
|
3011
3054
|
if (isCodecMediaSourceSupported(codecsToCheck[i], 'audio', preferManagedMediaSource)) {
|
3012
3055
|
CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
|
3013
3056
|
return codecsToCheck[i];
|
3057
|
+
} else if (codecsToCheck[i] === 'mp3' && (_getMediaSource = getMediaSource(preferManagedMediaSource)) != null && _getMediaSource.isTypeSupported('audio/mpeg')) {
|
3058
|
+
return '';
|
3014
3059
|
}
|
3015
3060
|
}
|
3016
3061
|
return lowerCaseCodec;
|
3017
3062
|
}
|
3018
|
-
const AUDIO_CODEC_REGEXP = /flac|opus/i;
|
3063
|
+
const AUDIO_CODEC_REGEXP = /flac|opus|mp4a\.40\.34/i;
|
3019
3064
|
function getCodecCompatibleName(codec, preferManagedMediaSource = true) {
|
3020
3065
|
return codec.replace(AUDIO_CODEC_REGEXP, m => getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource));
|
3021
3066
|
}
|
@@ -3038,6 +3083,16 @@ function convertAVC1ToAVCOTI(codec) {
|
|
3038
3083
|
}
|
3039
3084
|
return codec;
|
3040
3085
|
}
|
3086
|
+
function getM2TSSupportedAudioTypes(preferManagedMediaSource) {
|
3087
|
+
const MediaSource = getMediaSource(preferManagedMediaSource) || {
|
3088
|
+
isTypeSupported: () => false
|
3089
|
+
};
|
3090
|
+
return {
|
3091
|
+
mpeg: MediaSource.isTypeSupported('audio/mpeg'),
|
3092
|
+
mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
|
3093
|
+
ac3: MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
|
3094
|
+
};
|
3095
|
+
}
|
3041
3096
|
|
3042
3097
|
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;
|
3043
3098
|
const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g;
|
@@ -4704,7 +4759,47 @@ class LatencyController {
|
|
4704
4759
|
this.currentTime = 0;
|
4705
4760
|
this.stallCount = 0;
|
4706
4761
|
this._latency = null;
|
4707
|
-
this.
|
4762
|
+
this.onTimeupdate = () => {
|
4763
|
+
const {
|
4764
|
+
media,
|
4765
|
+
levelDetails
|
4766
|
+
} = this;
|
4767
|
+
if (!media || !levelDetails) {
|
4768
|
+
return;
|
4769
|
+
}
|
4770
|
+
this.currentTime = media.currentTime;
|
4771
|
+
const latency = this.computeLatency();
|
4772
|
+
if (latency === null) {
|
4773
|
+
return;
|
4774
|
+
}
|
4775
|
+
this._latency = latency;
|
4776
|
+
|
4777
|
+
// Adapt playbackRate to meet target latency in low-latency mode
|
4778
|
+
const {
|
4779
|
+
lowLatencyMode,
|
4780
|
+
maxLiveSyncPlaybackRate
|
4781
|
+
} = this.config;
|
4782
|
+
if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
|
4783
|
+
return;
|
4784
|
+
}
|
4785
|
+
const targetLatency = this.targetLatency;
|
4786
|
+
if (targetLatency === null) {
|
4787
|
+
return;
|
4788
|
+
}
|
4789
|
+
const distanceFromTarget = latency - targetLatency;
|
4790
|
+
// Only adjust playbackRate when within one target duration of targetLatency
|
4791
|
+
// and more than one second from under-buffering.
|
4792
|
+
// Playback further than one target duration from target can be considered DVR playback.
|
4793
|
+
const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
|
4794
|
+
const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
|
4795
|
+
if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
|
4796
|
+
const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
|
4797
|
+
const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
|
4798
|
+
media.playbackRate = Math.min(max, Math.max(1, rate));
|
4799
|
+
} else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
|
4800
|
+
media.playbackRate = 1;
|
4801
|
+
}
|
4802
|
+
};
|
4708
4803
|
this.hls = hls;
|
4709
4804
|
this.config = hls.config;
|
4710
4805
|
this.registerListeners();
|
@@ -4796,7 +4891,7 @@ class LatencyController {
|
|
4796
4891
|
this.onMediaDetaching();
|
4797
4892
|
this.levelDetails = null;
|
4798
4893
|
// @ts-ignore
|
4799
|
-
this.hls =
|
4894
|
+
this.hls = null;
|
4800
4895
|
}
|
4801
4896
|
registerListeners() {
|
4802
4897
|
this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
@@ -4814,11 +4909,11 @@ class LatencyController {
|
|
4814
4909
|
}
|
4815
4910
|
onMediaAttached(event, data) {
|
4816
4911
|
this.media = data.media;
|
4817
|
-
this.media.addEventListener('timeupdate', this.
|
4912
|
+
this.media.addEventListener('timeupdate', this.onTimeupdate);
|
4818
4913
|
}
|
4819
4914
|
onMediaDetaching() {
|
4820
4915
|
if (this.media) {
|
4821
|
-
this.media.removeEventListener('timeupdate', this.
|
4916
|
+
this.media.removeEventListener('timeupdate', this.onTimeupdate);
|
4822
4917
|
this.media = null;
|
4823
4918
|
}
|
4824
4919
|
}
|
@@ -4832,10 +4927,10 @@ class LatencyController {
|
|
4832
4927
|
}) {
|
4833
4928
|
this.levelDetails = details;
|
4834
4929
|
if (details.advanced) {
|
4835
|
-
this.
|
4930
|
+
this.onTimeupdate();
|
4836
4931
|
}
|
4837
4932
|
if (!details.live && this.media) {
|
4838
|
-
this.media.removeEventListener('timeupdate', this.
|
4933
|
+
this.media.removeEventListener('timeupdate', this.onTimeupdate);
|
4839
4934
|
}
|
4840
4935
|
}
|
4841
4936
|
onError(event, data) {
|
@@ -4845,48 +4940,7 @@ class LatencyController {
|
|
4845
4940
|
}
|
4846
4941
|
this.stallCount++;
|
4847
4942
|
if ((_this$levelDetails = this.levelDetails) != null && _this$levelDetails.live) {
|
4848
|
-
logger.warn('[
|
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;
|
4943
|
+
this.hls.logger.warn('[latency-controller]: Stall detected, adjusting target latency');
|
4890
4944
|
}
|
4891
4945
|
}
|
4892
4946
|
estimateLiveEdge() {
|
@@ -5658,18 +5712,13 @@ var ErrorActionFlags = {
|
|
5658
5712
|
MoveAllAlternatesMatchingHDCP: 2,
|
5659
5713
|
SwitchToSDR: 4
|
5660
5714
|
}; // Reserved for future use
|
5661
|
-
class ErrorController {
|
5715
|
+
class ErrorController extends Logger {
|
5662
5716
|
constructor(hls) {
|
5717
|
+
super('error-controller', hls.logger);
|
5663
5718
|
this.hls = void 0;
|
5664
5719
|
this.playlistError = 0;
|
5665
5720
|
this.penalizedRenditions = {};
|
5666
|
-
this.log = void 0;
|
5667
|
-
this.warn = void 0;
|
5668
|
-
this.error = void 0;
|
5669
5721
|
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]:`);
|
5673
5722
|
this.registerListeners();
|
5674
5723
|
}
|
5675
5724
|
registerListeners() {
|
@@ -6021,16 +6070,13 @@ class ErrorController {
|
|
6021
6070
|
}
|
6022
6071
|
}
|
6023
6072
|
|
6024
|
-
class BasePlaylistController {
|
6073
|
+
class BasePlaylistController extends Logger {
|
6025
6074
|
constructor(hls, logPrefix) {
|
6075
|
+
super(logPrefix, hls.logger);
|
6026
6076
|
this.hls = void 0;
|
6027
6077
|
this.timer = -1;
|
6028
6078
|
this.requestScheduled = -1;
|
6029
6079
|
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}:`);
|
6034
6080
|
this.hls = hls;
|
6035
6081
|
}
|
6036
6082
|
destroy() {
|
@@ -6063,7 +6109,7 @@ class BasePlaylistController {
|
|
6063
6109
|
try {
|
6064
6110
|
uri = new self.URL(attr.URI, previous.url).href;
|
6065
6111
|
} catch (error) {
|
6066
|
-
|
6112
|
+
this.warn(`Could not construct new URL for Rendition Report: ${error}`);
|
6067
6113
|
uri = attr.URI || '';
|
6068
6114
|
}
|
6069
6115
|
// Use exact match. Otherwise, the last partial match, if any, will be used
|
@@ -6822,8 +6868,9 @@ function searchDownAndUpList(arr, searchIndex, predicate) {
|
|
6822
6868
|
return -1;
|
6823
6869
|
}
|
6824
6870
|
|
6825
|
-
class AbrController {
|
6871
|
+
class AbrController extends Logger {
|
6826
6872
|
constructor(_hls) {
|
6873
|
+
super('abr', _hls.logger);
|
6827
6874
|
this.hls = void 0;
|
6828
6875
|
this.lastLevelLoadSec = 0;
|
6829
6876
|
this.lastLoadedFragLevel = -1;
|
@@ -6937,7 +6984,7 @@ class AbrController {
|
|
6937
6984
|
this.resetEstimator(nextLoadLevelBitrate);
|
6938
6985
|
}
|
6939
6986
|
this.clearTimer();
|
6940
|
-
|
6987
|
+
this.warn(`Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
|
6941
6988
|
Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s
|
6942
6989
|
Estimated load time for current fragment: ${fragLoadedDelay.toFixed(3)} s
|
6943
6990
|
Estimated load time for down switch fragment: ${fragLevelNextLoadedDelay.toFixed(3)} s
|
@@ -6957,7 +7004,7 @@ class AbrController {
|
|
6957
7004
|
}
|
6958
7005
|
resetEstimator(abrEwmaDefaultEstimate) {
|
6959
7006
|
if (abrEwmaDefaultEstimate) {
|
6960
|
-
|
7007
|
+
this.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
|
6961
7008
|
this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate;
|
6962
7009
|
}
|
6963
7010
|
this.firstSelection = -1;
|
@@ -7189,7 +7236,7 @@ class AbrController {
|
|
7189
7236
|
}
|
7190
7237
|
const firstLevel = this.hls.firstLevel;
|
7191
7238
|
const clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel);
|
7192
|
-
|
7239
|
+
this.warn(`Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
|
7193
7240
|
return clamped;
|
7194
7241
|
}
|
7195
7242
|
get forcedAutoLevel() {
|
@@ -7274,13 +7321,13 @@ class AbrController {
|
|
7274
7321
|
// cap maxLoadingDelay and ensure it is not bigger 'than bitrate test' frag duration
|
7275
7322
|
const maxLoadingDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxLoadingDelay) : config.maxLoadingDelay;
|
7276
7323
|
maxStarvationDelay = maxLoadingDelay - bitrateTestDelay;
|
7277
|
-
|
7324
|
+
this.info(`bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
|
7278
7325
|
// don't use conservative factor on bitrate test
|
7279
7326
|
bwFactor = bwUpFactor = 1;
|
7280
7327
|
}
|
7281
7328
|
}
|
7282
7329
|
const bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor);
|
7283
|
-
|
7330
|
+
this.info(`${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
|
7284
7331
|
if (bestLevel > -1) {
|
7285
7332
|
return bestLevel;
|
7286
7333
|
}
|
@@ -7342,7 +7389,7 @@ class AbrController {
|
|
7342
7389
|
currentVideoRange = preferHDR ? videoRanges[videoRanges.length - 1] : videoRanges[0];
|
7343
7390
|
currentFrameRate = minFramerate;
|
7344
7391
|
currentBw = Math.max(currentBw, minBitrate);
|
7345
|
-
|
7392
|
+
this.log(`picked start tier ${JSON.stringify(startTier)}`);
|
7346
7393
|
} else {
|
7347
7394
|
currentCodecSet = level == null ? void 0 : level.codecSet;
|
7348
7395
|
currentVideoRange = level == null ? void 0 : level.videoRange;
|
@@ -7366,11 +7413,11 @@ class AbrController {
|
|
7366
7413
|
const levels = this.hls.levels;
|
7367
7414
|
const index = levels.indexOf(levelInfo);
|
7368
7415
|
if (decodingInfo.error) {
|
7369
|
-
|
7416
|
+
this.warn(`MediaCapabilities decodingInfo error: "${decodingInfo.error}" for level ${index} ${JSON.stringify(decodingInfo)}`);
|
7370
7417
|
} else if (!decodingInfo.supported) {
|
7371
|
-
|
7418
|
+
this.warn(`Unsupported MediaCapabilities decodingInfo result for level ${index} ${JSON.stringify(decodingInfo)}`);
|
7372
7419
|
if (index > -1 && levels.length > 1) {
|
7373
|
-
|
7420
|
+
this.log(`Removing unsupported level ${index}`);
|
7374
7421
|
this.hls.removeLevel(index);
|
7375
7422
|
}
|
7376
7423
|
}
|
@@ -7417,9 +7464,9 @@ class AbrController {
|
|
7417
7464
|
const forcedAutoLevel = this.forcedAutoLevel;
|
7418
7465
|
if (i !== loadLevel && (forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)) {
|
7419
7466
|
if (levelsSkipped.length) {
|
7420
|
-
|
7467
|
+
this.trace(`Skipped level(s) ${levelsSkipped.join(',')} of ${maxAutoLevel} max with CODECS and VIDEO-RANGE:"${levels[levelsSkipped[0]].codecs}" ${levels[levelsSkipped[0]].videoRange}; not compatible with "${level.codecs}" ${currentVideoRange}`);
|
7421
7468
|
}
|
7422
|
-
|
7469
|
+
this.info(`switch candidate:${selectionBaseLevel}->${i} adjustedbw(${Math.round(adjustedbw)})-bitrate=${Math.round(adjustedbw - bitrate)} ttfb:${ttfbEstimateSec.toFixed(1)} avgDuration:${avgDuration.toFixed(1)} maxFetchDuration:${maxFetchDuration.toFixed(1)} fetchDuration:${fetchDuration.toFixed(1)} firstSelection:${firstSelection} codecSet:${currentCodecSet} videoRange:${currentVideoRange} hls.loadLevel:${loadLevel}`);
|
7423
7470
|
}
|
7424
7471
|
if (firstSelection) {
|
7425
7472
|
this.firstSelection = i;
|
@@ -7473,8 +7520,9 @@ class AbrController {
|
|
7473
7520
|
* we are limiting the task execution per call stack to exactly one, but scheduling/post-poning further
|
7474
7521
|
* task processing on the next main loop iteration (also known as "next tick" in the Node/JS runtime lingo).
|
7475
7522
|
*/
|
7476
|
-
class TaskLoop {
|
7477
|
-
constructor() {
|
7523
|
+
class TaskLoop extends Logger {
|
7524
|
+
constructor(label, logger) {
|
7525
|
+
super(label, logger);
|
7478
7526
|
this._boundTick = void 0;
|
7479
7527
|
this._tickTimer = null;
|
7480
7528
|
this._tickInterval = null;
|
@@ -8565,8 +8613,8 @@ function createLoaderContext(frag, part = null) {
|
|
8565
8613
|
var _frag$decryptdata;
|
8566
8614
|
let byteRangeStart = start;
|
8567
8615
|
let byteRangeEnd = end;
|
8568
|
-
if (frag.sn === 'initSegment' && ((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method)
|
8569
|
-
// MAP segment encrypted with method 'AES-128', when served with HTTP Range,
|
8616
|
+
if (frag.sn === 'initSegment' && isMethodFullSegmentAesCbc((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method)) {
|
8617
|
+
// MAP segment encrypted with method 'AES-128' or 'AES-256' (cbc), when served with HTTP Range,
|
8570
8618
|
// has the unencrypted size specified in the range.
|
8571
8619
|
// Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
|
8572
8620
|
const fragmentLen = end - start;
|
@@ -8599,6 +8647,9 @@ function createGapLoadError(frag, part) {
|
|
8599
8647
|
(part ? part : frag).stats.aborted = true;
|
8600
8648
|
return new LoadError(errorData);
|
8601
8649
|
}
|
8650
|
+
function isMethodFullSegmentAesCbc(method) {
|
8651
|
+
return method === 'AES-128' || method === 'AES-256';
|
8652
|
+
}
|
8602
8653
|
class LoadError extends Error {
|
8603
8654
|
constructor(data) {
|
8604
8655
|
super(data.error.message);
|
@@ -8608,33 +8659,61 @@ class LoadError extends Error {
|
|
8608
8659
|
}
|
8609
8660
|
|
8610
8661
|
class AESCrypto {
|
8611
|
-
constructor(subtle, iv) {
|
8662
|
+
constructor(subtle, iv, aesMode) {
|
8612
8663
|
this.subtle = void 0;
|
8613
8664
|
this.aesIV = void 0;
|
8665
|
+
this.aesMode = void 0;
|
8614
8666
|
this.subtle = subtle;
|
8615
8667
|
this.aesIV = iv;
|
8668
|
+
this.aesMode = aesMode;
|
8616
8669
|
}
|
8617
8670
|
decrypt(data, key) {
|
8618
|
-
|
8619
|
-
|
8620
|
-
|
8621
|
-
|
8671
|
+
switch (this.aesMode) {
|
8672
|
+
case DecrypterAesMode.cbc:
|
8673
|
+
return this.subtle.decrypt({
|
8674
|
+
name: 'AES-CBC',
|
8675
|
+
iv: this.aesIV
|
8676
|
+
}, key, data);
|
8677
|
+
case DecrypterAesMode.ctr:
|
8678
|
+
return this.subtle.decrypt({
|
8679
|
+
name: 'AES-CTR',
|
8680
|
+
counter: this.aesIV,
|
8681
|
+
length: 64
|
8682
|
+
},
|
8683
|
+
//64 : NIST SP800-38A standard suggests that the counter should occupy half of the counter block
|
8684
|
+
key, data);
|
8685
|
+
default:
|
8686
|
+
throw new Error(`[AESCrypto] invalid aes mode ${this.aesMode}`);
|
8687
|
+
}
|
8622
8688
|
}
|
8623
8689
|
}
|
8624
8690
|
|
8625
8691
|
class FastAESKey {
|
8626
|
-
constructor(subtle, key) {
|
8692
|
+
constructor(subtle, key, aesMode) {
|
8627
8693
|
this.subtle = void 0;
|
8628
8694
|
this.key = void 0;
|
8695
|
+
this.aesMode = void 0;
|
8629
8696
|
this.subtle = subtle;
|
8630
8697
|
this.key = key;
|
8698
|
+
this.aesMode = aesMode;
|
8631
8699
|
}
|
8632
8700
|
expandKey() {
|
8701
|
+
const subtleAlgoName = getSubtleAlgoName(this.aesMode);
|
8633
8702
|
return this.subtle.importKey('raw', this.key, {
|
8634
|
-
name:
|
8703
|
+
name: subtleAlgoName
|
8635
8704
|
}, false, ['encrypt', 'decrypt']);
|
8636
8705
|
}
|
8637
8706
|
}
|
8707
|
+
function getSubtleAlgoName(aesMode) {
|
8708
|
+
switch (aesMode) {
|
8709
|
+
case DecrypterAesMode.cbc:
|
8710
|
+
return 'AES-CBC';
|
8711
|
+
case DecrypterAesMode.ctr:
|
8712
|
+
return 'AES-CTR';
|
8713
|
+
default:
|
8714
|
+
throw new Error(`[FastAESKey] invalid aes mode ${aesMode}`);
|
8715
|
+
}
|
8716
|
+
}
|
8638
8717
|
|
8639
8718
|
// PKCS7
|
8640
8719
|
function removePadding(array) {
|
@@ -8884,7 +8963,8 @@ class Decrypter {
|
|
8884
8963
|
this.currentIV = null;
|
8885
8964
|
this.currentResult = null;
|
8886
8965
|
this.useSoftware = void 0;
|
8887
|
-
this.
|
8966
|
+
this.enableSoftwareAES = void 0;
|
8967
|
+
this.enableSoftwareAES = config.enableSoftwareAES;
|
8888
8968
|
this.removePKCS7Padding = removePKCS7Padding;
|
8889
8969
|
// built in decryptor expects PKCS7 padding
|
8890
8970
|
if (removePKCS7Padding) {
|
@@ -8897,9 +8977,7 @@ class Decrypter {
|
|
8897
8977
|
/* no-op */
|
8898
8978
|
}
|
8899
8979
|
}
|
8900
|
-
|
8901
|
-
this.useSoftware = true;
|
8902
|
-
}
|
8980
|
+
this.useSoftware = this.subtle === null;
|
8903
8981
|
}
|
8904
8982
|
destroy() {
|
8905
8983
|
this.subtle = null;
|
@@ -8937,10 +9015,10 @@ class Decrypter {
|
|
8937
9015
|
this.softwareDecrypter = null;
|
8938
9016
|
}
|
8939
9017
|
}
|
8940
|
-
decrypt(data, key, iv) {
|
9018
|
+
decrypt(data, key, iv, aesMode) {
|
8941
9019
|
if (this.useSoftware) {
|
8942
9020
|
return new Promise((resolve, reject) => {
|
8943
|
-
this.softwareDecrypt(new Uint8Array(data), key, iv);
|
9021
|
+
this.softwareDecrypt(new Uint8Array(data), key, iv, aesMode);
|
8944
9022
|
const decryptResult = this.flush();
|
8945
9023
|
if (decryptResult) {
|
8946
9024
|
resolve(decryptResult.buffer);
|
@@ -8949,17 +9027,21 @@ class Decrypter {
|
|
8949
9027
|
}
|
8950
9028
|
});
|
8951
9029
|
}
|
8952
|
-
return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
|
9030
|
+
return this.webCryptoDecrypt(new Uint8Array(data), key, iv, aesMode);
|
8953
9031
|
}
|
8954
9032
|
|
8955
9033
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
8956
9034
|
// data is handled in the flush() call
|
8957
|
-
softwareDecrypt(data, key, iv) {
|
9035
|
+
softwareDecrypt(data, key, iv, aesMode) {
|
8958
9036
|
const {
|
8959
9037
|
currentIV,
|
8960
9038
|
currentResult,
|
8961
9039
|
remainderData
|
8962
9040
|
} = this;
|
9041
|
+
if (aesMode !== DecrypterAesMode.cbc || key.byteLength !== 16) {
|
9042
|
+
logger.warn('SoftwareDecrypt: can only handle AES-128-CBC');
|
9043
|
+
return null;
|
9044
|
+
}
|
8963
9045
|
this.logOnce('JS AES decrypt');
|
8964
9046
|
// The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
|
8965
9047
|
// This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached
|
@@ -8992,11 +9074,11 @@ class Decrypter {
|
|
8992
9074
|
}
|
8993
9075
|
return result;
|
8994
9076
|
}
|
8995
|
-
webCryptoDecrypt(data, key, iv) {
|
9077
|
+
webCryptoDecrypt(data, key, iv, aesMode) {
|
8996
9078
|
const subtle = this.subtle;
|
8997
9079
|
if (this.key !== key || !this.fastAesKey) {
|
8998
9080
|
this.key = key;
|
8999
|
-
this.fastAesKey = new FastAESKey(subtle, key);
|
9081
|
+
this.fastAesKey = new FastAESKey(subtle, key, aesMode);
|
9000
9082
|
}
|
9001
9083
|
return this.fastAesKey.expandKey().then(aesKey => {
|
9002
9084
|
// decrypt using web crypto
|
@@ -9004,22 +9086,25 @@ class Decrypter {
|
|
9004
9086
|
return Promise.reject(new Error('web crypto not initialized'));
|
9005
9087
|
}
|
9006
9088
|
this.logOnce('WebCrypto AES decrypt');
|
9007
|
-
const crypto = new AESCrypto(subtle, new Uint8Array(iv));
|
9089
|
+
const crypto = new AESCrypto(subtle, new Uint8Array(iv), aesMode);
|
9008
9090
|
return crypto.decrypt(data.buffer, aesKey);
|
9009
9091
|
}).catch(err => {
|
9010
9092
|
logger.warn(`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`);
|
9011
|
-
return this.onWebCryptoError(data, key, iv);
|
9093
|
+
return this.onWebCryptoError(data, key, iv, aesMode);
|
9012
9094
|
});
|
9013
9095
|
}
|
9014
|
-
onWebCryptoError(data, key, iv) {
|
9015
|
-
|
9016
|
-
|
9017
|
-
|
9018
|
-
|
9019
|
-
|
9020
|
-
|
9096
|
+
onWebCryptoError(data, key, iv, aesMode) {
|
9097
|
+
const enableSoftwareAES = this.enableSoftwareAES;
|
9098
|
+
if (enableSoftwareAES) {
|
9099
|
+
this.useSoftware = true;
|
9100
|
+
this.logEnabled = true;
|
9101
|
+
this.softwareDecrypt(data, key, iv, aesMode);
|
9102
|
+
const decryptResult = this.flush();
|
9103
|
+
if (decryptResult) {
|
9104
|
+
return decryptResult.buffer;
|
9105
|
+
}
|
9021
9106
|
}
|
9022
|
-
throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data');
|
9107
|
+
throw new Error('WebCrypto' + (enableSoftwareAES ? ' and softwareDecrypt' : '') + ': failed to decrypt data');
|
9023
9108
|
}
|
9024
9109
|
getValidChunk(data) {
|
9025
9110
|
let currentChunk = data;
|
@@ -9070,7 +9155,7 @@ const State = {
|
|
9070
9155
|
};
|
9071
9156
|
class BaseStreamController extends TaskLoop {
|
9072
9157
|
constructor(hls, fragmentTracker, keyLoader, logPrefix, playlistType) {
|
9073
|
-
super();
|
9158
|
+
super(logPrefix, hls.logger);
|
9074
9159
|
this.hls = void 0;
|
9075
9160
|
this.fragPrevious = null;
|
9076
9161
|
this.fragCurrent = null;
|
@@ -9095,22 +9180,89 @@ class BaseStreamController extends TaskLoop {
|
|
9095
9180
|
this.startFragRequested = false;
|
9096
9181
|
this.decrypter = void 0;
|
9097
9182
|
this.initPTS = [];
|
9098
|
-
this.
|
9099
|
-
this.
|
9100
|
-
|
9101
|
-
|
9102
|
-
|
9183
|
+
this.buffering = true;
|
9184
|
+
this.onMediaSeeking = () => {
|
9185
|
+
const {
|
9186
|
+
config,
|
9187
|
+
fragCurrent,
|
9188
|
+
media,
|
9189
|
+
mediaBuffer,
|
9190
|
+
state
|
9191
|
+
} = this;
|
9192
|
+
const currentTime = media ? media.currentTime : 0;
|
9193
|
+
const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
|
9194
|
+
this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
|
9195
|
+
if (this.state === State.ENDED) {
|
9196
|
+
this.resetLoadingState();
|
9197
|
+
} else if (fragCurrent) {
|
9198
|
+
// Seeking while frag load is in progress
|
9199
|
+
const tolerance = config.maxFragLookUpTolerance;
|
9200
|
+
const fragStartOffset = fragCurrent.start - tolerance;
|
9201
|
+
const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
|
9202
|
+
// if seeking out of buffered range or into new one
|
9203
|
+
if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
|
9204
|
+
const pastFragment = currentTime > fragEndOffset;
|
9205
|
+
// if the seek position is outside the current fragment range
|
9206
|
+
if (currentTime < fragStartOffset || pastFragment) {
|
9207
|
+
if (pastFragment && fragCurrent.loader) {
|
9208
|
+
this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
|
9209
|
+
fragCurrent.abortRequests();
|
9210
|
+
this.resetLoadingState();
|
9211
|
+
}
|
9212
|
+
this.fragPrevious = null;
|
9213
|
+
}
|
9214
|
+
}
|
9215
|
+
}
|
9216
|
+
if (media) {
|
9217
|
+
// Remove gap fragments
|
9218
|
+
this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
|
9219
|
+
this.lastCurrentTime = currentTime;
|
9220
|
+
}
|
9221
|
+
|
9222
|
+
// in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
|
9223
|
+
if (!this.loadedmetadata && !bufferInfo.len) {
|
9224
|
+
this.nextLoadPosition = this.startPosition = currentTime;
|
9225
|
+
}
|
9226
|
+
|
9227
|
+
// Async tick to speed up processing
|
9228
|
+
this.tickImmediate();
|
9229
|
+
};
|
9230
|
+
this.onMediaEnded = () => {
|
9231
|
+
// reset startPosition and lastCurrentTime to restart playback @ stream beginning
|
9232
|
+
this.startPosition = this.lastCurrentTime = 0;
|
9233
|
+
if (this.playlistType === PlaylistLevelType.MAIN) {
|
9234
|
+
this.hls.trigger(Events.MEDIA_ENDED, {
|
9235
|
+
stalled: false
|
9236
|
+
});
|
9237
|
+
}
|
9238
|
+
};
|
9103
9239
|
this.playlistType = playlistType;
|
9104
|
-
this.logPrefix = logPrefix;
|
9105
|
-
this.log = logger.log.bind(logger, `${logPrefix}:`);
|
9106
|
-
this.warn = logger.warn.bind(logger, `${logPrefix}:`);
|
9107
9240
|
this.hls = hls;
|
9108
9241
|
this.fragmentLoader = new FragmentLoader(hls.config);
|
9109
9242
|
this.keyLoader = keyLoader;
|
9110
9243
|
this.fragmentTracker = fragmentTracker;
|
9111
9244
|
this.config = hls.config;
|
9112
9245
|
this.decrypter = new Decrypter(hls.config);
|
9246
|
+
}
|
9247
|
+
registerListeners() {
|
9248
|
+
const {
|
9249
|
+
hls
|
9250
|
+
} = this;
|
9251
|
+
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
9252
|
+
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
9253
|
+
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
9113
9254
|
hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
9255
|
+
hls.on(Events.ERROR, this.onError, this);
|
9256
|
+
}
|
9257
|
+
unregisterListeners() {
|
9258
|
+
const {
|
9259
|
+
hls
|
9260
|
+
} = this;
|
9261
|
+
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
9262
|
+
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
9263
|
+
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
9264
|
+
hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
9265
|
+
hls.off(Events.ERROR, this.onError, this);
|
9114
9266
|
}
|
9115
9267
|
doTick() {
|
9116
9268
|
this.onTickEnd();
|
@@ -9134,6 +9286,12 @@ class BaseStreamController extends TaskLoop {
|
|
9134
9286
|
this.clearNextTick();
|
9135
9287
|
this.state = State.STOPPED;
|
9136
9288
|
}
|
9289
|
+
pauseBuffering() {
|
9290
|
+
this.buffering = false;
|
9291
|
+
}
|
9292
|
+
resumeBuffering() {
|
9293
|
+
this.buffering = true;
|
9294
|
+
}
|
9137
9295
|
_streamEnded(bufferInfo, levelDetails) {
|
9138
9296
|
// If playlist is live, there is another buffered range after the current range, nothing buffered, media is detached,
|
9139
9297
|
// of nothing loading/loaded return false
|
@@ -9164,10 +9322,8 @@ class BaseStreamController extends TaskLoop {
|
|
9164
9322
|
}
|
9165
9323
|
onMediaAttached(event, data) {
|
9166
9324
|
const media = this.media = this.mediaBuffer = data.media;
|
9167
|
-
|
9168
|
-
|
9169
|
-
media.addEventListener('seeking', this.onvseeking);
|
9170
|
-
media.addEventListener('ended', this.onvended);
|
9325
|
+
media.addEventListener('seeking', this.onMediaSeeking);
|
9326
|
+
media.addEventListener('ended', this.onMediaEnded);
|
9171
9327
|
const config = this.config;
|
9172
9328
|
if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
|
9173
9329
|
this.startLoad(config.startPosition);
|
@@ -9181,10 +9337,9 @@ class BaseStreamController extends TaskLoop {
|
|
9181
9337
|
}
|
9182
9338
|
|
9183
9339
|
// remove video listeners
|
9184
|
-
if (media
|
9185
|
-
media.removeEventListener('seeking', this.
|
9186
|
-
media.removeEventListener('ended', this.
|
9187
|
-
this.onvseeking = this.onvended = null;
|
9340
|
+
if (media) {
|
9341
|
+
media.removeEventListener('seeking', this.onMediaSeeking);
|
9342
|
+
media.removeEventListener('ended', this.onMediaEnded);
|
9188
9343
|
}
|
9189
9344
|
if (this.keyLoader) {
|
9190
9345
|
this.keyLoader.detach();
|
@@ -9194,56 +9349,8 @@ class BaseStreamController extends TaskLoop {
|
|
9194
9349
|
this.fragmentTracker.removeAllFragments();
|
9195
9350
|
this.stopLoad();
|
9196
9351
|
}
|
9197
|
-
|
9198
|
-
|
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
|
-
}
|
9352
|
+
onManifestLoading() {}
|
9353
|
+
onError(event, data) {}
|
9247
9354
|
onManifestLoaded(event, data) {
|
9248
9355
|
this.startTimeOffset = data.startTimeOffset;
|
9249
9356
|
this.initPTS = [];
|
@@ -9253,7 +9360,7 @@ class BaseStreamController extends TaskLoop {
|
|
9253
9360
|
this.stopLoad();
|
9254
9361
|
super.onHandlerDestroying();
|
9255
9362
|
// @ts-ignore
|
9256
|
-
this.hls = null;
|
9363
|
+
this.hls = this.onMediaSeeking = this.onMediaEnded = null;
|
9257
9364
|
}
|
9258
9365
|
onHandlerDestroyed() {
|
9259
9366
|
this.state = State.STOPPED;
|
@@ -9384,10 +9491,10 @@ class BaseStreamController extends TaskLoop {
|
|
9384
9491
|
const decryptData = frag.decryptdata;
|
9385
9492
|
|
9386
9493
|
// check to see if the payload needs to be decrypted
|
9387
|
-
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method
|
9494
|
+
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
|
9388
9495
|
const startTime = self.performance.now();
|
9389
9496
|
// decrypt init segment data
|
9390
|
-
return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
|
9497
|
+
return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch(err => {
|
9391
9498
|
hls.trigger(Events.ERROR, {
|
9392
9499
|
type: ErrorTypes.MEDIA_ERROR,
|
9393
9500
|
details: ErrorDetails.FRAG_DECRYPT_ERROR,
|
@@ -9499,7 +9606,7 @@ class BaseStreamController extends TaskLoop {
|
|
9499
9606
|
}
|
9500
9607
|
let keyLoadingPromise = null;
|
9501
9608
|
if (frag.encrypted && !((_frag$decryptdata = frag.decryptdata) != null && _frag$decryptdata.key)) {
|
9502
|
-
this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.
|
9609
|
+
this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'} ${frag.level}`);
|
9503
9610
|
this.state = State.KEY_LOADING;
|
9504
9611
|
this.fragCurrent = frag;
|
9505
9612
|
keyLoadingPromise = this.keyLoader.load(frag).then(keyLoadedData => {
|
@@ -9530,7 +9637,7 @@ class BaseStreamController extends TaskLoop {
|
|
9530
9637
|
const partIndex = this.getNextPart(partList, frag, targetBufferTime);
|
9531
9638
|
if (partIndex > -1) {
|
9532
9639
|
const part = partList[partIndex];
|
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.
|
9640
|
+
this.log(`Loading part sn: ${frag.sn} p: ${part.index} cc: ${frag.cc} of playlist [${details.startSN}-${details.endSN}] parts [0-${partIndex}-${partList.length - 1}] ${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
|
9534
9641
|
this.nextLoadPosition = part.start + part.duration;
|
9535
9642
|
this.state = State.FRAG_LOADING;
|
9536
9643
|
let _result;
|
@@ -9559,7 +9666,7 @@ class BaseStreamController extends TaskLoop {
|
|
9559
9666
|
}
|
9560
9667
|
}
|
9561
9668
|
}
|
9562
|
-
this.log(`Loading fragment ${frag.sn} cc: ${frag.cc} ${details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''}${this.
|
9669
|
+
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))}`);
|
9563
9670
|
// Don't update nextLoadPosition for fragments which are not buffered
|
9564
9671
|
if (isFiniteNumber(frag.sn) && !this.bitrateTest) {
|
9565
9672
|
this.nextLoadPosition = frag.start + frag.duration;
|
@@ -10144,7 +10251,7 @@ class BaseStreamController extends TaskLoop {
|
|
10144
10251
|
errorAction.resolved = true;
|
10145
10252
|
}
|
10146
10253
|
} else {
|
10147
|
-
|
10254
|
+
this.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
|
10148
10255
|
return;
|
10149
10256
|
}
|
10150
10257
|
} else if ((errorAction == null ? void 0 : errorAction.action) === NetworkErrorAction.SendAlternateToPenaltyBox) {
|
@@ -10553,6 +10660,7 @@ const initPTSFn = (timestamp, timeOffset, initPTS) => {
|
|
10553
10660
|
*/
|
10554
10661
|
function getAudioConfig(observer, data, offset, audioCodec) {
|
10555
10662
|
let adtsObjectType;
|
10663
|
+
let originalAdtsObjectType;
|
10556
10664
|
let adtsExtensionSamplingIndex;
|
10557
10665
|
let adtsChannelConfig;
|
10558
10666
|
let config;
|
@@ -10560,7 +10668,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
10560
10668
|
const manifestCodec = audioCodec;
|
10561
10669
|
const adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
|
10562
10670
|
// byte 2
|
10563
|
-
adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
|
10671
|
+
adtsObjectType = originalAdtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
|
10564
10672
|
const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
|
10565
10673
|
if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
|
10566
10674
|
const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
|
@@ -10577,8 +10685,8 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
10577
10685
|
// byte 3
|
10578
10686
|
adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6;
|
10579
10687
|
logger.log(`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`);
|
10580
|
-
//
|
10581
|
-
if (/firefox/i.test(userAgent)) {
|
10688
|
+
// Firefox and Pale Moon: freq less than 24kHz = AAC SBR (HE-AAC)
|
10689
|
+
if (/firefox|palemoon/i.test(userAgent)) {
|
10582
10690
|
if (adtsSamplingIndex >= 6) {
|
10583
10691
|
adtsObjectType = 5;
|
10584
10692
|
config = new Array(4);
|
@@ -10672,6 +10780,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
10672
10780
|
samplerate: adtsSamplingRates[adtsSamplingIndex],
|
10673
10781
|
channelCount: adtsChannelConfig,
|
10674
10782
|
codec: 'mp4a.40.' + adtsObjectType,
|
10783
|
+
parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
|
10675
10784
|
manifestCodec
|
10676
10785
|
};
|
10677
10786
|
}
|
@@ -10726,7 +10835,8 @@ function initTrackConfig(track, observer, data, offset, audioCodec) {
|
|
10726
10835
|
track.channelCount = config.channelCount;
|
10727
10836
|
track.codec = config.codec;
|
10728
10837
|
track.manifestCodec = config.manifestCodec;
|
10729
|
-
|
10838
|
+
track.parsedCodec = config.parsedCodec;
|
10839
|
+
logger.log(`parsed codec:${track.parsedCodec}, codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
|
10730
10840
|
}
|
10731
10841
|
}
|
10732
10842
|
function getFrameDuration(samplerate) {
|
@@ -11317,6 +11427,110 @@ class BaseVideoParser {
|
|
11317
11427
|
logger.log(VideoSample.pts + '/' + VideoSample.dts + ':' + VideoSample.debug);
|
11318
11428
|
}
|
11319
11429
|
}
|
11430
|
+
parseNALu(track, array) {
|
11431
|
+
const len = array.byteLength;
|
11432
|
+
let state = track.naluState || 0;
|
11433
|
+
const lastState = state;
|
11434
|
+
const units = [];
|
11435
|
+
let i = 0;
|
11436
|
+
let value;
|
11437
|
+
let overflow;
|
11438
|
+
let unitType;
|
11439
|
+
let lastUnitStart = -1;
|
11440
|
+
let lastUnitType = 0;
|
11441
|
+
// logger.log('PES:' + Hex.hexDump(array));
|
11442
|
+
|
11443
|
+
if (state === -1) {
|
11444
|
+
// special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
|
11445
|
+
lastUnitStart = 0;
|
11446
|
+
// NALu type is value read from offset 0
|
11447
|
+
lastUnitType = this.getNALuType(array, 0);
|
11448
|
+
state = 0;
|
11449
|
+
i = 1;
|
11450
|
+
}
|
11451
|
+
while (i < len) {
|
11452
|
+
value = array[i++];
|
11453
|
+
// optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
|
11454
|
+
if (!state) {
|
11455
|
+
state = value ? 0 : 1;
|
11456
|
+
continue;
|
11457
|
+
}
|
11458
|
+
if (state === 1) {
|
11459
|
+
state = value ? 0 : 2;
|
11460
|
+
continue;
|
11461
|
+
}
|
11462
|
+
// here we have state either equal to 2 or 3
|
11463
|
+
if (!value) {
|
11464
|
+
state = 3;
|
11465
|
+
} else if (value === 1) {
|
11466
|
+
overflow = i - state - 1;
|
11467
|
+
if (lastUnitStart >= 0) {
|
11468
|
+
const unit = {
|
11469
|
+
data: array.subarray(lastUnitStart, overflow),
|
11470
|
+
type: lastUnitType
|
11471
|
+
};
|
11472
|
+
// logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
|
11473
|
+
units.push(unit);
|
11474
|
+
} else {
|
11475
|
+
// lastUnitStart is undefined => this is the first start code found in this PES packet
|
11476
|
+
// first check if start code delimiter is overlapping between 2 PES packets,
|
11477
|
+
// ie it started in last packet (lastState not zero)
|
11478
|
+
// and ended at the beginning of this PES packet (i <= 4 - lastState)
|
11479
|
+
const lastUnit = this.getLastNalUnit(track.samples);
|
11480
|
+
if (lastUnit) {
|
11481
|
+
if (lastState && i <= 4 - lastState) {
|
11482
|
+
// start delimiter overlapping between PES packets
|
11483
|
+
// strip start delimiter bytes from the end of last NAL unit
|
11484
|
+
// check if lastUnit had a state different from zero
|
11485
|
+
if (lastUnit.state) {
|
11486
|
+
// strip last bytes
|
11487
|
+
lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
|
11488
|
+
}
|
11489
|
+
}
|
11490
|
+
// If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
|
11491
|
+
|
11492
|
+
if (overflow > 0) {
|
11493
|
+
// logger.log('first NALU found with overflow:' + overflow);
|
11494
|
+
lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
|
11495
|
+
lastUnit.state = 0;
|
11496
|
+
}
|
11497
|
+
}
|
11498
|
+
}
|
11499
|
+
// check if we can read unit type
|
11500
|
+
if (i < len) {
|
11501
|
+
unitType = this.getNALuType(array, i);
|
11502
|
+
// logger.log('find NALU @ offset:' + i + ',type:' + unitType);
|
11503
|
+
lastUnitStart = i;
|
11504
|
+
lastUnitType = unitType;
|
11505
|
+
state = 0;
|
11506
|
+
} else {
|
11507
|
+
// not enough byte to read unit type. let's read it on next PES parsing
|
11508
|
+
state = -1;
|
11509
|
+
}
|
11510
|
+
} else {
|
11511
|
+
state = 0;
|
11512
|
+
}
|
11513
|
+
}
|
11514
|
+
if (lastUnitStart >= 0 && state >= 0) {
|
11515
|
+
const unit = {
|
11516
|
+
data: array.subarray(lastUnitStart, len),
|
11517
|
+
type: lastUnitType,
|
11518
|
+
state: state
|
11519
|
+
};
|
11520
|
+
units.push(unit);
|
11521
|
+
// logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
|
11522
|
+
}
|
11523
|
+
// no NALu found
|
11524
|
+
if (units.length === 0) {
|
11525
|
+
// append pes.data to previous NAL unit
|
11526
|
+
const lastUnit = this.getLastNalUnit(track.samples);
|
11527
|
+
if (lastUnit) {
|
11528
|
+
lastUnit.data = appendUint8Array(lastUnit.data, array);
|
11529
|
+
}
|
11530
|
+
}
|
11531
|
+
track.naluState = state;
|
11532
|
+
return units;
|
11533
|
+
}
|
11320
11534
|
}
|
11321
11535
|
|
11322
11536
|
/**
|
@@ -11459,21 +11673,171 @@ class ExpGolomb {
|
|
11459
11673
|
readUInt() {
|
11460
11674
|
return this.readBits(32);
|
11461
11675
|
}
|
11676
|
+
}
|
11462
11677
|
|
11463
|
-
|
11464
|
-
|
11465
|
-
|
11466
|
-
|
11467
|
-
|
11678
|
+
class AvcVideoParser extends BaseVideoParser {
|
11679
|
+
parsePES(track, textTrack, pes, last, duration) {
|
11680
|
+
const units = this.parseNALu(track, pes.data);
|
11681
|
+
let VideoSample = this.VideoSample;
|
11682
|
+
let push;
|
11683
|
+
let spsfound = false;
|
11684
|
+
// free pes.data to save up some memory
|
11685
|
+
pes.data = null;
|
11686
|
+
|
11687
|
+
// if new NAL units found and last sample still there, let's push ...
|
11688
|
+
// this helps parsing streams with missing AUD (only do this if AUD never found)
|
11689
|
+
if (VideoSample && units.length && !track.audFound) {
|
11690
|
+
this.pushAccessUnit(VideoSample, track);
|
11691
|
+
VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
|
11692
|
+
}
|
11693
|
+
units.forEach(unit => {
|
11694
|
+
var _VideoSample2;
|
11695
|
+
switch (unit.type) {
|
11696
|
+
// NDR
|
11697
|
+
case 1:
|
11698
|
+
{
|
11699
|
+
let iskey = false;
|
11700
|
+
push = true;
|
11701
|
+
const data = unit.data;
|
11702
|
+
// only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
|
11703
|
+
if (spsfound && data.length > 4) {
|
11704
|
+
// retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
|
11705
|
+
const sliceType = this.readSliceType(data);
|
11706
|
+
// 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
|
11707
|
+
// SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
|
11708
|
+
// An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
|
11709
|
+
// I slice: A slice that is not an SI slice that is decoded using intra prediction only.
|
11710
|
+
// if (sliceType === 2 || sliceType === 7) {
|
11711
|
+
if (sliceType === 2 || sliceType === 4 || sliceType === 7 || sliceType === 9) {
|
11712
|
+
iskey = true;
|
11713
|
+
}
|
11714
|
+
}
|
11715
|
+
if (iskey) {
|
11716
|
+
var _VideoSample;
|
11717
|
+
// if we have non-keyframe data already, that cannot belong to the same frame as a keyframe, so force a push
|
11718
|
+
if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
|
11719
|
+
this.pushAccessUnit(VideoSample, track);
|
11720
|
+
VideoSample = this.VideoSample = null;
|
11721
|
+
}
|
11722
|
+
}
|
11723
|
+
if (!VideoSample) {
|
11724
|
+
VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
|
11725
|
+
}
|
11726
|
+
VideoSample.frame = true;
|
11727
|
+
VideoSample.key = iskey;
|
11728
|
+
break;
|
11729
|
+
// IDR
|
11730
|
+
}
|
11731
|
+
case 5:
|
11732
|
+
push = true;
|
11733
|
+
// handle PES not starting with AUD
|
11734
|
+
// if we have frame data already, that cannot belong to the same frame, so force a push
|
11735
|
+
if ((_VideoSample2 = VideoSample) != null && _VideoSample2.frame && !VideoSample.key) {
|
11736
|
+
this.pushAccessUnit(VideoSample, track);
|
11737
|
+
VideoSample = this.VideoSample = null;
|
11738
|
+
}
|
11739
|
+
if (!VideoSample) {
|
11740
|
+
VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
|
11741
|
+
}
|
11742
|
+
VideoSample.key = true;
|
11743
|
+
VideoSample.frame = true;
|
11744
|
+
break;
|
11745
|
+
// SEI
|
11746
|
+
case 6:
|
11747
|
+
{
|
11748
|
+
push = true;
|
11749
|
+
parseSEIMessageFromNALu(unit.data, 1, pes.pts, textTrack.samples);
|
11750
|
+
break;
|
11751
|
+
// SPS
|
11752
|
+
}
|
11753
|
+
case 7:
|
11754
|
+
{
|
11755
|
+
var _track$pixelRatio, _track$pixelRatio2;
|
11756
|
+
push = true;
|
11757
|
+
spsfound = true;
|
11758
|
+
const sps = unit.data;
|
11759
|
+
const config = this.readSPS(sps);
|
11760
|
+
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]) {
|
11761
|
+
track.width = config.width;
|
11762
|
+
track.height = config.height;
|
11763
|
+
track.pixelRatio = config.pixelRatio;
|
11764
|
+
track.sps = [sps];
|
11765
|
+
track.duration = duration;
|
11766
|
+
const codecarray = sps.subarray(1, 4);
|
11767
|
+
let codecstring = 'avc1.';
|
11768
|
+
for (let i = 0; i < 3; i++) {
|
11769
|
+
let h = codecarray[i].toString(16);
|
11770
|
+
if (h.length < 2) {
|
11771
|
+
h = '0' + h;
|
11772
|
+
}
|
11773
|
+
codecstring += h;
|
11774
|
+
}
|
11775
|
+
track.codec = codecstring;
|
11776
|
+
}
|
11777
|
+
break;
|
11778
|
+
}
|
11779
|
+
// PPS
|
11780
|
+
case 8:
|
11781
|
+
push = true;
|
11782
|
+
track.pps = [unit.data];
|
11783
|
+
break;
|
11784
|
+
// AUD
|
11785
|
+
case 9:
|
11786
|
+
push = true;
|
11787
|
+
track.audFound = true;
|
11788
|
+
if (VideoSample) {
|
11789
|
+
this.pushAccessUnit(VideoSample, track);
|
11790
|
+
}
|
11791
|
+
VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
|
11792
|
+
break;
|
11793
|
+
// Filler Data
|
11794
|
+
case 12:
|
11795
|
+
push = true;
|
11796
|
+
break;
|
11797
|
+
default:
|
11798
|
+
push = false;
|
11799
|
+
if (VideoSample) {
|
11800
|
+
VideoSample.debug += 'unknown NAL ' + unit.type + ' ';
|
11801
|
+
}
|
11802
|
+
break;
|
11803
|
+
}
|
11804
|
+
if (VideoSample && push) {
|
11805
|
+
const units = VideoSample.units;
|
11806
|
+
units.push(unit);
|
11807
|
+
}
|
11808
|
+
});
|
11809
|
+
// if last PES packet, push samples
|
11810
|
+
if (last && VideoSample) {
|
11811
|
+
this.pushAccessUnit(VideoSample, track);
|
11812
|
+
this.VideoSample = null;
|
11813
|
+
}
|
11814
|
+
}
|
11815
|
+
getNALuType(data, offset) {
|
11816
|
+
return data[offset] & 0x1f;
|
11817
|
+
}
|
11818
|
+
readSliceType(data) {
|
11819
|
+
const eg = new ExpGolomb(data);
|
11820
|
+
// skip NALu type
|
11821
|
+
eg.readUByte();
|
11822
|
+
// discard first_mb_in_slice
|
11823
|
+
eg.readUEG();
|
11824
|
+
// return slice_type
|
11825
|
+
return eg.readUEG();
|
11826
|
+
}
|
11827
|
+
|
11828
|
+
/**
|
11829
|
+
* The scaling list is optionally transmitted as part of a sequence parameter
|
11830
|
+
* set and is not relevant to transmuxing.
|
11831
|
+
* @param count the number of entries in this scaling list
|
11468
11832
|
* @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
|
11469
11833
|
*/
|
11470
|
-
skipScalingList(count) {
|
11834
|
+
skipScalingList(count, reader) {
|
11471
11835
|
let lastScale = 8;
|
11472
11836
|
let nextScale = 8;
|
11473
11837
|
let deltaScale;
|
11474
11838
|
for (let j = 0; j < count; j++) {
|
11475
11839
|
if (nextScale !== 0) {
|
11476
|
-
deltaScale =
|
11840
|
+
deltaScale = reader.readEG();
|
11477
11841
|
nextScale = (lastScale + deltaScale + 256) % 256;
|
11478
11842
|
}
|
11479
11843
|
lastScale = nextScale === 0 ? lastScale : nextScale;
|
@@ -11488,7 +11852,8 @@ class ExpGolomb {
|
|
11488
11852
|
* sequence parameter set, including the dimensions of the
|
11489
11853
|
* associated video frames.
|
11490
11854
|
*/
|
11491
|
-
readSPS() {
|
11855
|
+
readSPS(sps) {
|
11856
|
+
const eg = new ExpGolomb(sps);
|
11492
11857
|
let frameCropLeftOffset = 0;
|
11493
11858
|
let frameCropRightOffset = 0;
|
11494
11859
|
let frameCropTopOffset = 0;
|
@@ -11496,13 +11861,13 @@ class ExpGolomb {
|
|
11496
11861
|
let numRefFramesInPicOrderCntCycle;
|
11497
11862
|
let scalingListCount;
|
11498
11863
|
let i;
|
11499
|
-
const readUByte =
|
11500
|
-
const readBits =
|
11501
|
-
const readUEG =
|
11502
|
-
const readBoolean =
|
11503
|
-
const skipBits =
|
11504
|
-
const skipEG =
|
11505
|
-
const skipUEG =
|
11864
|
+
const readUByte = eg.readUByte.bind(eg);
|
11865
|
+
const readBits = eg.readBits.bind(eg);
|
11866
|
+
const readUEG = eg.readUEG.bind(eg);
|
11867
|
+
const readBoolean = eg.readBoolean.bind(eg);
|
11868
|
+
const skipBits = eg.skipBits.bind(eg);
|
11869
|
+
const skipEG = eg.skipEG.bind(eg);
|
11870
|
+
const skipUEG = eg.skipUEG.bind(eg);
|
11506
11871
|
const skipScalingList = this.skipScalingList.bind(this);
|
11507
11872
|
readUByte();
|
11508
11873
|
const profileIdc = readUByte(); // profile_idc
|
@@ -11527,9 +11892,9 @@ class ExpGolomb {
|
|
11527
11892
|
if (readBoolean()) {
|
11528
11893
|
// seq_scaling_list_present_flag[ i ]
|
11529
11894
|
if (i < 6) {
|
11530
|
-
skipScalingList(16);
|
11895
|
+
skipScalingList(16, eg);
|
11531
11896
|
} else {
|
11532
|
-
skipScalingList(64);
|
11897
|
+
skipScalingList(64, eg);
|
11533
11898
|
}
|
11534
11899
|
}
|
11535
11900
|
}
|
@@ -11634,19 +11999,15 @@ class ExpGolomb {
|
|
11634
11999
|
pixelRatio: pixelRatio
|
11635
12000
|
};
|
11636
12001
|
}
|
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
|
-
}
|
11645
12002
|
}
|
11646
12003
|
|
11647
|
-
class
|
11648
|
-
|
11649
|
-
|
12004
|
+
class HevcVideoParser extends BaseVideoParser {
|
12005
|
+
constructor(...args) {
|
12006
|
+
super(...args);
|
12007
|
+
this.initVPS = null;
|
12008
|
+
}
|
12009
|
+
parsePES(track, textTrack, pes, last, duration) {
|
12010
|
+
const units = this.parseNALu(track, pes.data);
|
11650
12011
|
let VideoSample = this.VideoSample;
|
11651
12012
|
let push;
|
11652
12013
|
let spsfound = false;
|
@@ -11662,42 +12023,49 @@ class AvcVideoParser extends BaseVideoParser {
|
|
11662
12023
|
units.forEach(unit => {
|
11663
12024
|
var _VideoSample2;
|
11664
12025
|
switch (unit.type) {
|
11665
|
-
//
|
12026
|
+
// NON-IDR, NON RANDOM ACCESS SLICE
|
12027
|
+
case 0:
|
11666
12028
|
case 1:
|
11667
|
-
|
11668
|
-
|
11669
|
-
|
11670
|
-
|
11671
|
-
|
11672
|
-
|
11673
|
-
|
11674
|
-
|
11675
|
-
|
11676
|
-
|
11677
|
-
|
11678
|
-
|
11679
|
-
|
11680
|
-
|
11681
|
-
|
11682
|
-
|
11683
|
-
|
11684
|
-
|
11685
|
-
|
11686
|
-
|
11687
|
-
|
11688
|
-
|
11689
|
-
|
11690
|
-
|
11691
|
-
|
11692
|
-
if (!VideoSample) {
|
11693
|
-
|
12029
|
+
case 2:
|
12030
|
+
case 3:
|
12031
|
+
case 4:
|
12032
|
+
case 5:
|
12033
|
+
case 6:
|
12034
|
+
case 7:
|
12035
|
+
case 8:
|
12036
|
+
case 9:
|
12037
|
+
if (!VideoSample) {
|
12038
|
+
VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
|
12039
|
+
}
|
12040
|
+
VideoSample.frame = true;
|
12041
|
+
push = true;
|
12042
|
+
break;
|
12043
|
+
|
12044
|
+
// CRA, BLA (random access picture)
|
12045
|
+
case 16:
|
12046
|
+
case 17:
|
12047
|
+
case 18:
|
12048
|
+
case 21:
|
12049
|
+
push = true;
|
12050
|
+
if (spsfound) {
|
12051
|
+
var _VideoSample;
|
12052
|
+
// handle PES not starting with AUD
|
12053
|
+
// if we have frame data already, that cannot belong to the same frame, so force a push
|
12054
|
+
if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
|
12055
|
+
this.pushAccessUnit(VideoSample, track);
|
12056
|
+
VideoSample = this.VideoSample = null;
|
11694
12057
|
}
|
11695
|
-
VideoSample.frame = true;
|
11696
|
-
VideoSample.key = iskey;
|
11697
|
-
break;
|
11698
|
-
// IDR
|
11699
12058
|
}
|
11700
|
-
|
12059
|
+
if (!VideoSample) {
|
12060
|
+
VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
|
12061
|
+
}
|
12062
|
+
VideoSample.key = true;
|
12063
|
+
VideoSample.frame = true;
|
12064
|
+
break;
|
12065
|
+
|
12066
|
+
// IDR
|
12067
|
+
case 19:
|
12068
|
+
case 20:
|
11701
12069
|
push = true;
|
11702
12070
|
// handle PES not starting with AUD
|
11703
12071
|
// if we have frame data already, that cannot belong to the same frame, so force a push
|
@@ -11711,180 +12079,518 @@ class AvcVideoParser extends BaseVideoParser {
|
|
11711
12079
|
VideoSample.key = true;
|
11712
12080
|
VideoSample.frame = true;
|
11713
12081
|
break;
|
12082
|
+
|
11714
12083
|
// SEI
|
11715
|
-
case
|
11716
|
-
{
|
11717
|
-
push = true;
|
11718
|
-
parseSEIMessageFromNALu(unit.data, 1, pes.pts, textTrack.samples);
|
11719
|
-
break;
|
11720
|
-
// SPS
|
11721
|
-
}
|
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]) {
|
11731
|
-
track.width = config.width;
|
11732
|
-
track.height = config.height;
|
11733
|
-
track.pixelRatio = config.pixelRatio;
|
11734
|
-
track.sps = [sps];
|
11735
|
-
track.duration = duration;
|
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;
|
11744
|
-
}
|
11745
|
-
track.codec = codecstring;
|
11746
|
-
}
|
11747
|
-
break;
|
11748
|
-
}
|
11749
|
-
// PPS
|
11750
|
-
case 8:
|
11751
|
-
push = true;
|
11752
|
-
track.pps = [unit.data];
|
11753
|
-
break;
|
11754
|
-
// AUD
|
11755
|
-
case 9:
|
12084
|
+
case 39:
|
11756
12085
|
push = true;
|
11757
|
-
|
11758
|
-
|
11759
|
-
|
11760
|
-
}
|
11761
|
-
VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
|
12086
|
+
parseSEIMessageFromNALu(unit.data, 2,
|
12087
|
+
// NALu header size
|
12088
|
+
pes.pts, textTrack.samples);
|
11762
12089
|
break;
|
11763
|
-
|
11764
|
-
|
12090
|
+
|
12091
|
+
// VPS
|
12092
|
+
case 32:
|
11765
12093
|
push = true;
|
11766
|
-
|
11767
|
-
|
11768
|
-
|
11769
|
-
|
11770
|
-
VideoSample.debug += 'unknown NAL ' + unit.type + ' ';
|
12094
|
+
if (!track.vps) {
|
12095
|
+
const config = this.readVPS(unit.data);
|
12096
|
+
track.params = _objectSpread2({}, config);
|
12097
|
+
this.initVPS = unit.data;
|
11771
12098
|
}
|
12099
|
+
track.vps = [unit.data];
|
11772
12100
|
break;
|
11773
|
-
}
|
11774
|
-
if (VideoSample && push) {
|
11775
|
-
const units = VideoSample.units;
|
11776
|
-
units.push(unit);
|
11777
|
-
}
|
11778
|
-
});
|
11779
|
-
// if last PES packet, push samples
|
11780
|
-
if (last && VideoSample) {
|
11781
|
-
this.pushAccessUnit(VideoSample, track);
|
11782
|
-
this.VideoSample = null;
|
11783
|
-
}
|
11784
|
-
}
|
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));
|
11797
12101
|
|
11798
|
-
|
11799
|
-
|
11800
|
-
|
11801
|
-
|
11802
|
-
|
11803
|
-
|
11804
|
-
|
11805
|
-
|
11806
|
-
|
11807
|
-
|
11808
|
-
|
11809
|
-
|
11810
|
-
|
11811
|
-
|
11812
|
-
|
11813
|
-
|
11814
|
-
|
11815
|
-
|
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);
|
12102
|
+
// SPS
|
12103
|
+
case 33:
|
12104
|
+
push = true;
|
12105
|
+
spsfound = true;
|
12106
|
+
if (typeof track.params === 'object') {
|
12107
|
+
if (track.vps !== undefined && track.vps[0] !== this.initVPS && track.sps !== undefined && !this.matchSPS(track.sps[0], unit.data)) {
|
12108
|
+
this.initVPS = track.vps[0];
|
12109
|
+
track.sps = track.pps = undefined;
|
12110
|
+
}
|
12111
|
+
if (!track.sps) {
|
12112
|
+
const config = this.readSPS(unit.data);
|
12113
|
+
track.width = config.width;
|
12114
|
+
track.height = config.height;
|
12115
|
+
track.pixelRatio = config.pixelRatio;
|
12116
|
+
track.duration = duration;
|
12117
|
+
track.codec = config.codecString;
|
12118
|
+
track.sps = [];
|
12119
|
+
for (const prop in config.params) {
|
12120
|
+
track.params[prop] = config.params[prop];
|
11843
12121
|
}
|
11844
12122
|
}
|
11845
|
-
|
12123
|
+
if (track.vps !== undefined && track.vps[0] === this.initVPS) {
|
12124
|
+
track.sps.push(unit.data);
|
12125
|
+
}
|
12126
|
+
}
|
12127
|
+
if (!VideoSample) {
|
12128
|
+
VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
|
12129
|
+
}
|
12130
|
+
VideoSample.key = true;
|
12131
|
+
break;
|
11846
12132
|
|
11847
|
-
|
11848
|
-
|
11849
|
-
|
11850
|
-
|
12133
|
+
// PPS
|
12134
|
+
case 34:
|
12135
|
+
push = true;
|
12136
|
+
if (typeof track.params === 'object') {
|
12137
|
+
if (!track.pps) {
|
12138
|
+
track.pps = [];
|
12139
|
+
const config = this.readPPS(unit.data);
|
12140
|
+
for (const prop in config) {
|
12141
|
+
track.params[prop] = config[prop];
|
12142
|
+
}
|
12143
|
+
}
|
12144
|
+
if (this.initVPS !== null || track.pps.length === 0) {
|
12145
|
+
track.pps.push(unit.data);
|
11851
12146
|
}
|
11852
12147
|
}
|
12148
|
+
break;
|
12149
|
+
|
12150
|
+
// ACCESS UNIT DELIMITER
|
12151
|
+
case 35:
|
12152
|
+
push = true;
|
12153
|
+
track.audFound = true;
|
12154
|
+
if (VideoSample) {
|
12155
|
+
this.pushAccessUnit(VideoSample, track);
|
12156
|
+
}
|
12157
|
+
VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
|
12158
|
+
break;
|
12159
|
+
default:
|
12160
|
+
push = false;
|
12161
|
+
if (VideoSample) {
|
12162
|
+
VideoSample.debug += 'unknown or irrelevant NAL ' + unit.type + ' ';
|
12163
|
+
}
|
12164
|
+
break;
|
12165
|
+
}
|
12166
|
+
if (VideoSample && push) {
|
12167
|
+
const units = VideoSample.units;
|
12168
|
+
units.push(unit);
|
12169
|
+
}
|
12170
|
+
});
|
12171
|
+
// if last PES packet, push samples
|
12172
|
+
if (last && VideoSample) {
|
12173
|
+
this.pushAccessUnit(VideoSample, track);
|
12174
|
+
this.VideoSample = null;
|
12175
|
+
}
|
12176
|
+
}
|
12177
|
+
getNALuType(data, offset) {
|
12178
|
+
return (data[offset] & 0x7e) >>> 1;
|
12179
|
+
}
|
12180
|
+
ebsp2rbsp(arr) {
|
12181
|
+
const dst = new Uint8Array(arr.byteLength);
|
12182
|
+
let dstIdx = 0;
|
12183
|
+
for (let i = 0; i < arr.byteLength; i++) {
|
12184
|
+
if (i >= 2) {
|
12185
|
+
// Unescape: Skip 0x03 after 00 00
|
12186
|
+
if (arr[i] === 0x03 && arr[i - 1] === 0x00 && arr[i - 2] === 0x00) {
|
12187
|
+
continue;
|
11853
12188
|
}
|
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;
|
11864
|
-
}
|
11865
|
-
} else {
|
11866
|
-
state = 0;
|
11867
12189
|
}
|
12190
|
+
dst[dstIdx] = arr[i];
|
12191
|
+
dstIdx++;
|
11868
12192
|
}
|
11869
|
-
|
11870
|
-
|
11871
|
-
|
11872
|
-
|
11873
|
-
|
11874
|
-
|
11875
|
-
|
11876
|
-
|
12193
|
+
return new Uint8Array(dst.buffer, 0, dstIdx);
|
12194
|
+
}
|
12195
|
+
readVPS(vps) {
|
12196
|
+
const eg = new ExpGolomb(vps);
|
12197
|
+
// remove header
|
12198
|
+
eg.readUByte();
|
12199
|
+
eg.readUByte();
|
12200
|
+
eg.readBits(4); // video_parameter_set_id
|
12201
|
+
eg.skipBits(2);
|
12202
|
+
eg.readBits(6); // max_layers_minus1
|
12203
|
+
const max_sub_layers_minus1 = eg.readBits(3);
|
12204
|
+
const temporal_id_nesting_flag = eg.readBoolean();
|
12205
|
+
// ...vui fps can be here, but empty fps value is not critical for metadata
|
12206
|
+
|
12207
|
+
return {
|
12208
|
+
numTemporalLayers: max_sub_layers_minus1 + 1,
|
12209
|
+
temporalIdNested: temporal_id_nesting_flag
|
12210
|
+
};
|
12211
|
+
}
|
12212
|
+
readSPS(sps) {
|
12213
|
+
const eg = new ExpGolomb(this.ebsp2rbsp(sps));
|
12214
|
+
eg.readUByte();
|
12215
|
+
eg.readUByte();
|
12216
|
+
eg.readBits(4); //video_parameter_set_id
|
12217
|
+
const max_sub_layers_minus1 = eg.readBits(3);
|
12218
|
+
eg.readBoolean(); // temporal_id_nesting_flag
|
12219
|
+
|
12220
|
+
// profile_tier_level
|
12221
|
+
const general_profile_space = eg.readBits(2);
|
12222
|
+
const general_tier_flag = eg.readBoolean();
|
12223
|
+
const general_profile_idc = eg.readBits(5);
|
12224
|
+
const general_profile_compatibility_flags_1 = eg.readUByte();
|
12225
|
+
const general_profile_compatibility_flags_2 = eg.readUByte();
|
12226
|
+
const general_profile_compatibility_flags_3 = eg.readUByte();
|
12227
|
+
const general_profile_compatibility_flags_4 = eg.readUByte();
|
12228
|
+
const general_constraint_indicator_flags_1 = eg.readUByte();
|
12229
|
+
const general_constraint_indicator_flags_2 = eg.readUByte();
|
12230
|
+
const general_constraint_indicator_flags_3 = eg.readUByte();
|
12231
|
+
const general_constraint_indicator_flags_4 = eg.readUByte();
|
12232
|
+
const general_constraint_indicator_flags_5 = eg.readUByte();
|
12233
|
+
const general_constraint_indicator_flags_6 = eg.readUByte();
|
12234
|
+
const general_level_idc = eg.readUByte();
|
12235
|
+
const sub_layer_profile_present_flags = [];
|
12236
|
+
const sub_layer_level_present_flags = [];
|
12237
|
+
for (let i = 0; i < max_sub_layers_minus1; i++) {
|
12238
|
+
sub_layer_profile_present_flags.push(eg.readBoolean());
|
12239
|
+
sub_layer_level_present_flags.push(eg.readBoolean());
|
12240
|
+
}
|
12241
|
+
if (max_sub_layers_minus1 > 0) {
|
12242
|
+
for (let i = max_sub_layers_minus1; i < 8; i++) {
|
12243
|
+
eg.readBits(2);
|
12244
|
+
}
|
12245
|
+
}
|
12246
|
+
for (let i = 0; i < max_sub_layers_minus1; i++) {
|
12247
|
+
if (sub_layer_profile_present_flags[i]) {
|
12248
|
+
eg.readUByte(); // sub_layer_profile_space, sub_layer_tier_flag, sub_layer_profile_idc
|
12249
|
+
eg.readUByte();
|
12250
|
+
eg.readUByte();
|
12251
|
+
eg.readUByte();
|
12252
|
+
eg.readUByte(); // sub_layer_profile_compatibility_flag
|
12253
|
+
eg.readUByte();
|
12254
|
+
eg.readUByte();
|
12255
|
+
eg.readUByte();
|
12256
|
+
eg.readUByte();
|
12257
|
+
eg.readUByte();
|
12258
|
+
eg.readUByte();
|
12259
|
+
}
|
12260
|
+
if (sub_layer_level_present_flags[i]) {
|
12261
|
+
eg.readUByte();
|
12262
|
+
}
|
12263
|
+
}
|
12264
|
+
eg.readUEG(); // seq_parameter_set_id
|
12265
|
+
const chroma_format_idc = eg.readUEG();
|
12266
|
+
if (chroma_format_idc == 3) {
|
12267
|
+
eg.skipBits(1); //separate_colour_plane_flag
|
12268
|
+
}
|
12269
|
+
const pic_width_in_luma_samples = eg.readUEG();
|
12270
|
+
const pic_height_in_luma_samples = eg.readUEG();
|
12271
|
+
const conformance_window_flag = eg.readBoolean();
|
12272
|
+
let pic_left_offset = 0,
|
12273
|
+
pic_right_offset = 0,
|
12274
|
+
pic_top_offset = 0,
|
12275
|
+
pic_bottom_offset = 0;
|
12276
|
+
if (conformance_window_flag) {
|
12277
|
+
pic_left_offset += eg.readUEG();
|
12278
|
+
pic_right_offset += eg.readUEG();
|
12279
|
+
pic_top_offset += eg.readUEG();
|
12280
|
+
pic_bottom_offset += eg.readUEG();
|
12281
|
+
}
|
12282
|
+
const bit_depth_luma_minus8 = eg.readUEG();
|
12283
|
+
const bit_depth_chroma_minus8 = eg.readUEG();
|
12284
|
+
const log2_max_pic_order_cnt_lsb_minus4 = eg.readUEG();
|
12285
|
+
const sub_layer_ordering_info_present_flag = eg.readBoolean();
|
12286
|
+
for (let i = sub_layer_ordering_info_present_flag ? 0 : max_sub_layers_minus1; i <= max_sub_layers_minus1; i++) {
|
12287
|
+
eg.skipUEG(); // max_dec_pic_buffering_minus1[i]
|
12288
|
+
eg.skipUEG(); // max_num_reorder_pics[i]
|
12289
|
+
eg.skipUEG(); // max_latency_increase_plus1[i]
|
12290
|
+
}
|
12291
|
+
eg.skipUEG(); // log2_min_luma_coding_block_size_minus3
|
12292
|
+
eg.skipUEG(); // log2_diff_max_min_luma_coding_block_size
|
12293
|
+
eg.skipUEG(); // log2_min_transform_block_size_minus2
|
12294
|
+
eg.skipUEG(); // log2_diff_max_min_transform_block_size
|
12295
|
+
eg.skipUEG(); // max_transform_hierarchy_depth_inter
|
12296
|
+
eg.skipUEG(); // max_transform_hierarchy_depth_intra
|
12297
|
+
const scaling_list_enabled_flag = eg.readBoolean();
|
12298
|
+
if (scaling_list_enabled_flag) {
|
12299
|
+
const sps_scaling_list_data_present_flag = eg.readBoolean();
|
12300
|
+
if (sps_scaling_list_data_present_flag) {
|
12301
|
+
for (let sizeId = 0; sizeId < 4; sizeId++) {
|
12302
|
+
for (let matrixId = 0; matrixId < (sizeId === 3 ? 2 : 6); matrixId++) {
|
12303
|
+
const scaling_list_pred_mode_flag = eg.readBoolean();
|
12304
|
+
if (!scaling_list_pred_mode_flag) {
|
12305
|
+
eg.readUEG(); // scaling_list_pred_matrix_id_delta
|
12306
|
+
} else {
|
12307
|
+
const coefNum = Math.min(64, 1 << 4 + (sizeId << 1));
|
12308
|
+
if (sizeId > 1) {
|
12309
|
+
eg.readEG();
|
12310
|
+
}
|
12311
|
+
for (let i = 0; i < coefNum; i++) {
|
12312
|
+
eg.readEG();
|
12313
|
+
}
|
12314
|
+
}
|
12315
|
+
}
|
12316
|
+
}
|
12317
|
+
}
|
11877
12318
|
}
|
11878
|
-
//
|
11879
|
-
|
11880
|
-
|
11881
|
-
|
11882
|
-
|
11883
|
-
|
12319
|
+
eg.readBoolean(); // amp_enabled_flag
|
12320
|
+
eg.readBoolean(); // sample_adaptive_offset_enabled_flag
|
12321
|
+
const pcm_enabled_flag = eg.readBoolean();
|
12322
|
+
if (pcm_enabled_flag) {
|
12323
|
+
eg.readUByte();
|
12324
|
+
eg.skipUEG();
|
12325
|
+
eg.skipUEG();
|
12326
|
+
eg.readBoolean();
|
12327
|
+
}
|
12328
|
+
const num_short_term_ref_pic_sets = eg.readUEG();
|
12329
|
+
let num_delta_pocs = 0;
|
12330
|
+
for (let i = 0; i < num_short_term_ref_pic_sets; i++) {
|
12331
|
+
let inter_ref_pic_set_prediction_flag = false;
|
12332
|
+
if (i !== 0) {
|
12333
|
+
inter_ref_pic_set_prediction_flag = eg.readBoolean();
|
12334
|
+
}
|
12335
|
+
if (inter_ref_pic_set_prediction_flag) {
|
12336
|
+
if (i === num_short_term_ref_pic_sets) {
|
12337
|
+
eg.readUEG();
|
12338
|
+
}
|
12339
|
+
eg.readBoolean();
|
12340
|
+
eg.readUEG();
|
12341
|
+
let next_num_delta_pocs = 0;
|
12342
|
+
for (let j = 0; j <= num_delta_pocs; j++) {
|
12343
|
+
const used_by_curr_pic_flag = eg.readBoolean();
|
12344
|
+
let use_delta_flag = false;
|
12345
|
+
if (!used_by_curr_pic_flag) {
|
12346
|
+
use_delta_flag = eg.readBoolean();
|
12347
|
+
}
|
12348
|
+
if (used_by_curr_pic_flag || use_delta_flag) {
|
12349
|
+
next_num_delta_pocs++;
|
12350
|
+
}
|
12351
|
+
}
|
12352
|
+
num_delta_pocs = next_num_delta_pocs;
|
12353
|
+
} else {
|
12354
|
+
const num_negative_pics = eg.readUEG();
|
12355
|
+
const num_positive_pics = eg.readUEG();
|
12356
|
+
num_delta_pocs = num_negative_pics + num_positive_pics;
|
12357
|
+
for (let j = 0; j < num_negative_pics; j++) {
|
12358
|
+
eg.readUEG();
|
12359
|
+
eg.readBoolean();
|
12360
|
+
}
|
12361
|
+
for (let j = 0; j < num_positive_pics; j++) {
|
12362
|
+
eg.readUEG();
|
12363
|
+
eg.readBoolean();
|
12364
|
+
}
|
12365
|
+
}
|
12366
|
+
}
|
12367
|
+
const long_term_ref_pics_present_flag = eg.readBoolean();
|
12368
|
+
if (long_term_ref_pics_present_flag) {
|
12369
|
+
const num_long_term_ref_pics_sps = eg.readUEG();
|
12370
|
+
for (let i = 0; i < num_long_term_ref_pics_sps; i++) {
|
12371
|
+
for (let j = 0; j < log2_max_pic_order_cnt_lsb_minus4 + 4; j++) {
|
12372
|
+
eg.readBits(1);
|
12373
|
+
}
|
12374
|
+
eg.readBits(1);
|
12375
|
+
}
|
12376
|
+
}
|
12377
|
+
let min_spatial_segmentation_idc = 0;
|
12378
|
+
let sar_width = 1,
|
12379
|
+
sar_height = 1;
|
12380
|
+
let fps_fixed = true,
|
12381
|
+
fps_den = 1,
|
12382
|
+
fps_num = 0;
|
12383
|
+
eg.readBoolean(); // sps_temporal_mvp_enabled_flag
|
12384
|
+
eg.readBoolean(); // strong_intra_smoothing_enabled_flag
|
12385
|
+
let default_display_window_flag = false;
|
12386
|
+
const vui_parameters_present_flag = eg.readBoolean();
|
12387
|
+
if (vui_parameters_present_flag) {
|
12388
|
+
const aspect_ratio_info_present_flag = eg.readBoolean();
|
12389
|
+
if (aspect_ratio_info_present_flag) {
|
12390
|
+
const aspect_ratio_idc = eg.readUByte();
|
12391
|
+
const sar_width_table = [1, 12, 10, 16, 40, 24, 20, 32, 80, 18, 15, 64, 160, 4, 3, 2];
|
12392
|
+
const sar_height_table = [1, 11, 11, 11, 33, 11, 11, 11, 33, 11, 11, 33, 99, 3, 2, 1];
|
12393
|
+
if (aspect_ratio_idc > 0 && aspect_ratio_idc < 16) {
|
12394
|
+
sar_width = sar_width_table[aspect_ratio_idc - 1];
|
12395
|
+
sar_height = sar_height_table[aspect_ratio_idc - 1];
|
12396
|
+
} else if (aspect_ratio_idc === 255) {
|
12397
|
+
sar_width = eg.readBits(16);
|
12398
|
+
sar_height = eg.readBits(16);
|
12399
|
+
}
|
12400
|
+
}
|
12401
|
+
const overscan_info_present_flag = eg.readBoolean();
|
12402
|
+
if (overscan_info_present_flag) {
|
12403
|
+
eg.readBoolean();
|
12404
|
+
}
|
12405
|
+
const video_signal_type_present_flag = eg.readBoolean();
|
12406
|
+
if (video_signal_type_present_flag) {
|
12407
|
+
eg.readBits(3);
|
12408
|
+
eg.readBoolean();
|
12409
|
+
const colour_description_present_flag = eg.readBoolean();
|
12410
|
+
if (colour_description_present_flag) {
|
12411
|
+
eg.readUByte();
|
12412
|
+
eg.readUByte();
|
12413
|
+
eg.readUByte();
|
12414
|
+
}
|
12415
|
+
}
|
12416
|
+
const chroma_loc_info_present_flag = eg.readBoolean();
|
12417
|
+
if (chroma_loc_info_present_flag) {
|
12418
|
+
eg.readUEG();
|
12419
|
+
eg.readUEG();
|
12420
|
+
}
|
12421
|
+
eg.readBoolean(); // neutral_chroma_indication_flag
|
12422
|
+
eg.readBoolean(); // field_seq_flag
|
12423
|
+
eg.readBoolean(); // frame_field_info_present_flag
|
12424
|
+
default_display_window_flag = eg.readBoolean();
|
12425
|
+
if (default_display_window_flag) {
|
12426
|
+
pic_left_offset += eg.readUEG();
|
12427
|
+
pic_right_offset += eg.readUEG();
|
12428
|
+
pic_top_offset += eg.readUEG();
|
12429
|
+
pic_bottom_offset += eg.readUEG();
|
12430
|
+
}
|
12431
|
+
const vui_timing_info_present_flag = eg.readBoolean();
|
12432
|
+
if (vui_timing_info_present_flag) {
|
12433
|
+
fps_den = eg.readBits(32);
|
12434
|
+
fps_num = eg.readBits(32);
|
12435
|
+
const vui_poc_proportional_to_timing_flag = eg.readBoolean();
|
12436
|
+
if (vui_poc_proportional_to_timing_flag) {
|
12437
|
+
eg.readUEG();
|
12438
|
+
}
|
12439
|
+
const vui_hrd_parameters_present_flag = eg.readBoolean();
|
12440
|
+
if (vui_hrd_parameters_present_flag) {
|
12441
|
+
//const commonInfPresentFlag = true;
|
12442
|
+
//if (commonInfPresentFlag) {
|
12443
|
+
const nal_hrd_parameters_present_flag = eg.readBoolean();
|
12444
|
+
const vcl_hrd_parameters_present_flag = eg.readBoolean();
|
12445
|
+
let sub_pic_hrd_params_present_flag = false;
|
12446
|
+
if (nal_hrd_parameters_present_flag || vcl_hrd_parameters_present_flag) {
|
12447
|
+
sub_pic_hrd_params_present_flag = eg.readBoolean();
|
12448
|
+
if (sub_pic_hrd_params_present_flag) {
|
12449
|
+
eg.readUByte();
|
12450
|
+
eg.readBits(5);
|
12451
|
+
eg.readBoolean();
|
12452
|
+
eg.readBits(5);
|
12453
|
+
}
|
12454
|
+
eg.readBits(4); // bit_rate_scale
|
12455
|
+
eg.readBits(4); // cpb_size_scale
|
12456
|
+
if (sub_pic_hrd_params_present_flag) {
|
12457
|
+
eg.readBits(4);
|
12458
|
+
}
|
12459
|
+
eg.readBits(5);
|
12460
|
+
eg.readBits(5);
|
12461
|
+
eg.readBits(5);
|
12462
|
+
}
|
12463
|
+
//}
|
12464
|
+
for (let i = 0; i <= max_sub_layers_minus1; i++) {
|
12465
|
+
fps_fixed = eg.readBoolean(); // fixed_pic_rate_general_flag
|
12466
|
+
const fixed_pic_rate_within_cvs_flag = fps_fixed || eg.readBoolean();
|
12467
|
+
let low_delay_hrd_flag = false;
|
12468
|
+
if (fixed_pic_rate_within_cvs_flag) {
|
12469
|
+
eg.readEG();
|
12470
|
+
} else {
|
12471
|
+
low_delay_hrd_flag = eg.readBoolean();
|
12472
|
+
}
|
12473
|
+
const cpb_cnt = low_delay_hrd_flag ? 1 : eg.readUEG() + 1;
|
12474
|
+
if (nal_hrd_parameters_present_flag) {
|
12475
|
+
for (let j = 0; j < cpb_cnt; j++) {
|
12476
|
+
eg.readUEG();
|
12477
|
+
eg.readUEG();
|
12478
|
+
if (sub_pic_hrd_params_present_flag) {
|
12479
|
+
eg.readUEG();
|
12480
|
+
eg.readUEG();
|
12481
|
+
}
|
12482
|
+
eg.skipBits(1);
|
12483
|
+
}
|
12484
|
+
}
|
12485
|
+
if (vcl_hrd_parameters_present_flag) {
|
12486
|
+
for (let j = 0; j < cpb_cnt; j++) {
|
12487
|
+
eg.readUEG();
|
12488
|
+
eg.readUEG();
|
12489
|
+
if (sub_pic_hrd_params_present_flag) {
|
12490
|
+
eg.readUEG();
|
12491
|
+
eg.readUEG();
|
12492
|
+
}
|
12493
|
+
eg.skipBits(1);
|
12494
|
+
}
|
12495
|
+
}
|
12496
|
+
}
|
12497
|
+
}
|
11884
12498
|
}
|
12499
|
+
const bitstream_restriction_flag = eg.readBoolean();
|
12500
|
+
if (bitstream_restriction_flag) {
|
12501
|
+
eg.readBoolean(); // tiles_fixed_structure_flag
|
12502
|
+
eg.readBoolean(); // motion_vectors_over_pic_boundaries_flag
|
12503
|
+
eg.readBoolean(); // restricted_ref_pic_lists_flag
|
12504
|
+
min_spatial_segmentation_idc = eg.readUEG();
|
12505
|
+
}
|
12506
|
+
}
|
12507
|
+
let width = pic_width_in_luma_samples,
|
12508
|
+
height = pic_height_in_luma_samples;
|
12509
|
+
if (conformance_window_flag || default_display_window_flag) {
|
12510
|
+
let chroma_scale_w = 1,
|
12511
|
+
chroma_scale_h = 1;
|
12512
|
+
if (chroma_format_idc === 1) {
|
12513
|
+
// YUV 420
|
12514
|
+
chroma_scale_w = chroma_scale_h = 2;
|
12515
|
+
} else if (chroma_format_idc == 2) {
|
12516
|
+
// YUV 422
|
12517
|
+
chroma_scale_w = 2;
|
12518
|
+
}
|
12519
|
+
width = pic_width_in_luma_samples - chroma_scale_w * pic_right_offset - chroma_scale_w * pic_left_offset;
|
12520
|
+
height = pic_height_in_luma_samples - chroma_scale_h * pic_bottom_offset - chroma_scale_h * pic_top_offset;
|
12521
|
+
}
|
12522
|
+
const profile_space_string = general_profile_space ? ['A', 'B', 'C'][general_profile_space] : '';
|
12523
|
+
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;
|
12524
|
+
let profile_compatibility_rev = 0;
|
12525
|
+
for (let i = 0; i < 32; i++) {
|
12526
|
+
profile_compatibility_rev = (profile_compatibility_rev | (profile_compatibility_buf >> i & 1) << 31 - i) >>> 0; // reverse bit position (and cast as UInt32)
|
12527
|
+
}
|
12528
|
+
let profile_compatibility_flags_string = profile_compatibility_rev.toString(16);
|
12529
|
+
if (general_profile_idc === 1 && profile_compatibility_flags_string === '2') {
|
12530
|
+
profile_compatibility_flags_string = '6';
|
12531
|
+
}
|
12532
|
+
const tier_flag_string = general_tier_flag ? 'H' : 'L';
|
12533
|
+
return {
|
12534
|
+
codecString: `hvc1.${profile_space_string}${general_profile_idc}.${profile_compatibility_flags_string}.${tier_flag_string}${general_level_idc}.B0`,
|
12535
|
+
params: {
|
12536
|
+
general_tier_flag,
|
12537
|
+
general_profile_idc,
|
12538
|
+
general_profile_space,
|
12539
|
+
general_profile_compatibility_flags: [general_profile_compatibility_flags_1, general_profile_compatibility_flags_2, general_profile_compatibility_flags_3, general_profile_compatibility_flags_4],
|
12540
|
+
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],
|
12541
|
+
general_level_idc,
|
12542
|
+
bit_depth: bit_depth_luma_minus8 + 8,
|
12543
|
+
bit_depth_luma_minus8,
|
12544
|
+
bit_depth_chroma_minus8,
|
12545
|
+
min_spatial_segmentation_idc,
|
12546
|
+
chroma_format_idc: chroma_format_idc,
|
12547
|
+
frame_rate: {
|
12548
|
+
fixed: fps_fixed,
|
12549
|
+
fps: fps_num / fps_den
|
12550
|
+
}
|
12551
|
+
},
|
12552
|
+
width,
|
12553
|
+
height,
|
12554
|
+
pixelRatio: [sar_width, sar_height]
|
12555
|
+
};
|
12556
|
+
}
|
12557
|
+
readPPS(pps) {
|
12558
|
+
const eg = new ExpGolomb(this.ebsp2rbsp(pps));
|
12559
|
+
eg.readUByte();
|
12560
|
+
eg.readUByte();
|
12561
|
+
eg.skipUEG(); // pic_parameter_set_id
|
12562
|
+
eg.skipUEG(); // seq_parameter_set_id
|
12563
|
+
eg.skipBits(2); // dependent_slice_segments_enabled_flag, output_flag_present_flag
|
12564
|
+
eg.skipBits(3); // num_extra_slice_header_bits
|
12565
|
+
eg.skipBits(2); // sign_data_hiding_enabled_flag, cabac_init_present_flag
|
12566
|
+
eg.skipUEG();
|
12567
|
+
eg.skipUEG();
|
12568
|
+
eg.skipEG(); // init_qp_minus26
|
12569
|
+
eg.skipBits(2); // constrained_intra_pred_flag, transform_skip_enabled_flag
|
12570
|
+
const cu_qp_delta_enabled_flag = eg.readBoolean();
|
12571
|
+
if (cu_qp_delta_enabled_flag) {
|
12572
|
+
eg.skipUEG();
|
12573
|
+
}
|
12574
|
+
eg.skipEG(); // cb_qp_offset
|
12575
|
+
eg.skipEG(); // cr_qp_offset
|
12576
|
+
eg.skipBits(4); // pps_slice_chroma_qp_offsets_present_flag, weighted_pred_flag, weighted_bipred_flag, transquant_bypass_enabled_flag
|
12577
|
+
const tiles_enabled_flag = eg.readBoolean();
|
12578
|
+
const entropy_coding_sync_enabled_flag = eg.readBoolean();
|
12579
|
+
let parallelismType = 1; // slice-based parallel decoding
|
12580
|
+
if (entropy_coding_sync_enabled_flag && tiles_enabled_flag) {
|
12581
|
+
parallelismType = 0; // mixed-type parallel decoding
|
12582
|
+
} else if (entropy_coding_sync_enabled_flag) {
|
12583
|
+
parallelismType = 3; // wavefront-based parallel decoding
|
12584
|
+
} else if (tiles_enabled_flag) {
|
12585
|
+
parallelismType = 2; // tile-based parallel decoding
|
11885
12586
|
}
|
11886
|
-
|
11887
|
-
|
12587
|
+
return {
|
12588
|
+
parallelismType
|
12589
|
+
};
|
12590
|
+
}
|
12591
|
+
matchSPS(sps1, sps2) {
|
12592
|
+
// compare without headers and VPS related params
|
12593
|
+
return String.fromCharCode.apply(null, sps1).substr(3) === String.fromCharCode.apply(null, sps2).substr(3);
|
11888
12594
|
}
|
11889
12595
|
}
|
11890
12596
|
|
@@ -11902,7 +12608,7 @@ class SampleAesDecrypter {
|
|
11902
12608
|
});
|
11903
12609
|
}
|
11904
12610
|
decryptBuffer(encryptedData) {
|
11905
|
-
return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer);
|
12611
|
+
return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer, DecrypterAesMode.cbc);
|
11906
12612
|
}
|
11907
12613
|
|
11908
12614
|
// AAC - encrypt all full 16 bytes blocks starting from offset 16
|
@@ -12016,7 +12722,7 @@ class TSDemuxer {
|
|
12016
12722
|
this.observer = observer;
|
12017
12723
|
this.config = config;
|
12018
12724
|
this.typeSupported = typeSupported;
|
12019
|
-
this.videoParser =
|
12725
|
+
this.videoParser = null;
|
12020
12726
|
}
|
12021
12727
|
static probe(data) {
|
12022
12728
|
const syncOffset = TSDemuxer.syncOffset(data);
|
@@ -12181,7 +12887,21 @@ class TSDemuxer {
|
|
12181
12887
|
case videoPid:
|
12182
12888
|
if (stt) {
|
12183
12889
|
if (videoData && (pes = parsePES(videoData))) {
|
12184
|
-
this.videoParser
|
12890
|
+
if (this.videoParser === null) {
|
12891
|
+
switch (videoTrack.segmentCodec) {
|
12892
|
+
case 'avc':
|
12893
|
+
this.videoParser = new AvcVideoParser();
|
12894
|
+
break;
|
12895
|
+
case 'hevc':
|
12896
|
+
{
|
12897
|
+
this.videoParser = new HevcVideoParser();
|
12898
|
+
}
|
12899
|
+
break;
|
12900
|
+
}
|
12901
|
+
}
|
12902
|
+
if (this.videoParser !== null) {
|
12903
|
+
this.videoParser.parsePES(videoTrack, textTrack, pes, false, this._duration);
|
12904
|
+
}
|
12185
12905
|
}
|
12186
12906
|
videoData = {
|
12187
12907
|
data: [],
|
@@ -12348,8 +13068,22 @@ class TSDemuxer {
|
|
12348
13068
|
// try to parse last PES packets
|
12349
13069
|
let pes;
|
12350
13070
|
if (videoData && (pes = parsePES(videoData))) {
|
12351
|
-
this.videoParser
|
12352
|
-
|
13071
|
+
if (this.videoParser === null) {
|
13072
|
+
switch (videoTrack.segmentCodec) {
|
13073
|
+
case 'avc':
|
13074
|
+
this.videoParser = new AvcVideoParser();
|
13075
|
+
break;
|
13076
|
+
case 'hevc':
|
13077
|
+
{
|
13078
|
+
this.videoParser = new HevcVideoParser();
|
13079
|
+
}
|
13080
|
+
break;
|
13081
|
+
}
|
13082
|
+
}
|
13083
|
+
if (this.videoParser !== null) {
|
13084
|
+
this.videoParser.parsePES(videoTrack, textTrack, pes, true, this._duration);
|
13085
|
+
videoTrack.pesData = null;
|
13086
|
+
}
|
12353
13087
|
} else {
|
12354
13088
|
// either avcData null or PES truncated, keep it for next frag parsing
|
12355
13089
|
videoTrack.pesData = videoData;
|
@@ -12682,7 +13416,14 @@ function parsePMT(data, offset, typeSupported, isSampleAes) {
|
|
12682
13416
|
logger.warn('Unsupported EC-3 in M2TS found');
|
12683
13417
|
break;
|
12684
13418
|
case 0x24:
|
12685
|
-
|
13419
|
+
// ITU-T Rec. H.265 and ISO/IEC 23008-2 (HEVC)
|
13420
|
+
{
|
13421
|
+
if (result.videoPid === -1) {
|
13422
|
+
result.videoPid = pid;
|
13423
|
+
result.segmentVideoCodec = 'hevc';
|
13424
|
+
logger.log('HEVC in M2TS found');
|
13425
|
+
}
|
13426
|
+
}
|
12686
13427
|
break;
|
12687
13428
|
}
|
12688
13429
|
// move to the next table entry
|
@@ -12905,6 +13646,8 @@ class MP4 {
|
|
12905
13646
|
avc1: [],
|
12906
13647
|
// codingname
|
12907
13648
|
avcC: [],
|
13649
|
+
hvc1: [],
|
13650
|
+
hvcC: [],
|
12908
13651
|
btrt: [],
|
12909
13652
|
dinf: [],
|
12910
13653
|
dref: [],
|
@@ -13329,8 +14072,10 @@ class MP4 {
|
|
13329
14072
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.ac3(track));
|
13330
14073
|
}
|
13331
14074
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track));
|
13332
|
-
} else {
|
14075
|
+
} else if (track.segmentCodec === 'avc') {
|
13333
14076
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
|
14077
|
+
} else {
|
14078
|
+
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.hvc1(track));
|
13334
14079
|
}
|
13335
14080
|
}
|
13336
14081
|
static tkhd(track) {
|
@@ -13468,6 +14213,84 @@ class MP4 {
|
|
13468
14213
|
const result = appendUint8Array(MP4.FTYP, movie);
|
13469
14214
|
return result;
|
13470
14215
|
}
|
14216
|
+
static hvc1(track) {
|
14217
|
+
const ps = track.params;
|
14218
|
+
const units = [track.vps, track.sps, track.pps];
|
14219
|
+
const NALuLengthSize = 4;
|
14220
|
+
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]);
|
14221
|
+
|
14222
|
+
// compute hvcC size in bytes
|
14223
|
+
let length = config.length;
|
14224
|
+
for (let i = 0; i < units.length; i += 1) {
|
14225
|
+
length += 3;
|
14226
|
+
for (let j = 0; j < units[i].length; j += 1) {
|
14227
|
+
length += 2 + units[i][j].length;
|
14228
|
+
}
|
14229
|
+
}
|
14230
|
+
const hvcC = new Uint8Array(length);
|
14231
|
+
hvcC.set(config, 0);
|
14232
|
+
length = config.length;
|
14233
|
+
// append parameter set units: one vps, one or more sps and pps
|
14234
|
+
const iMax = units.length - 1;
|
14235
|
+
for (let i = 0; i < units.length; i += 1) {
|
14236
|
+
hvcC.set(new Uint8Array([32 + i | (i === iMax ? 128 : 0), 0x00, units[i].length]), length);
|
14237
|
+
length += 3;
|
14238
|
+
for (let j = 0; j < units[i].length; j += 1) {
|
14239
|
+
hvcC.set(new Uint8Array([units[i][j].length >> 8, units[i][j].length & 255]), length);
|
14240
|
+
length += 2;
|
14241
|
+
hvcC.set(units[i][j], length);
|
14242
|
+
length += units[i][j].length;
|
14243
|
+
}
|
14244
|
+
}
|
14245
|
+
const hvcc = MP4.box(MP4.types.hvcC, hvcC);
|
14246
|
+
const width = track.width;
|
14247
|
+
const height = track.height;
|
14248
|
+
const hSpacing = track.pixelRatio[0];
|
14249
|
+
const vSpacing = track.pixelRatio[1];
|
14250
|
+
return MP4.box(MP4.types.hvc1, new Uint8Array([0x00, 0x00, 0x00,
|
14251
|
+
// reserved
|
14252
|
+
0x00, 0x00, 0x00,
|
14253
|
+
// reserved
|
14254
|
+
0x00, 0x01,
|
14255
|
+
// data_reference_index
|
14256
|
+
0x00, 0x00,
|
14257
|
+
// pre_defined
|
14258
|
+
0x00, 0x00,
|
14259
|
+
// reserved
|
14260
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
14261
|
+
// pre_defined
|
14262
|
+
width >> 8 & 0xff, width & 0xff,
|
14263
|
+
// width
|
14264
|
+
height >> 8 & 0xff, height & 0xff,
|
14265
|
+
// height
|
14266
|
+
0x00, 0x48, 0x00, 0x00,
|
14267
|
+
// horizresolution
|
14268
|
+
0x00, 0x48, 0x00, 0x00,
|
14269
|
+
// vertresolution
|
14270
|
+
0x00, 0x00, 0x00, 0x00,
|
14271
|
+
// reserved
|
14272
|
+
0x00, 0x01,
|
14273
|
+
// frame_count
|
14274
|
+
0x12, 0x64, 0x61, 0x69, 0x6c,
|
14275
|
+
// dailymotion/hls.js
|
14276
|
+
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,
|
14277
|
+
// compressorname
|
14278
|
+
0x00, 0x18,
|
14279
|
+
// depth = 24
|
14280
|
+
0x11, 0x11]),
|
14281
|
+
// pre_defined = -1
|
14282
|
+
hvcc, MP4.box(MP4.types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80,
|
14283
|
+
// bufferSizeDB
|
14284
|
+
0x00, 0x2d, 0xc6, 0xc0,
|
14285
|
+
// maxBitrate
|
14286
|
+
0x00, 0x2d, 0xc6, 0xc0])),
|
14287
|
+
// avgBitrate
|
14288
|
+
MP4.box(MP4.types.pasp, new Uint8Array([hSpacing >> 24,
|
14289
|
+
// hSpacing
|
14290
|
+
hSpacing >> 16 & 0xff, hSpacing >> 8 & 0xff, hSpacing & 0xff, vSpacing >> 24,
|
14291
|
+
// vSpacing
|
14292
|
+
vSpacing >> 16 & 0xff, vSpacing >> 8 & 0xff, vSpacing & 0xff])));
|
14293
|
+
}
|
13471
14294
|
}
|
13472
14295
|
MP4.types = void 0;
|
13473
14296
|
MP4.HDLR_TYPES = void 0;
|
@@ -13849,9 +14672,9 @@ class MP4Remuxer {
|
|
13849
14672
|
const foundOverlap = delta < -1;
|
13850
14673
|
if (foundHole || foundOverlap) {
|
13851
14674
|
if (foundHole) {
|
13852
|
-
logger.warn(
|
14675
|
+
logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
|
13853
14676
|
} else {
|
13854
|
-
logger.warn(
|
14677
|
+
logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
|
13855
14678
|
}
|
13856
14679
|
if (!foundOverlap || nextAvcDts >= inputSamples[0].pts || chromeVersion) {
|
13857
14680
|
firstDTS = nextAvcDts;
|
@@ -13860,12 +14683,24 @@ class MP4Remuxer {
|
|
13860
14683
|
inputSamples[0].dts = firstDTS;
|
13861
14684
|
inputSamples[0].pts = firstPTS;
|
13862
14685
|
} else {
|
14686
|
+
let isPTSOrderRetained = true;
|
13863
14687
|
for (let i = 0; i < inputSamples.length; i++) {
|
13864
|
-
if (inputSamples[i].dts > firstPTS) {
|
14688
|
+
if (inputSamples[i].dts > firstPTS && isPTSOrderRetained) {
|
13865
14689
|
break;
|
13866
14690
|
}
|
14691
|
+
const prevPTS = inputSamples[i].pts;
|
13867
14692
|
inputSamples[i].dts -= delta;
|
13868
14693
|
inputSamples[i].pts -= delta;
|
14694
|
+
|
14695
|
+
// check to see if this sample's PTS order has changed
|
14696
|
+
// relative to the next one
|
14697
|
+
if (i < inputSamples.length - 1) {
|
14698
|
+
const nextSamplePTS = inputSamples[i + 1].pts;
|
14699
|
+
const currentSamplePTS = inputSamples[i].pts;
|
14700
|
+
const currentOrder = nextSamplePTS <= currentSamplePTS;
|
14701
|
+
const prevOrder = nextSamplePTS <= prevPTS;
|
14702
|
+
isPTSOrderRetained = currentOrder == prevOrder;
|
14703
|
+
}
|
13869
14704
|
}
|
13870
14705
|
}
|
13871
14706
|
logger.log(`Video: Initial PTS/DTS adjusted: ${toMsFromMpegTsClock(firstPTS, true)}/${toMsFromMpegTsClock(firstDTS, true)}, delta: ${toMsFromMpegTsClock(delta, true)} ms`);
|
@@ -14013,7 +14848,7 @@ class MP4Remuxer {
|
|
14013
14848
|
}
|
14014
14849
|
}
|
14015
14850
|
}
|
14016
|
-
// next AVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
|
14851
|
+
// next AVC/HEVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
|
14017
14852
|
mp4SampleDuration = stretchedLastFrame || !mp4SampleDuration ? averageSampleDuration : mp4SampleDuration;
|
14018
14853
|
this.nextAvcDts = nextAvcDts = lastDTS + mp4SampleDuration;
|
14019
14854
|
this.videoSampleDuration = mp4SampleDuration;
|
@@ -14146,7 +14981,7 @@ class MP4Remuxer {
|
|
14146
14981
|
logger.warn(`[mp4-remuxer]: Injecting ${missing} audio frame @ ${(nextPts / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`);
|
14147
14982
|
for (let j = 0; j < missing; j++) {
|
14148
14983
|
const newStamp = Math.max(nextPts, 0);
|
14149
|
-
let fillFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
|
14984
|
+
let fillFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
|
14150
14985
|
if (!fillFrame) {
|
14151
14986
|
logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.');
|
14152
14987
|
fillFrame = sample.unit.subarray();
|
@@ -14274,7 +15109,7 @@ class MP4Remuxer {
|
|
14274
15109
|
// samples count of this segment's duration
|
14275
15110
|
const nbSamples = Math.ceil((endDTS - startDTS) / frameDuration);
|
14276
15111
|
// silent frame
|
14277
|
-
const silentFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
|
15112
|
+
const silentFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
|
14278
15113
|
logger.warn('[mp4-remuxer]: remux empty Audio');
|
14279
15114
|
// Can't remux if we can't generate a silent frame...
|
14280
15115
|
if (!silentFrame) {
|
@@ -14668,13 +15503,15 @@ class Transmuxer {
|
|
14668
15503
|
initSegmentData
|
14669
15504
|
} = transmuxConfig;
|
14670
15505
|
const keyData = getEncryptionType(uintData, decryptdata);
|
14671
|
-
if (keyData && keyData.method
|
15506
|
+
if (keyData && isFullSegmentEncryption(keyData.method)) {
|
14672
15507
|
const decrypter = this.getDecrypter();
|
15508
|
+
const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
|
15509
|
+
|
14673
15510
|
// Software decryption is synchronous; webCrypto is not
|
14674
15511
|
if (decrypter.isSync()) {
|
14675
15512
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
14676
15513
|
// data is handled in the flush() call
|
14677
|
-
let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer);
|
15514
|
+
let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode);
|
14678
15515
|
// For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
|
14679
15516
|
const loadingParts = chunkMeta.part > -1;
|
14680
15517
|
if (loadingParts) {
|
@@ -14686,7 +15523,7 @@ class Transmuxer {
|
|
14686
15523
|
}
|
14687
15524
|
uintData = new Uint8Array(decryptedData);
|
14688
15525
|
} else {
|
14689
|
-
this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer).then(decryptedData => {
|
15526
|
+
this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode).then(decryptedData => {
|
14690
15527
|
// Calling push here is important; if flush() is called while this is still resolving, this ensures that
|
14691
15528
|
// the decrypted data has been transmuxed
|
14692
15529
|
const result = this.push(decryptedData, null, chunkMeta);
|
@@ -15340,14 +16177,7 @@ class TransmuxerInterface {
|
|
15340
16177
|
this.observer = new EventEmitter();
|
15341
16178
|
this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
|
15342
16179
|
this.observer.on(Events.ERROR, forwardMessage);
|
15343
|
-
const
|
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
|
-
};
|
16180
|
+
const m2tsTypeSupported = getM2TSSupportedAudioTypes(config.preferManagedMediaSource);
|
15351
16181
|
|
15352
16182
|
// navigator.vendor is not always available in Web Worker
|
15353
16183
|
// refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
|
@@ -15635,7 +16465,7 @@ const TICK_INTERVAL$2 = 100; // how often to tick in ms
|
|
15635
16465
|
|
15636
16466
|
class AudioStreamController extends BaseStreamController {
|
15637
16467
|
constructor(hls, fragmentTracker, keyLoader) {
|
15638
|
-
super(hls, fragmentTracker, keyLoader, '
|
16468
|
+
super(hls, fragmentTracker, keyLoader, 'audio-stream-controller', PlaylistLevelType.AUDIO);
|
15639
16469
|
this.videoBuffer = null;
|
15640
16470
|
this.videoTrackCC = -1;
|
15641
16471
|
this.waitingVideoCC = -1;
|
@@ -15647,27 +16477,24 @@ class AudioStreamController extends BaseStreamController {
|
|
15647
16477
|
this.flushing = false;
|
15648
16478
|
this.bufferFlushed = false;
|
15649
16479
|
this.cachedTrackLoadedData = null;
|
15650
|
-
this.
|
16480
|
+
this.registerListeners();
|
15651
16481
|
}
|
15652
16482
|
onHandlerDestroying() {
|
15653
|
-
this.
|
16483
|
+
this.unregisterListeners();
|
15654
16484
|
super.onHandlerDestroying();
|
15655
16485
|
this.mainDetails = null;
|
15656
16486
|
this.bufferedTrack = null;
|
15657
16487
|
this.switchingTrack = null;
|
15658
16488
|
}
|
15659
|
-
|
16489
|
+
registerListeners() {
|
16490
|
+
super.registerListeners();
|
15660
16491
|
const {
|
15661
16492
|
hls
|
15662
16493
|
} = 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);
|
15666
16494
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
15667
16495
|
hls.on(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
|
15668
16496
|
hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
15669
16497
|
hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
|
15670
|
-
hls.on(Events.ERROR, this.onError, this);
|
15671
16498
|
hls.on(Events.BUFFER_RESET, this.onBufferReset, this);
|
15672
16499
|
hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
15673
16500
|
hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
@@ -15675,18 +16502,18 @@ class AudioStreamController extends BaseStreamController {
|
|
15675
16502
|
hls.on(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);
|
15676
16503
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
15677
16504
|
}
|
15678
|
-
|
16505
|
+
unregisterListeners() {
|
15679
16506
|
const {
|
15680
16507
|
hls
|
15681
16508
|
} = this;
|
15682
|
-
hls
|
15683
|
-
|
15684
|
-
|
16509
|
+
if (!hls) {
|
16510
|
+
return;
|
16511
|
+
}
|
16512
|
+
super.unregisterListeners();
|
15685
16513
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
15686
16514
|
hls.off(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
|
15687
16515
|
hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
15688
16516
|
hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
|
15689
|
-
hls.off(Events.ERROR, this.onError, this);
|
15690
16517
|
hls.off(Events.BUFFER_RESET, this.onBufferReset, this);
|
15691
16518
|
hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
15692
16519
|
hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
@@ -15855,12 +16682,13 @@ class AudioStreamController extends BaseStreamController {
|
|
15855
16682
|
} = this;
|
15856
16683
|
const config = hls.config;
|
15857
16684
|
|
15858
|
-
// 1. if
|
16685
|
+
// 1. if buffering is suspended
|
16686
|
+
// 2. if video not attached AND
|
15859
16687
|
// start fragment already requested OR start frag prefetch not enabled
|
15860
|
-
//
|
16688
|
+
// 3. if tracks or track not loaded and selected
|
15861
16689
|
// then exit loop
|
15862
16690
|
// => if media not attached but start frag prefetch is enabled and start frag not requested yet, we will not exit loop
|
15863
|
-
if (!media && (this.startFragRequested || !config.startFragPrefetch) || !(levels != null && levels[trackId])) {
|
16691
|
+
if (!this.buffering || !media && (this.startFragRequested || !config.startFragPrefetch) || !(levels != null && levels[trackId])) {
|
15864
16692
|
return;
|
15865
16693
|
}
|
15866
16694
|
const levelInfo = levels[trackId];
|
@@ -16418,7 +17246,7 @@ class AudioStreamController extends BaseStreamController {
|
|
16418
17246
|
|
16419
17247
|
class AudioTrackController extends BasePlaylistController {
|
16420
17248
|
constructor(hls) {
|
16421
|
-
super(hls, '
|
17249
|
+
super(hls, 'audio-track-controller');
|
16422
17250
|
this.tracks = [];
|
16423
17251
|
this.groupIds = null;
|
16424
17252
|
this.tracksInGroup = [];
|
@@ -16737,26 +17565,23 @@ const TICK_INTERVAL$1 = 500; // how often to tick in ms
|
|
16737
17565
|
|
16738
17566
|
class SubtitleStreamController extends BaseStreamController {
|
16739
17567
|
constructor(hls, fragmentTracker, keyLoader) {
|
16740
|
-
super(hls, fragmentTracker, keyLoader, '
|
17568
|
+
super(hls, fragmentTracker, keyLoader, 'subtitle-stream-controller', PlaylistLevelType.SUBTITLE);
|
16741
17569
|
this.currentTrackId = -1;
|
16742
17570
|
this.tracksBuffered = [];
|
16743
17571
|
this.mainDetails = null;
|
16744
|
-
this.
|
17572
|
+
this.registerListeners();
|
16745
17573
|
}
|
16746
17574
|
onHandlerDestroying() {
|
16747
|
-
this.
|
17575
|
+
this.unregisterListeners();
|
16748
17576
|
super.onHandlerDestroying();
|
16749
17577
|
this.mainDetails = null;
|
16750
17578
|
}
|
16751
|
-
|
17579
|
+
registerListeners() {
|
17580
|
+
super.registerListeners();
|
16752
17581
|
const {
|
16753
17582
|
hls
|
16754
17583
|
} = 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);
|
16758
17584
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
16759
|
-
hls.on(Events.ERROR, this.onError, this);
|
16760
17585
|
hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
|
16761
17586
|
hls.on(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
|
16762
17587
|
hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
|
@@ -16764,15 +17589,12 @@ class SubtitleStreamController extends BaseStreamController {
|
|
16764
17589
|
hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
16765
17590
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
16766
17591
|
}
|
16767
|
-
|
17592
|
+
unregisterListeners() {
|
17593
|
+
super.unregisterListeners();
|
16768
17594
|
const {
|
16769
17595
|
hls
|
16770
17596
|
} = 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);
|
16774
17597
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
16775
|
-
hls.off(Events.ERROR, this.onError, this);
|
16776
17598
|
hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
|
16777
17599
|
hls.off(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
|
16778
17600
|
hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
|
@@ -16999,10 +17821,10 @@ class SubtitleStreamController extends BaseStreamController {
|
|
16999
17821
|
return;
|
17000
17822
|
}
|
17001
17823
|
// check to see if the payload needs to be decrypted
|
17002
|
-
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method
|
17824
|
+
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
|
17003
17825
|
const startTime = performance.now();
|
17004
17826
|
// decrypt the subtitles
|
17005
|
-
this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
|
17827
|
+
this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch(err => {
|
17006
17828
|
hls.trigger(Events.ERROR, {
|
17007
17829
|
type: ErrorTypes.MEDIA_ERROR,
|
17008
17830
|
details: ErrorDetails.FRAG_DECRYPT_ERROR,
|
@@ -17136,7 +17958,7 @@ class BufferableInstance {
|
|
17136
17958
|
|
17137
17959
|
class SubtitleTrackController extends BasePlaylistController {
|
17138
17960
|
constructor(hls) {
|
17139
|
-
super(hls, '
|
17961
|
+
super(hls, 'subtitle-track-controller');
|
17140
17962
|
this.media = null;
|
17141
17963
|
this.tracks = [];
|
17142
17964
|
this.groupIds = null;
|
@@ -17145,10 +17967,10 @@ class SubtitleTrackController extends BasePlaylistController {
|
|
17145
17967
|
this.currentTrack = null;
|
17146
17968
|
this.selectDefaultTrack = true;
|
17147
17969
|
this.queuedDefaultTrack = -1;
|
17148
|
-
this.asyncPollTrackChange = () => this.pollTrackChange(0);
|
17149
17970
|
this.useTextTrackPolling = false;
|
17150
17971
|
this.subtitlePollingInterval = -1;
|
17151
17972
|
this._subtitleDisplay = true;
|
17973
|
+
this.asyncPollTrackChange = () => this.pollTrackChange(0);
|
17152
17974
|
this.onTextTracksChanged = () => {
|
17153
17975
|
if (!this.useTextTrackPolling) {
|
17154
17976
|
self.clearInterval(this.subtitlePollingInterval);
|
@@ -17182,6 +18004,7 @@ class SubtitleTrackController extends BasePlaylistController {
|
|
17182
18004
|
this.tracks.length = 0;
|
17183
18005
|
this.tracksInGroup.length = 0;
|
17184
18006
|
this.currentTrack = null;
|
18007
|
+
// @ts-ignore
|
17185
18008
|
this.onTextTracksChanged = this.asyncPollTrackChange = null;
|
17186
18009
|
super.destroy();
|
17187
18010
|
}
|
@@ -17642,8 +18465,9 @@ class BufferOperationQueue {
|
|
17642
18465
|
}
|
17643
18466
|
|
17644
18467
|
const VIDEO_CODEC_PROFILE_REPLACE = /(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\.[^.,]+)+/;
|
17645
|
-
class BufferController {
|
18468
|
+
class BufferController extends Logger {
|
17646
18469
|
constructor(hls) {
|
18470
|
+
super('buffer-controller', hls.logger);
|
17647
18471
|
// The level details used to determine duration, target-duration and live
|
17648
18472
|
this.details = null;
|
17649
18473
|
// cache the self generated object url to detect hijack of video tag
|
@@ -17673,9 +18497,6 @@ class BufferController {
|
|
17673
18497
|
this.tracks = {};
|
17674
18498
|
this.pendingTracks = {};
|
17675
18499
|
this.sourceBuffer = void 0;
|
17676
|
-
this.log = void 0;
|
17677
|
-
this.warn = void 0;
|
17678
|
-
this.error = void 0;
|
17679
18500
|
this._onEndStreaming = event => {
|
17680
18501
|
if (!this.hls) {
|
17681
18502
|
return;
|
@@ -17721,15 +18542,11 @@ class BufferController {
|
|
17721
18542
|
_objectUrl
|
17722
18543
|
} = this;
|
17723
18544
|
if (mediaSrc !== _objectUrl) {
|
17724
|
-
|
18545
|
+
this.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
|
17725
18546
|
}
|
17726
18547
|
};
|
17727
18548
|
this.hls = hls;
|
17728
|
-
const logPrefix = '[buffer-controller]';
|
17729
18549
|
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);
|
17733
18550
|
this._initSourceBuffer();
|
17734
18551
|
this.registerListeners();
|
17735
18552
|
}
|
@@ -17742,6 +18559,12 @@ class BufferController {
|
|
17742
18559
|
this.lastMpegAudioChunk = null;
|
17743
18560
|
// @ts-ignore
|
17744
18561
|
this.hls = null;
|
18562
|
+
// @ts-ignore
|
18563
|
+
this._onMediaSourceOpen = this._onMediaSourceClose = null;
|
18564
|
+
// @ts-ignore
|
18565
|
+
this._onMediaSourceEnded = null;
|
18566
|
+
// @ts-ignore
|
18567
|
+
this._onStartStreaming = this._onEndStreaming = null;
|
17745
18568
|
}
|
17746
18569
|
registerListeners() {
|
17747
18570
|
const {
|
@@ -17904,6 +18727,7 @@ class BufferController {
|
|
17904
18727
|
this.resetBuffer(type);
|
17905
18728
|
});
|
17906
18729
|
this._initSourceBuffer();
|
18730
|
+
this.hls.resumeBuffering();
|
17907
18731
|
}
|
17908
18732
|
resetBuffer(type) {
|
17909
18733
|
const sb = this.sourceBuffer[type];
|
@@ -21006,14 +21830,12 @@ class TimelineController {
|
|
21006
21830
|
this.cea608Parser1 = this.cea608Parser2 = undefined;
|
21007
21831
|
}
|
21008
21832
|
initCea608Parsers() {
|
21009
|
-
|
21010
|
-
|
21011
|
-
|
21012
|
-
|
21013
|
-
|
21014
|
-
|
21015
|
-
this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
|
21016
|
-
}
|
21833
|
+
const channel1 = new OutputFilter(this, 'textTrack1');
|
21834
|
+
const channel2 = new OutputFilter(this, 'textTrack2');
|
21835
|
+
const channel3 = new OutputFilter(this, 'textTrack3');
|
21836
|
+
const channel4 = new OutputFilter(this, 'textTrack4');
|
21837
|
+
this.cea608Parser1 = new Cea608Parser(1, channel1, channel2);
|
21838
|
+
this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
|
21017
21839
|
}
|
21018
21840
|
addCues(trackName, startTime, endTime, screen, cueRanges) {
|
21019
21841
|
// skip cues which overlap more than 50% with previously parsed time ranges
|
@@ -21251,7 +22073,7 @@ class TimelineController {
|
|
21251
22073
|
if (inUseTracks != null && inUseTracks.length) {
|
21252
22074
|
const unusedTextTracks = inUseTracks.filter(t => t !== null).map(t => t.label);
|
21253
22075
|
if (unusedTextTracks.length) {
|
21254
|
-
logger.warn(`Media element contains unused subtitle tracks: ${unusedTextTracks.join(', ')}. Replace media element for each source to clear TextTracks and captions menu.`);
|
22076
|
+
this.hls.logger.warn(`Media element contains unused subtitle tracks: ${unusedTextTracks.join(', ')}. Replace media element for each source to clear TextTracks and captions menu.`);
|
21255
22077
|
}
|
21256
22078
|
}
|
21257
22079
|
} else if (this.tracks.length) {
|
@@ -21296,26 +22118,23 @@ class TimelineController {
|
|
21296
22118
|
return level == null ? void 0 : level.attrs['CLOSED-CAPTIONS'];
|
21297
22119
|
}
|
21298
22120
|
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
|
-
}
|
21310
22121
|
// if this frag isn't contiguous, clear the parser so cues with bad start/end times aren't added to the textTrack
|
21311
|
-
if (data.frag.type === PlaylistLevelType.MAIN) {
|
22122
|
+
if (this.enabled && data.frag.type === PlaylistLevelType.MAIN) {
|
21312
22123
|
var _data$part$index, _data$part;
|
22124
|
+
const {
|
22125
|
+
cea608Parser1,
|
22126
|
+
cea608Parser2,
|
22127
|
+
lastSn
|
22128
|
+
} = this;
|
22129
|
+
if (!cea608Parser1 || !cea608Parser2) {
|
22130
|
+
return;
|
22131
|
+
}
|
21313
22132
|
const {
|
21314
22133
|
cc,
|
21315
22134
|
sn
|
21316
22135
|
} = data.frag;
|
21317
|
-
const partIndex = (_data$part$index =
|
21318
|
-
if (!(sn === lastSn + 1 || sn === lastSn && partIndex === lastPartIndex + 1 || cc === lastCc)) {
|
22136
|
+
const partIndex = (_data$part$index = (_data$part = data.part) == null ? void 0 : _data$part.index) != null ? _data$part$index : -1;
|
22137
|
+
if (!(sn === lastSn + 1 || sn === lastSn && partIndex === this.lastPartIndex + 1 || cc === this.lastCc)) {
|
21319
22138
|
cea608Parser1.reset();
|
21320
22139
|
cea608Parser2.reset();
|
21321
22140
|
}
|
@@ -21372,7 +22191,7 @@ class TimelineController {
|
|
21372
22191
|
frag: frag
|
21373
22192
|
});
|
21374
22193
|
}, error => {
|
21375
|
-
logger.log(`Failed to parse IMSC1: ${error}`);
|
22194
|
+
hls.logger.log(`Failed to parse IMSC1: ${error}`);
|
21376
22195
|
hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
|
21377
22196
|
success: false,
|
21378
22197
|
frag: frag,
|
@@ -21413,7 +22232,7 @@ class TimelineController {
|
|
21413
22232
|
this._fallbackToIMSC1(frag, payload);
|
21414
22233
|
}
|
21415
22234
|
// Something went wrong while parsing. Trigger event with success false.
|
21416
|
-
logger.log(`Failed to parse VTT cue: ${error}`);
|
22235
|
+
hls.logger.log(`Failed to parse VTT cue: ${error}`);
|
21417
22236
|
if (missingInitPTS && maxAvCC > frag.cc) {
|
21418
22237
|
return;
|
21419
22238
|
}
|
@@ -21474,12 +22293,7 @@ class TimelineController {
|
|
21474
22293
|
this.captionsTracks = {};
|
21475
22294
|
}
|
21476
22295
|
onFragParsingUserdata(event, data) {
|
21477
|
-
this.
|
21478
|
-
const {
|
21479
|
-
cea608Parser1,
|
21480
|
-
cea608Parser2
|
21481
|
-
} = this;
|
21482
|
-
if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
|
22296
|
+
if (!this.enabled || !this.config.enableCEA708Captions) {
|
21483
22297
|
return;
|
21484
22298
|
}
|
21485
22299
|
const {
|
@@ -21494,9 +22308,12 @@ class TimelineController {
|
|
21494
22308
|
for (let i = 0; i < samples.length; i++) {
|
21495
22309
|
const ccBytes = samples[i].bytes;
|
21496
22310
|
if (ccBytes) {
|
22311
|
+
if (!this.cea608Parser1) {
|
22312
|
+
this.initCea608Parsers();
|
22313
|
+
}
|
21497
22314
|
const ccdatas = this.extractCea608Data(ccBytes);
|
21498
|
-
cea608Parser1.addData(samples[i].pts, ccdatas[0]);
|
21499
|
-
cea608Parser2.addData(samples[i].pts, ccdatas[1]);
|
22315
|
+
this.cea608Parser1.addData(samples[i].pts, ccdatas[0]);
|
22316
|
+
this.cea608Parser2.addData(samples[i].pts, ccdatas[1]);
|
21500
22317
|
}
|
21501
22318
|
}
|
21502
22319
|
}
|
@@ -21692,7 +22509,7 @@ class CapLevelController {
|
|
21692
22509
|
const hls = this.hls;
|
21693
22510
|
const maxLevel = this.getMaxLevel(levels.length - 1);
|
21694
22511
|
if (maxLevel !== this.autoLevelCapping) {
|
21695
|
-
logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
|
22512
|
+
hls.logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
|
21696
22513
|
}
|
21697
22514
|
hls.autoLevelCapping = maxLevel;
|
21698
22515
|
if (hls.autoLevelCapping > this.autoLevelCapping && this.streamController) {
|
@@ -21870,10 +22687,10 @@ class FPSController {
|
|
21870
22687
|
totalDroppedFrames: droppedFrames
|
21871
22688
|
});
|
21872
22689
|
if (droppedFPS > 0) {
|
21873
|
-
// logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
|
22690
|
+
// hls.logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
|
21874
22691
|
if (currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
|
21875
22692
|
let currentLevel = hls.currentLevel;
|
21876
|
-
logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
|
22693
|
+
hls.logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
|
21877
22694
|
if (currentLevel > 0 && (hls.autoLevelCapping === -1 || hls.autoLevelCapping >= currentLevel)) {
|
21878
22695
|
currentLevel = currentLevel - 1;
|
21879
22696
|
hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, {
|
@@ -21905,7 +22722,6 @@ class FPSController {
|
|
21905
22722
|
}
|
21906
22723
|
}
|
21907
22724
|
|
21908
|
-
const LOGGER_PREFIX = '[eme]';
|
21909
22725
|
/**
|
21910
22726
|
* Controller to deal with encrypted media extensions (EME)
|
21911
22727
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/Encrypted_Media_Extensions_API
|
@@ -21913,8 +22729,9 @@ const LOGGER_PREFIX = '[eme]';
|
|
21913
22729
|
* @class
|
21914
22730
|
* @constructor
|
21915
22731
|
*/
|
21916
|
-
class EMEController {
|
22732
|
+
class EMEController extends Logger {
|
21917
22733
|
constructor(hls) {
|
22734
|
+
super('eme', hls.logger);
|
21918
22735
|
this.hls = void 0;
|
21919
22736
|
this.config = void 0;
|
21920
22737
|
this.media = null;
|
@@ -21924,12 +22741,100 @@ class EMEController {
|
|
21924
22741
|
this.mediaKeySessions = [];
|
21925
22742
|
this.keyIdToKeySessionPromise = {};
|
21926
22743
|
this.setMediaKeysQueue = EMEController.CDMCleanupPromise ? [EMEController.CDMCleanupPromise] : [];
|
21927
|
-
this.onMediaEncrypted =
|
21928
|
-
|
21929
|
-
|
21930
|
-
|
21931
|
-
|
21932
|
-
|
22744
|
+
this.onMediaEncrypted = event => {
|
22745
|
+
const {
|
22746
|
+
initDataType,
|
22747
|
+
initData
|
22748
|
+
} = event;
|
22749
|
+
this.debug(`"${event.type}" event: init data type: "${initDataType}"`);
|
22750
|
+
|
22751
|
+
// Ignore event when initData is null
|
22752
|
+
if (initData === null) {
|
22753
|
+
return;
|
22754
|
+
}
|
22755
|
+
let keyId;
|
22756
|
+
let keySystemDomain;
|
22757
|
+
if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) {
|
22758
|
+
// Match sinf keyId to playlist skd://keyId=
|
22759
|
+
const json = bin2str(new Uint8Array(initData));
|
22760
|
+
try {
|
22761
|
+
const sinf = base64Decode(JSON.parse(json).sinf);
|
22762
|
+
const tenc = parseSinf(new Uint8Array(sinf));
|
22763
|
+
if (!tenc) {
|
22764
|
+
return;
|
22765
|
+
}
|
22766
|
+
keyId = tenc.subarray(8, 24);
|
22767
|
+
keySystemDomain = KeySystems.FAIRPLAY;
|
22768
|
+
} catch (error) {
|
22769
|
+
this.warn('Failed to parse sinf "encrypted" event message initData');
|
22770
|
+
return;
|
22771
|
+
}
|
22772
|
+
} else {
|
22773
|
+
// Support clear-lead key-session creation (otherwise depend on playlist keys)
|
22774
|
+
const psshInfo = parsePssh(initData);
|
22775
|
+
if (psshInfo === null) {
|
22776
|
+
return;
|
22777
|
+
}
|
22778
|
+
if (psshInfo.version === 0 && psshInfo.systemId === KeySystemIds.WIDEVINE && psshInfo.data) {
|
22779
|
+
keyId = psshInfo.data.subarray(8, 24);
|
22780
|
+
}
|
22781
|
+
keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
|
22782
|
+
}
|
22783
|
+
if (!keySystemDomain || !keyId) {
|
22784
|
+
return;
|
22785
|
+
}
|
22786
|
+
const keyIdHex = Hex.hexDump(keyId);
|
22787
|
+
const {
|
22788
|
+
keyIdToKeySessionPromise,
|
22789
|
+
mediaKeySessions
|
22790
|
+
} = this;
|
22791
|
+
let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
|
22792
|
+
for (let i = 0; i < mediaKeySessions.length; i++) {
|
22793
|
+
// Match playlist key
|
22794
|
+
const keyContext = mediaKeySessions[i];
|
22795
|
+
const decryptdata = keyContext.decryptdata;
|
22796
|
+
if (decryptdata.pssh || !decryptdata.keyId) {
|
22797
|
+
continue;
|
22798
|
+
}
|
22799
|
+
const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
|
22800
|
+
if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
|
22801
|
+
keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
|
22802
|
+
delete keyIdToKeySessionPromise[oldKeyIdHex];
|
22803
|
+
decryptdata.pssh = new Uint8Array(initData);
|
22804
|
+
decryptdata.keyId = keyId;
|
22805
|
+
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
|
22806
|
+
return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
|
22807
|
+
});
|
22808
|
+
break;
|
22809
|
+
}
|
22810
|
+
}
|
22811
|
+
if (!keySessionContextPromise) {
|
22812
|
+
// Clear-lead key (not encountered in playlist)
|
22813
|
+
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
|
22814
|
+
keySystem,
|
22815
|
+
mediaKeys
|
22816
|
+
}) => {
|
22817
|
+
var _keySystemToKeySystem;
|
22818
|
+
this.throwIfDestroyed();
|
22819
|
+
const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
|
22820
|
+
decryptdata.pssh = new Uint8Array(initData);
|
22821
|
+
decryptdata.keyId = keyId;
|
22822
|
+
return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
|
22823
|
+
this.throwIfDestroyed();
|
22824
|
+
const keySessionContext = this.createMediaKeySessionContext({
|
22825
|
+
decryptdata,
|
22826
|
+
keySystem,
|
22827
|
+
mediaKeys
|
22828
|
+
});
|
22829
|
+
return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
|
22830
|
+
});
|
22831
|
+
});
|
22832
|
+
}
|
22833
|
+
keySessionContextPromise.catch(error => this.handleError(error));
|
22834
|
+
};
|
22835
|
+
this.onWaitingForKey = event => {
|
22836
|
+
this.log(`"${event.type}" event`);
|
22837
|
+
};
|
21933
22838
|
this.hls = hls;
|
21934
22839
|
this.config = hls.config;
|
21935
22840
|
this.registerListeners();
|
@@ -21943,9 +22848,9 @@ class EMEController {
|
|
21943
22848
|
config.licenseXhrSetup = config.licenseResponseCallback = undefined;
|
21944
22849
|
config.drmSystems = config.drmSystemOptions = {};
|
21945
22850
|
// @ts-ignore
|
21946
|
-
this.hls = this.
|
22851
|
+
this.hls = this.config = this.keyIdToKeySessionPromise = null;
|
21947
22852
|
// @ts-ignore
|
21948
|
-
this.
|
22853
|
+
this.onMediaEncrypted = this.onWaitingForKey = null;
|
21949
22854
|
}
|
21950
22855
|
registerListeners() {
|
21951
22856
|
this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
@@ -22209,100 +23114,6 @@ class EMEController {
|
|
22209
23114
|
}
|
22210
23115
|
return this.attemptKeySystemAccess(keySystemsToAttempt);
|
22211
23116
|
}
|
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
|
-
}
|
22306
23117
|
attemptSetMediaKeys(keySystem, mediaKeys) {
|
22307
23118
|
const queue = this.setMediaKeysQueue.slice();
|
22308
23119
|
this.log(`Setting media-keys for "${keySystem}"`);
|
@@ -22895,20 +23706,6 @@ class SfItem {
|
|
22895
23706
|
}
|
22896
23707
|
}
|
22897
23708
|
|
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
|
-
|
22912
23709
|
const DICT = 'Dict';
|
22913
23710
|
|
22914
23711
|
function format(value) {
|
@@ -22932,29 +23729,27 @@ function throwError(action, src, type, cause) {
|
|
22932
23729
|
});
|
22933
23730
|
}
|
22934
23731
|
|
22935
|
-
|
22936
|
-
|
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;
|
23732
|
+
function serializeError(src, type, cause) {
|
23733
|
+
return throwError('serialize', src, type, cause);
|
22947
23734
|
}
|
22948
23735
|
|
22949
|
-
|
22950
|
-
|
22951
|
-
|
23736
|
+
/**
|
23737
|
+
* A class to represent structured field tokens when `Symbol` is not available.
|
23738
|
+
*
|
23739
|
+
* @group Structured Field
|
23740
|
+
*
|
23741
|
+
* @beta
|
23742
|
+
*/
|
23743
|
+
class SfToken {
|
23744
|
+
constructor(description) {
|
23745
|
+
this.description = void 0;
|
23746
|
+
this.description = description;
|
23747
|
+
}
|
23748
|
+
}
|
22952
23749
|
|
22953
|
-
const
|
23750
|
+
const BARE_ITEM = 'Bare Item';
|
22954
23751
|
|
22955
|
-
|
22956
|
-
return throwError('serialize', src, type, cause);
|
22957
|
-
}
|
23752
|
+
const BOOLEAN = 'Boolean';
|
22958
23753
|
|
22959
23754
|
// 4.1.9. Serializing a Boolean
|
22960
23755
|
//
|
@@ -22993,6 +23788,8 @@ function base64encode(binary) {
|
|
22993
23788
|
return btoa(String.fromCharCode(...binary));
|
22994
23789
|
}
|
22995
23790
|
|
23791
|
+
const BYTES = 'Byte Sequence';
|
23792
|
+
|
22996
23793
|
// 4.1.8. Serializing a Byte Sequence
|
22997
23794
|
//
|
22998
23795
|
// Given a Byte Sequence as input_bytes, return an ASCII string suitable
|
@@ -23024,6 +23821,12 @@ function serializeByteSequence(value) {
|
|
23024
23821
|
return `:${base64encode(value)}:`;
|
23025
23822
|
}
|
23026
23823
|
|
23824
|
+
const INTEGER = 'Integer';
|
23825
|
+
|
23826
|
+
function isInvalidInt(value) {
|
23827
|
+
return value < -999999999999999 || 999999999999999 < value;
|
23828
|
+
}
|
23829
|
+
|
23027
23830
|
// 4.1.4. Serializing an Integer
|
23028
23831
|
//
|
23029
23832
|
// Given an Integer as input_integer, return an ASCII string suitable
|
@@ -23089,6 +23892,8 @@ function roundToEven(value, precision) {
|
|
23089
23892
|
}
|
23090
23893
|
}
|
23091
23894
|
|
23895
|
+
const DECIMAL = 'Decimal';
|
23896
|
+
|
23092
23897
|
// 4.1.5. Serializing a Decimal
|
23093
23898
|
//
|
23094
23899
|
// Given a decimal number as input_decimal, return an ASCII string
|
@@ -23134,6 +23939,8 @@ function serializeDecimal(value) {
|
|
23134
23939
|
|
23135
23940
|
const STRING = 'String';
|
23136
23941
|
|
23942
|
+
const STRING_REGEX = /[\x00-\x1f\x7f]+/; // eslint-disable-line no-control-regex
|
23943
|
+
|
23137
23944
|
// 4.1.6. Serializing a String
|
23138
23945
|
//
|
23139
23946
|
// Given a String as input_string, return an ASCII string suitable for
|
@@ -23169,6 +23976,8 @@ function symbolToStr(symbol) {
|
|
23169
23976
|
return symbol.description || symbol.toString().slice(7, -1);
|
23170
23977
|
}
|
23171
23978
|
|
23979
|
+
const TOKEN = 'Token';
|
23980
|
+
|
23172
23981
|
function serializeToken(token) {
|
23173
23982
|
const value = symbolToStr(token);
|
23174
23983
|
if (/^([a-zA-Z*])([!#$%&'*+\-.^_`|~\w:/]*)$/.test(value) === false) {
|
@@ -23236,6 +24045,8 @@ function serializeBareItem(value) {
|
|
23236
24045
|
}
|
23237
24046
|
}
|
23238
24047
|
|
24048
|
+
const KEY = 'Key';
|
24049
|
+
|
23239
24050
|
// 4.1.1.3. Serializing a Key
|
23240
24051
|
//
|
23241
24052
|
// Given a key as input_key, return an ASCII string suitable for use in
|
@@ -23477,36 +24288,6 @@ function urlToRelativePath(url, base) {
|
|
23477
24288
|
return toPath.join('/');
|
23478
24289
|
}
|
23479
24290
|
|
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
|
-
|
23510
24291
|
const toRounded = value => Math.round(value);
|
23511
24292
|
const toUrlSafe = (value, options) => {
|
23512
24293
|
if (options != null && options.baseUrl) {
|
@@ -23732,6 +24513,36 @@ function appendCmcdQuery(url, cmcd, options) {
|
|
23732
24513
|
return `${url}${separator}${query}`;
|
23733
24514
|
}
|
23734
24515
|
|
24516
|
+
/**
|
24517
|
+
* Generate a random v4 UUID
|
24518
|
+
*
|
24519
|
+
* @returns A random v4 UUID
|
24520
|
+
*
|
24521
|
+
* @group Utils
|
24522
|
+
*
|
24523
|
+
* @beta
|
24524
|
+
*/
|
24525
|
+
function uuid() {
|
24526
|
+
try {
|
24527
|
+
return crypto.randomUUID();
|
24528
|
+
} catch (error) {
|
24529
|
+
try {
|
24530
|
+
const url = URL.createObjectURL(new Blob());
|
24531
|
+
const uuid = url.toString();
|
24532
|
+
URL.revokeObjectURL(url);
|
24533
|
+
return uuid.slice(uuid.lastIndexOf('/') + 1);
|
24534
|
+
} catch (error) {
|
24535
|
+
let dt = new Date().getTime();
|
24536
|
+
const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
24537
|
+
const r = (dt + Math.random() * 16) % 16 | 0;
|
24538
|
+
dt = Math.floor(dt / 16);
|
24539
|
+
return (c == 'x' ? r : r & 0x3 | 0x8).toString(16);
|
24540
|
+
});
|
24541
|
+
return uuid;
|
24542
|
+
}
|
24543
|
+
}
|
24544
|
+
}
|
24545
|
+
|
23735
24546
|
/**
|
23736
24547
|
* Controller to deal with Common Media Client Data (CMCD)
|
23737
24548
|
* @see https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf
|
@@ -23795,6 +24606,12 @@ class CMCDController {
|
|
23795
24606
|
data.tb = this.getTopBandwidth(ot) / 1000;
|
23796
24607
|
data.bl = this.getBufferLength(ot);
|
23797
24608
|
}
|
24609
|
+
const next = this.getNextFrag(fragment);
|
24610
|
+
if (next) {
|
24611
|
+
if (next.url && next.url !== fragment.url) {
|
24612
|
+
data.nor = next.url;
|
24613
|
+
}
|
24614
|
+
}
|
23798
24615
|
this.apply(context, data);
|
23799
24616
|
} catch (error) {
|
23800
24617
|
logger.warn('Could not generate segment CMCD data.', error);
|
@@ -23887,7 +24704,7 @@ class CMCDController {
|
|
23887
24704
|
data.su = this.buffering;
|
23888
24705
|
}
|
23889
24706
|
|
23890
|
-
// TODO: Implement rtp, nrr,
|
24707
|
+
// TODO: Implement rtp, nrr, dl
|
23891
24708
|
|
23892
24709
|
const {
|
23893
24710
|
includeKeys
|
@@ -23898,15 +24715,28 @@ class CMCDController {
|
|
23898
24715
|
return acc;
|
23899
24716
|
}, {});
|
23900
24717
|
}
|
24718
|
+
const options = {
|
24719
|
+
baseUrl: context.url
|
24720
|
+
};
|
23901
24721
|
if (this.useHeaders) {
|
23902
24722
|
if (!context.headers) {
|
23903
24723
|
context.headers = {};
|
23904
24724
|
}
|
23905
|
-
appendCmcdHeaders(context.headers, data);
|
24725
|
+
appendCmcdHeaders(context.headers, data, options);
|
23906
24726
|
} else {
|
23907
|
-
context.url = appendCmcdQuery(context.url, data);
|
24727
|
+
context.url = appendCmcdQuery(context.url, data, options);
|
24728
|
+
}
|
24729
|
+
}
|
24730
|
+
getNextFrag(fragment) {
|
24731
|
+
var _this$hls$levels$frag;
|
24732
|
+
const levelDetails = (_this$hls$levels$frag = this.hls.levels[fragment.level]) == null ? void 0 : _this$hls$levels$frag.details;
|
24733
|
+
if (levelDetails) {
|
24734
|
+
const index = fragment.sn - levelDetails.startSN;
|
24735
|
+
return levelDetails.fragments[index + 1];
|
23908
24736
|
}
|
24737
|
+
return undefined;
|
23909
24738
|
}
|
24739
|
+
|
23910
24740
|
/**
|
23911
24741
|
* The CMCD object type.
|
23912
24742
|
*/
|
@@ -24035,10 +24865,10 @@ class CMCDController {
|
|
24035
24865
|
}
|
24036
24866
|
|
24037
24867
|
const PATHWAY_PENALTY_DURATION_MS = 300000;
|
24038
|
-
class ContentSteeringController {
|
24868
|
+
class ContentSteeringController extends Logger {
|
24039
24869
|
constructor(hls) {
|
24870
|
+
super('content-steering', hls.logger);
|
24040
24871
|
this.hls = void 0;
|
24041
|
-
this.log = void 0;
|
24042
24872
|
this.loader = null;
|
24043
24873
|
this.uri = null;
|
24044
24874
|
this.pathwayId = '.';
|
@@ -24053,7 +24883,6 @@ class ContentSteeringController {
|
|
24053
24883
|
this.subtitleTracks = null;
|
24054
24884
|
this.penalizedPathways = {};
|
24055
24885
|
this.hls = hls;
|
24056
|
-
this.log = logger.log.bind(logger, `[content-steering]:`);
|
24057
24886
|
this.registerListeners();
|
24058
24887
|
}
|
24059
24888
|
registerListeners() {
|
@@ -24177,7 +25006,7 @@ class ContentSteeringController {
|
|
24177
25006
|
errorAction.resolved = this.pathwayId !== errorPathway;
|
24178
25007
|
}
|
24179
25008
|
if (!errorAction.resolved) {
|
24180
|
-
|
25009
|
+
this.warn(`Could not resolve ${data.details} ("${data.error.message}") with content-steering for Pathway: ${errorPathway} levels: ${levels ? levels.length : levels} priorities: ${JSON.stringify(pathwayPriority)} penalized: ${JSON.stringify(this.penalizedPathways)}`);
|
24181
25010
|
}
|
24182
25011
|
}
|
24183
25012
|
}
|
@@ -24348,7 +25177,7 @@ class ContentSteeringController {
|
|
24348
25177
|
onSuccess: (response, stats, context, networkDetails) => {
|
24349
25178
|
this.log(`Loaded steering manifest: "${url}"`);
|
24350
25179
|
const steeringData = response.data;
|
24351
|
-
if (steeringData.VERSION !== 1) {
|
25180
|
+
if ((steeringData == null ? void 0 : steeringData.VERSION) !== 1) {
|
24352
25181
|
this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
|
24353
25182
|
return;
|
24354
25183
|
}
|
@@ -25318,7 +26147,7 @@ function timelineConfig() {
|
|
25318
26147
|
/**
|
25319
26148
|
* @ignore
|
25320
26149
|
*/
|
25321
|
-
function mergeConfig(defaultConfig, userConfig) {
|
26150
|
+
function mergeConfig(defaultConfig, userConfig, logger) {
|
25322
26151
|
if ((userConfig.liveSyncDurationCount || userConfig.liveMaxLatencyDurationCount) && (userConfig.liveSyncDuration || userConfig.liveMaxLatencyDuration)) {
|
25323
26152
|
throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration");
|
25324
26153
|
}
|
@@ -25388,7 +26217,7 @@ function deepCpy(obj) {
|
|
25388
26217
|
/**
|
25389
26218
|
* @ignore
|
25390
26219
|
*/
|
25391
|
-
function enableStreamingMode(config) {
|
26220
|
+
function enableStreamingMode(config, logger) {
|
25392
26221
|
const currentLoader = config.loader;
|
25393
26222
|
if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
|
25394
26223
|
// If a developer has configured their own loader, respect that choice
|
@@ -25405,10 +26234,9 @@ function enableStreamingMode(config) {
|
|
25405
26234
|
}
|
25406
26235
|
}
|
25407
26236
|
|
25408
|
-
let chromeOrFirefox;
|
25409
26237
|
class LevelController extends BasePlaylistController {
|
25410
26238
|
constructor(hls, contentSteeringController) {
|
25411
|
-
super(hls, '
|
26239
|
+
super(hls, 'level-controller');
|
25412
26240
|
this._levels = [];
|
25413
26241
|
this._firstLevel = -1;
|
25414
26242
|
this._maxAutoLevel = -1;
|
@@ -25479,23 +26307,15 @@ class LevelController extends BasePlaylistController {
|
|
25479
26307
|
let videoCodecFound = false;
|
25480
26308
|
let audioCodecFound = false;
|
25481
26309
|
data.levels.forEach(levelParsed => {
|
25482
|
-
var
|
26310
|
+
var _videoCodec;
|
25483
26311
|
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
|
25487
26312
|
let {
|
25488
26313
|
audioCodec,
|
25489
26314
|
videoCodec
|
25490
26315
|
} = 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
|
-
}
|
25497
26316
|
if (audioCodec) {
|
25498
|
-
|
26317
|
+
// Returns empty and set to undefined for 'mp4a.40.34' with fallback to 'audio/mpeg' SourceBuffer
|
26318
|
+
levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource) || undefined;
|
25499
26319
|
}
|
25500
26320
|
if (((_videoCodec = videoCodec) == null ? void 0 : _videoCodec.indexOf('avc1')) === 0) {
|
25501
26321
|
videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
|
@@ -26081,6 +26901,8 @@ class KeyLoader {
|
|
26081
26901
|
}
|
26082
26902
|
return this.loadKeyEME(keyInfo, frag);
|
26083
26903
|
case 'AES-128':
|
26904
|
+
case 'AES-256':
|
26905
|
+
case 'AES-256-CTR':
|
26084
26906
|
return this.loadKeyHTTP(keyInfo, frag);
|
26085
26907
|
default:
|
26086
26908
|
return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`)));
|
@@ -26218,8 +27040,9 @@ const STALL_MINIMUM_DURATION_MS = 250;
|
|
26218
27040
|
const MAX_START_GAP_JUMP = 2.0;
|
26219
27041
|
const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
|
26220
27042
|
const SKIP_BUFFER_RANGE_START = 0.05;
|
26221
|
-
class GapController {
|
27043
|
+
class GapController extends Logger {
|
26222
27044
|
constructor(config, media, fragmentTracker, hls) {
|
27045
|
+
super('gap-controller', hls.logger);
|
26223
27046
|
this.config = void 0;
|
26224
27047
|
this.media = null;
|
26225
27048
|
this.fragmentTracker = void 0;
|
@@ -26229,6 +27052,7 @@ class GapController {
|
|
26229
27052
|
this.stalled = null;
|
26230
27053
|
this.moved = false;
|
26231
27054
|
this.seeking = false;
|
27055
|
+
this.ended = 0;
|
26232
27056
|
this.config = config;
|
26233
27057
|
this.media = media;
|
26234
27058
|
this.fragmentTracker = fragmentTracker;
|
@@ -26246,7 +27070,7 @@ class GapController {
|
|
26246
27070
|
*
|
26247
27071
|
* @param lastCurrentTime - Previously read playhead position
|
26248
27072
|
*/
|
26249
|
-
poll(lastCurrentTime, activeFrag) {
|
27073
|
+
poll(lastCurrentTime, activeFrag, levelDetails, state) {
|
26250
27074
|
const {
|
26251
27075
|
config,
|
26252
27076
|
media,
|
@@ -26265,6 +27089,7 @@ class GapController {
|
|
26265
27089
|
|
26266
27090
|
// The playhead is moving, no-op
|
26267
27091
|
if (currentTime !== lastCurrentTime) {
|
27092
|
+
this.ended = 0;
|
26268
27093
|
this.moved = true;
|
26269
27094
|
if (!seeking) {
|
26270
27095
|
this.nudgeRetry = 0;
|
@@ -26273,7 +27098,7 @@ class GapController {
|
|
26273
27098
|
// The playhead is now moving, but was previously stalled
|
26274
27099
|
if (this.stallReported) {
|
26275
27100
|
const _stalledDuration = self.performance.now() - stalled;
|
26276
|
-
|
27101
|
+
this.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
|
26277
27102
|
this.stallReported = false;
|
26278
27103
|
}
|
26279
27104
|
this.stalled = null;
|
@@ -26309,7 +27134,6 @@ class GapController {
|
|
26309
27134
|
// Skip start gaps if we haven't played, but the last poll detected the start of a stall
|
26310
27135
|
// The addition poll gives the browser a chance to jump the gap for us
|
26311
27136
|
if (!this.moved && this.stalled !== null) {
|
26312
|
-
var _level$details;
|
26313
27137
|
// There is no playable buffer (seeked, waiting for buffer)
|
26314
27138
|
const isBuffered = bufferInfo.len > 0;
|
26315
27139
|
if (!isBuffered && !nextStart) {
|
@@ -26321,9 +27145,8 @@ class GapController {
|
|
26321
27145
|
// When joining a live stream with audio tracks, account for live playlist window sliding by allowing
|
26322
27146
|
// a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
|
26323
27147
|
// that begins over 1 target duration after the video start position.
|
26324
|
-
const
|
26325
|
-
const
|
26326
|
-
const maxStartGapJump = isLive ? level.details.targetduration * 2 : MAX_START_GAP_JUMP;
|
27148
|
+
const isLive = !!(levelDetails != null && levelDetails.live);
|
27149
|
+
const maxStartGapJump = isLive ? levelDetails.targetduration * 2 : MAX_START_GAP_JUMP;
|
26327
27150
|
const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
|
26328
27151
|
if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
|
26329
27152
|
if (!media.paused) {
|
@@ -26341,6 +27164,17 @@ class GapController {
|
|
26341
27164
|
}
|
26342
27165
|
const stalledDuration = tnow - stalled;
|
26343
27166
|
if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) {
|
27167
|
+
// Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
|
27168
|
+
if (state === State.ENDED && !(levelDetails && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? void 0 : levelDetails.edge) || 0)) < 1) {
|
27169
|
+
if (stalledDuration < 1000 || this.ended) {
|
27170
|
+
return;
|
27171
|
+
}
|
27172
|
+
this.ended = currentTime;
|
27173
|
+
this.hls.trigger(Events.MEDIA_ENDED, {
|
27174
|
+
stalled: true
|
27175
|
+
});
|
27176
|
+
return;
|
27177
|
+
}
|
26344
27178
|
// Report stalling after trying to fix
|
26345
27179
|
this._reportStall(bufferInfo);
|
26346
27180
|
if (!this.media) {
|
@@ -26384,7 +27218,7 @@ class GapController {
|
|
26384
27218
|
// needs to cross some sort of threshold covering all source-buffers content
|
26385
27219
|
// to start playing properly.
|
26386
27220
|
if ((bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
|
26387
|
-
|
27221
|
+
this.warn('Trying to nudge playhead over buffer-hole');
|
26388
27222
|
// Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
|
26389
27223
|
// We only try to jump the hole if it's under the configured size
|
26390
27224
|
// Reset stalled so to rearm watchdog timer
|
@@ -26408,7 +27242,7 @@ class GapController {
|
|
26408
27242
|
// Report stalled error once
|
26409
27243
|
this.stallReported = true;
|
26410
27244
|
const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${JSON.stringify(bufferInfo)})`);
|
26411
|
-
|
27245
|
+
this.warn(error.message);
|
26412
27246
|
hls.trigger(Events.ERROR, {
|
26413
27247
|
type: ErrorTypes.MEDIA_ERROR,
|
26414
27248
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
@@ -26476,7 +27310,7 @@ class GapController {
|
|
26476
27310
|
}
|
26477
27311
|
}
|
26478
27312
|
const targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
|
26479
|
-
|
27313
|
+
this.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
|
26480
27314
|
this.moved = true;
|
26481
27315
|
this.stalled = null;
|
26482
27316
|
media.currentTime = targetTime;
|
@@ -26517,7 +27351,7 @@ class GapController {
|
|
26517
27351
|
const targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset;
|
26518
27352
|
// playback stalled in buffered area ... let's nudge currentTime to try to overcome this
|
26519
27353
|
const error = new Error(`Nudging 'currentTime' from ${currentTime} to ${targetTime}`);
|
26520
|
-
|
27354
|
+
this.warn(error.message);
|
26521
27355
|
media.currentTime = targetTime;
|
26522
27356
|
hls.trigger(Events.ERROR, {
|
26523
27357
|
type: ErrorTypes.MEDIA_ERROR,
|
@@ -26527,7 +27361,7 @@ class GapController {
|
|
26527
27361
|
});
|
26528
27362
|
} else {
|
26529
27363
|
const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`);
|
26530
|
-
|
27364
|
+
this.error(error.message);
|
26531
27365
|
hls.trigger(Events.ERROR, {
|
26532
27366
|
type: ErrorTypes.MEDIA_ERROR,
|
26533
27367
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
@@ -26542,7 +27376,7 @@ const TICK_INTERVAL = 100; // how often to tick in ms
|
|
26542
27376
|
|
26543
27377
|
class StreamController extends BaseStreamController {
|
26544
27378
|
constructor(hls, fragmentTracker, keyLoader) {
|
26545
|
-
super(hls, fragmentTracker, keyLoader, '
|
27379
|
+
super(hls, fragmentTracker, keyLoader, 'stream-controller', PlaylistLevelType.MAIN);
|
26546
27380
|
this.audioCodecSwap = false;
|
26547
27381
|
this.gapController = null;
|
26548
27382
|
this.level = -1;
|
@@ -26550,27 +27384,43 @@ class StreamController extends BaseStreamController {
|
|
26550
27384
|
this.altAudio = false;
|
26551
27385
|
this.audioOnly = false;
|
26552
27386
|
this.fragPlaying = null;
|
26553
|
-
this.onvplaying = null;
|
26554
|
-
this.onvseeked = null;
|
26555
27387
|
this.fragLastKbps = 0;
|
26556
27388
|
this.couldBacktrack = false;
|
26557
27389
|
this.backtrackFragment = null;
|
26558
27390
|
this.audioCodecSwitch = false;
|
26559
27391
|
this.videoBuffer = null;
|
26560
|
-
this.
|
27392
|
+
this.onMediaPlaying = () => {
|
27393
|
+
// tick to speed up FRAG_CHANGED triggering
|
27394
|
+
this.tick();
|
27395
|
+
};
|
27396
|
+
this.onMediaSeeked = () => {
|
27397
|
+
const media = this.media;
|
27398
|
+
const currentTime = media ? media.currentTime : null;
|
27399
|
+
if (isFiniteNumber(currentTime)) {
|
27400
|
+
this.log(`Media seeked to ${currentTime.toFixed(3)}`);
|
27401
|
+
}
|
27402
|
+
|
27403
|
+
// If seeked was issued before buffer was appended do not tick immediately
|
27404
|
+
const bufferInfo = this.getMainFwdBufferInfo();
|
27405
|
+
if (bufferInfo === null || bufferInfo.len === 0) {
|
27406
|
+
this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
|
27407
|
+
return;
|
27408
|
+
}
|
27409
|
+
|
27410
|
+
// tick to speed up FRAG_CHANGED triggering
|
27411
|
+
this.tick();
|
27412
|
+
};
|
27413
|
+
this.registerListeners();
|
26561
27414
|
}
|
26562
|
-
|
27415
|
+
registerListeners() {
|
27416
|
+
super.registerListeners();
|
26563
27417
|
const {
|
26564
27418
|
hls
|
26565
27419
|
} = 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);
|
26569
27420
|
hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
26570
27421
|
hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
|
26571
27422
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
26572
27423
|
hls.on(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
|
26573
|
-
hls.on(Events.ERROR, this.onError, this);
|
26574
27424
|
hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
26575
27425
|
hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
26576
27426
|
hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -26578,17 +27428,14 @@ class StreamController extends BaseStreamController {
|
|
26578
27428
|
hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
|
26579
27429
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
26580
27430
|
}
|
26581
|
-
|
27431
|
+
unregisterListeners() {
|
27432
|
+
super.unregisterListeners();
|
26582
27433
|
const {
|
26583
27434
|
hls
|
26584
27435
|
} = 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);
|
26588
27436
|
hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
26589
27437
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
26590
27438
|
hls.off(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
|
26591
|
-
hls.off(Events.ERROR, this.onError, this);
|
26592
27439
|
hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
26593
27440
|
hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
26594
27441
|
hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -26597,7 +27444,9 @@ class StreamController extends BaseStreamController {
|
|
26597
27444
|
hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
26598
27445
|
}
|
26599
27446
|
onHandlerDestroying() {
|
26600
|
-
|
27447
|
+
// @ts-ignore
|
27448
|
+
this.onMediaPlaying = this.onMediaSeeked = null;
|
27449
|
+
this.unregisterListeners();
|
26601
27450
|
super.onHandlerDestroying();
|
26602
27451
|
}
|
26603
27452
|
startLoad(startPosition) {
|
@@ -26717,7 +27566,7 @@ class StreamController extends BaseStreamController {
|
|
26717
27566
|
if (this.altAudio && this.audioOnly) {
|
26718
27567
|
return;
|
26719
27568
|
}
|
26720
|
-
if (!(levels != null && levels[level])) {
|
27569
|
+
if (!this.buffering || !(levels != null && levels[level])) {
|
26721
27570
|
return;
|
26722
27571
|
}
|
26723
27572
|
const levelInfo = levels[level];
|
@@ -26925,20 +27774,17 @@ class StreamController extends BaseStreamController {
|
|
26925
27774
|
onMediaAttached(event, data) {
|
26926
27775
|
super.onMediaAttached(event, data);
|
26927
27776
|
const media = data.media;
|
26928
|
-
|
26929
|
-
|
26930
|
-
media.addEventListener('playing', this.onvplaying);
|
26931
|
-
media.addEventListener('seeked', this.onvseeked);
|
27777
|
+
media.addEventListener('playing', this.onMediaPlaying);
|
27778
|
+
media.addEventListener('seeked', this.onMediaSeeked);
|
26932
27779
|
this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
|
26933
27780
|
}
|
26934
27781
|
onMediaDetaching() {
|
26935
27782
|
const {
|
26936
27783
|
media
|
26937
27784
|
} = this;
|
26938
|
-
if (media
|
26939
|
-
media.removeEventListener('playing', this.
|
26940
|
-
media.removeEventListener('seeked', this.
|
26941
|
-
this.onvplaying = this.onvseeked = null;
|
27785
|
+
if (media) {
|
27786
|
+
media.removeEventListener('playing', this.onMediaPlaying);
|
27787
|
+
media.removeEventListener('seeked', this.onMediaSeeked);
|
26942
27788
|
this.videoBuffer = null;
|
26943
27789
|
}
|
26944
27790
|
this.fragPlaying = null;
|
@@ -26948,27 +27794,6 @@ class StreamController extends BaseStreamController {
|
|
26948
27794
|
}
|
26949
27795
|
super.onMediaDetaching();
|
26950
27796
|
}
|
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
|
-
}
|
26972
27797
|
onManifestLoading() {
|
26973
27798
|
// reset buffer on manifest loading
|
26974
27799
|
this.log('Trigger BUFFER_RESET');
|
@@ -27260,8 +28085,10 @@ class StreamController extends BaseStreamController {
|
|
27260
28085
|
}
|
27261
28086
|
if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) {
|
27262
28087
|
// Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
|
27263
|
-
const
|
27264
|
-
|
28088
|
+
const state = this.state;
|
28089
|
+
const activeFrag = state !== State.IDLE ? this.fragCurrent : null;
|
28090
|
+
const levelDetails = this.getLevelDetails();
|
28091
|
+
gapController.poll(this.lastCurrentTime, activeFrag, levelDetails, state);
|
27265
28092
|
}
|
27266
28093
|
this.lastCurrentTime = media.currentTime;
|
27267
28094
|
}
|
@@ -27699,7 +28526,7 @@ class Hls {
|
|
27699
28526
|
* Get the video-dev/hls.js package version.
|
27700
28527
|
*/
|
27701
28528
|
static get version() {
|
27702
|
-
return "1.5.
|
28529
|
+
return "1.5.5-0.canary.9978";
|
27703
28530
|
}
|
27704
28531
|
|
27705
28532
|
/**
|
@@ -27762,9 +28589,12 @@ class Hls {
|
|
27762
28589
|
* The configuration object provided on player instantiation.
|
27763
28590
|
*/
|
27764
28591
|
this.userConfig = void 0;
|
28592
|
+
/**
|
28593
|
+
* The logger functions used by this player instance, configured on player instantiation.
|
28594
|
+
*/
|
28595
|
+
this.logger = void 0;
|
27765
28596
|
this.coreComponents = void 0;
|
27766
28597
|
this.networkControllers = void 0;
|
27767
|
-
this.started = false;
|
27768
28598
|
this._emitter = new EventEmitter();
|
27769
28599
|
this._autoLevelCapping = -1;
|
27770
28600
|
this._maxHdcpLevel = null;
|
@@ -27781,11 +28611,11 @@ class Hls {
|
|
27781
28611
|
this._media = null;
|
27782
28612
|
this.url = null;
|
27783
28613
|
this.triggeringException = void 0;
|
27784
|
-
enableLogs(userConfig.debug || false, 'Hls instance');
|
27785
|
-
const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig);
|
28614
|
+
const logger = this.logger = enableLogs(userConfig.debug || false, 'Hls instance');
|
28615
|
+
const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig, logger);
|
27786
28616
|
this.userConfig = userConfig;
|
27787
28617
|
if (config.progressive) {
|
27788
|
-
enableStreamingMode(config);
|
28618
|
+
enableStreamingMode(config, logger);
|
27789
28619
|
}
|
27790
28620
|
|
27791
28621
|
// core controllers and network loaders
|
@@ -27884,7 +28714,7 @@ class Hls {
|
|
27884
28714
|
try {
|
27885
28715
|
return this.emit(event, event, eventObject);
|
27886
28716
|
} catch (error) {
|
27887
|
-
logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
|
28717
|
+
this.logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
|
27888
28718
|
// Prevent recursion in error event handlers that throw #5497
|
27889
28719
|
if (!this.triggeringException) {
|
27890
28720
|
this.triggeringException = true;
|
@@ -27910,7 +28740,7 @@ class Hls {
|
|
27910
28740
|
* Dispose of the instance
|
27911
28741
|
*/
|
27912
28742
|
destroy() {
|
27913
|
-
logger.log('destroy');
|
28743
|
+
this.logger.log('destroy');
|
27914
28744
|
this.trigger(Events.DESTROYING, undefined);
|
27915
28745
|
this.detachMedia();
|
27916
28746
|
this.removeAllListeners();
|
@@ -27931,7 +28761,7 @@ class Hls {
|
|
27931
28761
|
* Attaches Hls.js to a media element
|
27932
28762
|
*/
|
27933
28763
|
attachMedia(media) {
|
27934
|
-
logger.log('attachMedia');
|
28764
|
+
this.logger.log('attachMedia');
|
27935
28765
|
this._media = media;
|
27936
28766
|
this.trigger(Events.MEDIA_ATTACHING, {
|
27937
28767
|
media: media
|
@@ -27942,7 +28772,7 @@ class Hls {
|
|
27942
28772
|
* Detach Hls.js from the media
|
27943
28773
|
*/
|
27944
28774
|
detachMedia() {
|
27945
|
-
logger.log('detachMedia');
|
28775
|
+
this.logger.log('detachMedia');
|
27946
28776
|
this.trigger(Events.MEDIA_DETACHING, undefined);
|
27947
28777
|
this._media = null;
|
27948
28778
|
}
|
@@ -27959,7 +28789,7 @@ class Hls {
|
|
27959
28789
|
});
|
27960
28790
|
this._autoLevelCapping = -1;
|
27961
28791
|
this._maxHdcpLevel = null;
|
27962
|
-
logger.log(`loadSource:${loadingSource}`);
|
28792
|
+
this.logger.log(`loadSource:${loadingSource}`);
|
27963
28793
|
if (media && loadedSource && (loadedSource !== loadingSource || this.bufferController.hasSourceTypes())) {
|
27964
28794
|
this.detachMedia();
|
27965
28795
|
this.attachMedia(media);
|
@@ -27978,8 +28808,7 @@ class Hls {
|
|
27978
28808
|
* Defaults to -1 (None: starts from earliest point)
|
27979
28809
|
*/
|
27980
28810
|
startLoad(startPosition = -1) {
|
27981
|
-
logger.log(`startLoad(${startPosition})`);
|
27982
|
-
this.started = true;
|
28811
|
+
this.logger.log(`startLoad(${startPosition})`);
|
27983
28812
|
this.networkControllers.forEach(controller => {
|
27984
28813
|
controller.startLoad(startPosition);
|
27985
28814
|
});
|
@@ -27989,34 +28818,31 @@ class Hls {
|
|
27989
28818
|
* Stop loading of any stream data.
|
27990
28819
|
*/
|
27991
28820
|
stopLoad() {
|
27992
|
-
logger.log('stopLoad');
|
27993
|
-
this.started = false;
|
28821
|
+
this.logger.log('stopLoad');
|
27994
28822
|
this.networkControllers.forEach(controller => {
|
27995
28823
|
controller.stopLoad();
|
27996
28824
|
});
|
27997
28825
|
}
|
27998
28826
|
|
27999
28827
|
/**
|
28000
|
-
* Resumes stream controller segment loading
|
28828
|
+
* Resumes stream controller segment loading after `pauseBuffering` has been called.
|
28001
28829
|
*/
|
28002
28830
|
resumeBuffering() {
|
28003
|
-
|
28004
|
-
|
28005
|
-
|
28006
|
-
|
28007
|
-
|
28008
|
-
});
|
28009
|
-
}
|
28831
|
+
this.networkControllers.forEach(controller => {
|
28832
|
+
if (controller.resumeBuffering) {
|
28833
|
+
controller.resumeBuffering();
|
28834
|
+
}
|
28835
|
+
});
|
28010
28836
|
}
|
28011
28837
|
|
28012
28838
|
/**
|
28013
|
-
*
|
28839
|
+
* Prevents stream controller from loading new segments until `resumeBuffering` is called.
|
28014
28840
|
* This allows for media buffering to be paused without interupting playlist loading.
|
28015
28841
|
*/
|
28016
28842
|
pauseBuffering() {
|
28017
28843
|
this.networkControllers.forEach(controller => {
|
28018
|
-
if (
|
28019
|
-
controller.
|
28844
|
+
if (controller.pauseBuffering) {
|
28845
|
+
controller.pauseBuffering();
|
28020
28846
|
}
|
28021
28847
|
});
|
28022
28848
|
}
|
@@ -28025,7 +28851,7 @@ class Hls {
|
|
28025
28851
|
* Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
|
28026
28852
|
*/
|
28027
28853
|
swapAudioCodec() {
|
28028
|
-
logger.log('swapAudioCodec');
|
28854
|
+
this.logger.log('swapAudioCodec');
|
28029
28855
|
this.streamController.swapAudioCodec();
|
28030
28856
|
}
|
28031
28857
|
|
@@ -28036,7 +28862,7 @@ class Hls {
|
|
28036
28862
|
* Automatic recovery of media-errors by this process is configurable.
|
28037
28863
|
*/
|
28038
28864
|
recoverMediaError() {
|
28039
|
-
logger.log('recoverMediaError');
|
28865
|
+
this.logger.log('recoverMediaError');
|
28040
28866
|
const media = this._media;
|
28041
28867
|
this.detachMedia();
|
28042
28868
|
if (media) {
|
@@ -28066,7 +28892,7 @@ class Hls {
|
|
28066
28892
|
* 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.
|
28067
28893
|
*/
|
28068
28894
|
set currentLevel(newLevel) {
|
28069
|
-
logger.log(`set currentLevel:${newLevel}`);
|
28895
|
+
this.logger.log(`set currentLevel:${newLevel}`);
|
28070
28896
|
this.levelController.manualLevel = newLevel;
|
28071
28897
|
this.streamController.immediateLevelSwitch();
|
28072
28898
|
}
|
@@ -28085,7 +28911,7 @@ class Hls {
|
|
28085
28911
|
* @param newLevel - Pass -1 for automatic level selection
|
28086
28912
|
*/
|
28087
28913
|
set nextLevel(newLevel) {
|
28088
|
-
logger.log(`set nextLevel:${newLevel}`);
|
28914
|
+
this.logger.log(`set nextLevel:${newLevel}`);
|
28089
28915
|
this.levelController.manualLevel = newLevel;
|
28090
28916
|
this.streamController.nextLevelSwitch();
|
28091
28917
|
}
|
@@ -28104,7 +28930,7 @@ class Hls {
|
|
28104
28930
|
* @param newLevel - Pass -1 for automatic level selection
|
28105
28931
|
*/
|
28106
28932
|
set loadLevel(newLevel) {
|
28107
|
-
logger.log(`set loadLevel:${newLevel}`);
|
28933
|
+
this.logger.log(`set loadLevel:${newLevel}`);
|
28108
28934
|
this.levelController.manualLevel = newLevel;
|
28109
28935
|
}
|
28110
28936
|
|
@@ -28135,7 +28961,7 @@ class Hls {
|
|
28135
28961
|
* Sets "first-level", see getter.
|
28136
28962
|
*/
|
28137
28963
|
set firstLevel(newLevel) {
|
28138
|
-
logger.log(`set firstLevel:${newLevel}`);
|
28964
|
+
this.logger.log(`set firstLevel:${newLevel}`);
|
28139
28965
|
this.levelController.firstLevel = newLevel;
|
28140
28966
|
}
|
28141
28967
|
|
@@ -28160,7 +28986,7 @@ class Hls {
|
|
28160
28986
|
* (determined from download of first segment)
|
28161
28987
|
*/
|
28162
28988
|
set startLevel(newLevel) {
|
28163
|
-
logger.log(`set startLevel:${newLevel}`);
|
28989
|
+
this.logger.log(`set startLevel:${newLevel}`);
|
28164
28990
|
// if not in automatic start level detection, ensure startLevel is greater than minAutoLevel
|
28165
28991
|
if (newLevel !== -1) {
|
28166
28992
|
newLevel = Math.max(newLevel, this.minAutoLevel);
|
@@ -28235,7 +29061,7 @@ class Hls {
|
|
28235
29061
|
*/
|
28236
29062
|
set autoLevelCapping(newLevel) {
|
28237
29063
|
if (this._autoLevelCapping !== newLevel) {
|
28238
|
-
logger.log(`set autoLevelCapping:${newLevel}`);
|
29064
|
+
this.logger.log(`set autoLevelCapping:${newLevel}`);
|
28239
29065
|
this._autoLevelCapping = newLevel;
|
28240
29066
|
this.levelController.checkMaxAutoUpdated();
|
28241
29067
|
}
|