hls.js 1.5.4 → 1.5.5-0.canary.9977
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 +1930 -1095
- package/dist/hls.js.d.ts +63 -50
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +1609 -778
- 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 +1363 -542
- 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 +1635 -815
- 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 +63 -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.9977"}`);
|
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
|
-
|
11775
|
-
|
11776
|
-
|
11777
|
-
|
11778
|
-
|
11779
|
-
|
11780
|
-
|
11781
|
-
|
11782
|
-
|
11783
|
-
|
11784
|
-
|
11785
|
-
|
11786
|
-
|
11787
|
-
|
11788
|
-
|
11789
|
-
|
11790
|
-
|
11791
|
-
|
11792
|
-
|
11793
|
-
let unitType;
|
11794
|
-
let lastUnitStart = -1;
|
11795
|
-
let lastUnitType = 0;
|
11796
|
-
// logger.log('PES:' + Hex.hexDump(array));
|
11797
|
-
|
11798
|
-
if (state === -1) {
|
11799
|
-
// special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
|
11800
|
-
lastUnitStart = 0;
|
11801
|
-
// NALu type is value read from offset 0
|
11802
|
-
lastUnitType = array[0] & 0x1f;
|
11803
|
-
state = 0;
|
11804
|
-
i = 1;
|
11805
|
-
}
|
11806
|
-
while (i < len) {
|
11807
|
-
value = array[i++];
|
11808
|
-
// optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
|
11809
|
-
if (!state) {
|
11810
|
-
state = value ? 0 : 1;
|
11811
|
-
continue;
|
11812
|
-
}
|
11813
|
-
if (state === 1) {
|
11814
|
-
state = value ? 0 : 2;
|
11815
|
-
continue;
|
11816
|
-
}
|
11817
|
-
// here we have state either equal to 2 or 3
|
11818
|
-
if (!value) {
|
11819
|
-
state = 3;
|
11820
|
-
} else if (value === 1) {
|
11821
|
-
overflow = i - state - 1;
|
11822
|
-
if (lastUnitStart >= 0) {
|
11823
|
-
const unit = {
|
11824
|
-
data: array.subarray(lastUnitStart, overflow),
|
11825
|
-
type: lastUnitType
|
11826
|
-
};
|
11827
|
-
// logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
|
11828
|
-
units.push(unit);
|
11829
|
-
} else {
|
11830
|
-
// lastUnitStart is undefined => this is the first start code found in this PES packet
|
11831
|
-
// first check if start code delimiter is overlapping between 2 PES packets,
|
11832
|
-
// ie it started in last packet (lastState not zero)
|
11833
|
-
// and ended at the beginning of this PES packet (i <= 4 - lastState)
|
11834
|
-
const lastUnit = this.getLastNalUnit(track.samples);
|
11835
|
-
if (lastUnit) {
|
11836
|
-
if (lastState && i <= 4 - lastState) {
|
11837
|
-
// start delimiter overlapping between PES packets
|
11838
|
-
// strip start delimiter bytes from the end of last NAL unit
|
11839
|
-
// check if lastUnit had a state different from zero
|
11840
|
-
if (lastUnit.state) {
|
11841
|
-
// strip last bytes
|
11842
|
-
lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
|
12101
|
+
|
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,19 @@ 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
|
+
this.videoParser = new HevcVideoParser();
|
12897
|
+
break;
|
12898
|
+
}
|
12899
|
+
}
|
12900
|
+
if (this.videoParser !== null) {
|
12901
|
+
this.videoParser.parsePES(videoTrack, textTrack, pes, false, this._duration);
|
12902
|
+
}
|
12185
12903
|
}
|
12186
12904
|
videoData = {
|
12187
12905
|
data: [],
|
@@ -12348,8 +13066,20 @@ class TSDemuxer {
|
|
12348
13066
|
// try to parse last PES packets
|
12349
13067
|
let pes;
|
12350
13068
|
if (videoData && (pes = parsePES(videoData))) {
|
12351
|
-
this.videoParser
|
12352
|
-
|
13069
|
+
if (this.videoParser === null) {
|
13070
|
+
switch (videoTrack.segmentCodec) {
|
13071
|
+
case 'avc':
|
13072
|
+
this.videoParser = new AvcVideoParser();
|
13073
|
+
break;
|
13074
|
+
case 'hevc':
|
13075
|
+
this.videoParser = new HevcVideoParser();
|
13076
|
+
break;
|
13077
|
+
}
|
13078
|
+
}
|
13079
|
+
if (this.videoParser !== null) {
|
13080
|
+
this.videoParser.parsePES(videoTrack, textTrack, pes, true, this._duration);
|
13081
|
+
videoTrack.pesData = null;
|
13082
|
+
}
|
12353
13083
|
} else {
|
12354
13084
|
// either avcData null or PES truncated, keep it for next frag parsing
|
12355
13085
|
videoTrack.pesData = videoData;
|
@@ -12682,7 +13412,12 @@ function parsePMT(data, offset, typeSupported, isSampleAes) {
|
|
12682
13412
|
logger.warn('Unsupported EC-3 in M2TS found');
|
12683
13413
|
break;
|
12684
13414
|
case 0x24:
|
12685
|
-
|
13415
|
+
// ITU-T Rec. H.265 and ISO/IEC 23008-2 (HEVC)
|
13416
|
+
if (result.videoPid === -1) {
|
13417
|
+
result.videoPid = pid;
|
13418
|
+
result.segmentVideoCodec = 'hevc';
|
13419
|
+
logger.log('HEVC in M2TS found');
|
13420
|
+
}
|
12686
13421
|
break;
|
12687
13422
|
}
|
12688
13423
|
// move to the next table entry
|
@@ -12905,6 +13640,8 @@ class MP4 {
|
|
12905
13640
|
avc1: [],
|
12906
13641
|
// codingname
|
12907
13642
|
avcC: [],
|
13643
|
+
hvc1: [],
|
13644
|
+
hvcC: [],
|
12908
13645
|
btrt: [],
|
12909
13646
|
dinf: [],
|
12910
13647
|
dref: [],
|
@@ -13329,8 +14066,10 @@ class MP4 {
|
|
13329
14066
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.ac3(track));
|
13330
14067
|
}
|
13331
14068
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track));
|
13332
|
-
} else {
|
14069
|
+
} else if (track.segmentCodec === 'avc') {
|
13333
14070
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
|
14071
|
+
} else {
|
14072
|
+
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.hvc1(track));
|
13334
14073
|
}
|
13335
14074
|
}
|
13336
14075
|
static tkhd(track) {
|
@@ -13468,6 +14207,84 @@ class MP4 {
|
|
13468
14207
|
const result = appendUint8Array(MP4.FTYP, movie);
|
13469
14208
|
return result;
|
13470
14209
|
}
|
14210
|
+
static hvc1(track) {
|
14211
|
+
const ps = track.params;
|
14212
|
+
const units = [track.vps, track.sps, track.pps];
|
14213
|
+
const NALuLengthSize = 4;
|
14214
|
+
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]);
|
14215
|
+
|
14216
|
+
// compute hvcC size in bytes
|
14217
|
+
let length = config.length;
|
14218
|
+
for (let i = 0; i < units.length; i += 1) {
|
14219
|
+
length += 3;
|
14220
|
+
for (let j = 0; j < units[i].length; j += 1) {
|
14221
|
+
length += 2 + units[i][j].length;
|
14222
|
+
}
|
14223
|
+
}
|
14224
|
+
const hvcC = new Uint8Array(length);
|
14225
|
+
hvcC.set(config, 0);
|
14226
|
+
length = config.length;
|
14227
|
+
// append parameter set units: one vps, one or more sps and pps
|
14228
|
+
const iMax = units.length - 1;
|
14229
|
+
for (let i = 0; i < units.length; i += 1) {
|
14230
|
+
hvcC.set(new Uint8Array([32 + i | (i === iMax ? 128 : 0), 0x00, units[i].length]), length);
|
14231
|
+
length += 3;
|
14232
|
+
for (let j = 0; j < units[i].length; j += 1) {
|
14233
|
+
hvcC.set(new Uint8Array([units[i][j].length >> 8, units[i][j].length & 255]), length);
|
14234
|
+
length += 2;
|
14235
|
+
hvcC.set(units[i][j], length);
|
14236
|
+
length += units[i][j].length;
|
14237
|
+
}
|
14238
|
+
}
|
14239
|
+
const hvcc = MP4.box(MP4.types.hvcC, hvcC);
|
14240
|
+
const width = track.width;
|
14241
|
+
const height = track.height;
|
14242
|
+
const hSpacing = track.pixelRatio[0];
|
14243
|
+
const vSpacing = track.pixelRatio[1];
|
14244
|
+
return MP4.box(MP4.types.hvc1, new Uint8Array([0x00, 0x00, 0x00,
|
14245
|
+
// reserved
|
14246
|
+
0x00, 0x00, 0x00,
|
14247
|
+
// reserved
|
14248
|
+
0x00, 0x01,
|
14249
|
+
// data_reference_index
|
14250
|
+
0x00, 0x00,
|
14251
|
+
// pre_defined
|
14252
|
+
0x00, 0x00,
|
14253
|
+
// reserved
|
14254
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
14255
|
+
// pre_defined
|
14256
|
+
width >> 8 & 0xff, width & 0xff,
|
14257
|
+
// width
|
14258
|
+
height >> 8 & 0xff, height & 0xff,
|
14259
|
+
// height
|
14260
|
+
0x00, 0x48, 0x00, 0x00,
|
14261
|
+
// horizresolution
|
14262
|
+
0x00, 0x48, 0x00, 0x00,
|
14263
|
+
// vertresolution
|
14264
|
+
0x00, 0x00, 0x00, 0x00,
|
14265
|
+
// reserved
|
14266
|
+
0x00, 0x01,
|
14267
|
+
// frame_count
|
14268
|
+
0x12, 0x64, 0x61, 0x69, 0x6c,
|
14269
|
+
// dailymotion/hls.js
|
14270
|
+
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,
|
14271
|
+
// compressorname
|
14272
|
+
0x00, 0x18,
|
14273
|
+
// depth = 24
|
14274
|
+
0x11, 0x11]),
|
14275
|
+
// pre_defined = -1
|
14276
|
+
hvcc, MP4.box(MP4.types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80,
|
14277
|
+
// bufferSizeDB
|
14278
|
+
0x00, 0x2d, 0xc6, 0xc0,
|
14279
|
+
// maxBitrate
|
14280
|
+
0x00, 0x2d, 0xc6, 0xc0])),
|
14281
|
+
// avgBitrate
|
14282
|
+
MP4.box(MP4.types.pasp, new Uint8Array([hSpacing >> 24,
|
14283
|
+
// hSpacing
|
14284
|
+
hSpacing >> 16 & 0xff, hSpacing >> 8 & 0xff, hSpacing & 0xff, vSpacing >> 24,
|
14285
|
+
// vSpacing
|
14286
|
+
vSpacing >> 16 & 0xff, vSpacing >> 8 & 0xff, vSpacing & 0xff])));
|
14287
|
+
}
|
13471
14288
|
}
|
13472
14289
|
MP4.types = void 0;
|
13473
14290
|
MP4.HDLR_TYPES = void 0;
|
@@ -13849,9 +14666,9 @@ class MP4Remuxer {
|
|
13849
14666
|
const foundOverlap = delta < -1;
|
13850
14667
|
if (foundHole || foundOverlap) {
|
13851
14668
|
if (foundHole) {
|
13852
|
-
logger.warn(
|
14669
|
+
logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
|
13853
14670
|
} else {
|
13854
|
-
logger.warn(
|
14671
|
+
logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
|
13855
14672
|
}
|
13856
14673
|
if (!foundOverlap || nextAvcDts >= inputSamples[0].pts || chromeVersion) {
|
13857
14674
|
firstDTS = nextAvcDts;
|
@@ -13860,12 +14677,24 @@ class MP4Remuxer {
|
|
13860
14677
|
inputSamples[0].dts = firstDTS;
|
13861
14678
|
inputSamples[0].pts = firstPTS;
|
13862
14679
|
} else {
|
14680
|
+
let isPTSOrderRetained = true;
|
13863
14681
|
for (let i = 0; i < inputSamples.length; i++) {
|
13864
|
-
if (inputSamples[i].dts > firstPTS) {
|
14682
|
+
if (inputSamples[i].dts > firstPTS && isPTSOrderRetained) {
|
13865
14683
|
break;
|
13866
14684
|
}
|
14685
|
+
const prevPTS = inputSamples[i].pts;
|
13867
14686
|
inputSamples[i].dts -= delta;
|
13868
14687
|
inputSamples[i].pts -= delta;
|
14688
|
+
|
14689
|
+
// check to see if this sample's PTS order has changed
|
14690
|
+
// relative to the next one
|
14691
|
+
if (i < inputSamples.length - 1) {
|
14692
|
+
const nextSamplePTS = inputSamples[i + 1].pts;
|
14693
|
+
const currentSamplePTS = inputSamples[i].pts;
|
14694
|
+
const currentOrder = nextSamplePTS <= currentSamplePTS;
|
14695
|
+
const prevOrder = nextSamplePTS <= prevPTS;
|
14696
|
+
isPTSOrderRetained = currentOrder == prevOrder;
|
14697
|
+
}
|
13869
14698
|
}
|
13870
14699
|
}
|
13871
14700
|
logger.log(`Video: Initial PTS/DTS adjusted: ${toMsFromMpegTsClock(firstPTS, true)}/${toMsFromMpegTsClock(firstDTS, true)}, delta: ${toMsFromMpegTsClock(delta, true)} ms`);
|
@@ -14013,7 +14842,7 @@ class MP4Remuxer {
|
|
14013
14842
|
}
|
14014
14843
|
}
|
14015
14844
|
}
|
14016
|
-
// next AVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
|
14845
|
+
// next AVC/HEVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
|
14017
14846
|
mp4SampleDuration = stretchedLastFrame || !mp4SampleDuration ? averageSampleDuration : mp4SampleDuration;
|
14018
14847
|
this.nextAvcDts = nextAvcDts = lastDTS + mp4SampleDuration;
|
14019
14848
|
this.videoSampleDuration = mp4SampleDuration;
|
@@ -14146,7 +14975,7 @@ class MP4Remuxer {
|
|
14146
14975
|
logger.warn(`[mp4-remuxer]: Injecting ${missing} audio frame @ ${(nextPts / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`);
|
14147
14976
|
for (let j = 0; j < missing; j++) {
|
14148
14977
|
const newStamp = Math.max(nextPts, 0);
|
14149
|
-
let fillFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
|
14978
|
+
let fillFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
|
14150
14979
|
if (!fillFrame) {
|
14151
14980
|
logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.');
|
14152
14981
|
fillFrame = sample.unit.subarray();
|
@@ -14274,7 +15103,7 @@ class MP4Remuxer {
|
|
14274
15103
|
// samples count of this segment's duration
|
14275
15104
|
const nbSamples = Math.ceil((endDTS - startDTS) / frameDuration);
|
14276
15105
|
// silent frame
|
14277
|
-
const silentFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
|
15106
|
+
const silentFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
|
14278
15107
|
logger.warn('[mp4-remuxer]: remux empty Audio');
|
14279
15108
|
// Can't remux if we can't generate a silent frame...
|
14280
15109
|
if (!silentFrame) {
|
@@ -14668,13 +15497,15 @@ class Transmuxer {
|
|
14668
15497
|
initSegmentData
|
14669
15498
|
} = transmuxConfig;
|
14670
15499
|
const keyData = getEncryptionType(uintData, decryptdata);
|
14671
|
-
if (keyData && keyData.method
|
15500
|
+
if (keyData && isFullSegmentEncryption(keyData.method)) {
|
14672
15501
|
const decrypter = this.getDecrypter();
|
15502
|
+
const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
|
15503
|
+
|
14673
15504
|
// Software decryption is synchronous; webCrypto is not
|
14674
15505
|
if (decrypter.isSync()) {
|
14675
15506
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
14676
15507
|
// data is handled in the flush() call
|
14677
|
-
let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer);
|
15508
|
+
let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode);
|
14678
15509
|
// For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
|
14679
15510
|
const loadingParts = chunkMeta.part > -1;
|
14680
15511
|
if (loadingParts) {
|
@@ -14686,7 +15517,7 @@ class Transmuxer {
|
|
14686
15517
|
}
|
14687
15518
|
uintData = new Uint8Array(decryptedData);
|
14688
15519
|
} else {
|
14689
|
-
this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer).then(decryptedData => {
|
15520
|
+
this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode).then(decryptedData => {
|
14690
15521
|
// Calling push here is important; if flush() is called while this is still resolving, this ensures that
|
14691
15522
|
// the decrypted data has been transmuxed
|
14692
15523
|
const result = this.push(decryptedData, null, chunkMeta);
|
@@ -15340,14 +16171,7 @@ class TransmuxerInterface {
|
|
15340
16171
|
this.observer = new EventEmitter();
|
15341
16172
|
this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
|
15342
16173
|
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
|
-
};
|
16174
|
+
const m2tsTypeSupported = getM2TSSupportedAudioTypes(config.preferManagedMediaSource);
|
15351
16175
|
|
15352
16176
|
// navigator.vendor is not always available in Web Worker
|
15353
16177
|
// refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
|
@@ -15635,7 +16459,7 @@ const TICK_INTERVAL$2 = 100; // how often to tick in ms
|
|
15635
16459
|
|
15636
16460
|
class AudioStreamController extends BaseStreamController {
|
15637
16461
|
constructor(hls, fragmentTracker, keyLoader) {
|
15638
|
-
super(hls, fragmentTracker, keyLoader, '
|
16462
|
+
super(hls, fragmentTracker, keyLoader, 'audio-stream-controller', PlaylistLevelType.AUDIO);
|
15639
16463
|
this.videoBuffer = null;
|
15640
16464
|
this.videoTrackCC = -1;
|
15641
16465
|
this.waitingVideoCC = -1;
|
@@ -15647,27 +16471,24 @@ class AudioStreamController extends BaseStreamController {
|
|
15647
16471
|
this.flushing = false;
|
15648
16472
|
this.bufferFlushed = false;
|
15649
16473
|
this.cachedTrackLoadedData = null;
|
15650
|
-
this.
|
16474
|
+
this.registerListeners();
|
15651
16475
|
}
|
15652
16476
|
onHandlerDestroying() {
|
15653
|
-
this.
|
16477
|
+
this.unregisterListeners();
|
15654
16478
|
super.onHandlerDestroying();
|
15655
16479
|
this.mainDetails = null;
|
15656
16480
|
this.bufferedTrack = null;
|
15657
16481
|
this.switchingTrack = null;
|
15658
16482
|
}
|
15659
|
-
|
16483
|
+
registerListeners() {
|
16484
|
+
super.registerListeners();
|
15660
16485
|
const {
|
15661
16486
|
hls
|
15662
16487
|
} = 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
16488
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
15667
16489
|
hls.on(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
|
15668
16490
|
hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
15669
16491
|
hls.on(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
|
15670
|
-
hls.on(Events.ERROR, this.onError, this);
|
15671
16492
|
hls.on(Events.BUFFER_RESET, this.onBufferReset, this);
|
15672
16493
|
hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
15673
16494
|
hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
@@ -15675,18 +16496,18 @@ class AudioStreamController extends BaseStreamController {
|
|
15675
16496
|
hls.on(Events.INIT_PTS_FOUND, this.onInitPtsFound, this);
|
15676
16497
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
15677
16498
|
}
|
15678
|
-
|
16499
|
+
unregisterListeners() {
|
15679
16500
|
const {
|
15680
16501
|
hls
|
15681
16502
|
} = this;
|
15682
|
-
hls
|
15683
|
-
|
15684
|
-
|
16503
|
+
if (!hls) {
|
16504
|
+
return;
|
16505
|
+
}
|
16506
|
+
super.unregisterListeners();
|
15685
16507
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
15686
16508
|
hls.off(Events.AUDIO_TRACKS_UPDATED, this.onAudioTracksUpdated, this);
|
15687
16509
|
hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
15688
16510
|
hls.off(Events.AUDIO_TRACK_LOADED, this.onAudioTrackLoaded, this);
|
15689
|
-
hls.off(Events.ERROR, this.onError, this);
|
15690
16511
|
hls.off(Events.BUFFER_RESET, this.onBufferReset, this);
|
15691
16512
|
hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
15692
16513
|
hls.off(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
@@ -15855,12 +16676,13 @@ class AudioStreamController extends BaseStreamController {
|
|
15855
16676
|
} = this;
|
15856
16677
|
const config = hls.config;
|
15857
16678
|
|
15858
|
-
// 1. if
|
16679
|
+
// 1. if buffering is suspended
|
16680
|
+
// 2. if video not attached AND
|
15859
16681
|
// start fragment already requested OR start frag prefetch not enabled
|
15860
|
-
//
|
16682
|
+
// 3. if tracks or track not loaded and selected
|
15861
16683
|
// then exit loop
|
15862
16684
|
// => 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])) {
|
16685
|
+
if (!this.buffering || !media && (this.startFragRequested || !config.startFragPrefetch) || !(levels != null && levels[trackId])) {
|
15864
16686
|
return;
|
15865
16687
|
}
|
15866
16688
|
const levelInfo = levels[trackId];
|
@@ -16418,7 +17240,7 @@ class AudioStreamController extends BaseStreamController {
|
|
16418
17240
|
|
16419
17241
|
class AudioTrackController extends BasePlaylistController {
|
16420
17242
|
constructor(hls) {
|
16421
|
-
super(hls, '
|
17243
|
+
super(hls, 'audio-track-controller');
|
16422
17244
|
this.tracks = [];
|
16423
17245
|
this.groupIds = null;
|
16424
17246
|
this.tracksInGroup = [];
|
@@ -16737,26 +17559,23 @@ const TICK_INTERVAL$1 = 500; // how often to tick in ms
|
|
16737
17559
|
|
16738
17560
|
class SubtitleStreamController extends BaseStreamController {
|
16739
17561
|
constructor(hls, fragmentTracker, keyLoader) {
|
16740
|
-
super(hls, fragmentTracker, keyLoader, '
|
17562
|
+
super(hls, fragmentTracker, keyLoader, 'subtitle-stream-controller', PlaylistLevelType.SUBTITLE);
|
16741
17563
|
this.currentTrackId = -1;
|
16742
17564
|
this.tracksBuffered = [];
|
16743
17565
|
this.mainDetails = null;
|
16744
|
-
this.
|
17566
|
+
this.registerListeners();
|
16745
17567
|
}
|
16746
17568
|
onHandlerDestroying() {
|
16747
|
-
this.
|
17569
|
+
this.unregisterListeners();
|
16748
17570
|
super.onHandlerDestroying();
|
16749
17571
|
this.mainDetails = null;
|
16750
17572
|
}
|
16751
|
-
|
17573
|
+
registerListeners() {
|
17574
|
+
super.registerListeners();
|
16752
17575
|
const {
|
16753
17576
|
hls
|
16754
17577
|
} = 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
17578
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
16759
|
-
hls.on(Events.ERROR, this.onError, this);
|
16760
17579
|
hls.on(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
|
16761
17580
|
hls.on(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
|
16762
17581
|
hls.on(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
|
@@ -16764,15 +17583,12 @@ class SubtitleStreamController extends BaseStreamController {
|
|
16764
17583
|
hls.on(Events.BUFFER_FLUSHING, this.onBufferFlushing, this);
|
16765
17584
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
16766
17585
|
}
|
16767
|
-
|
17586
|
+
unregisterListeners() {
|
17587
|
+
super.unregisterListeners();
|
16768
17588
|
const {
|
16769
17589
|
hls
|
16770
17590
|
} = 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
17591
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
16775
|
-
hls.off(Events.ERROR, this.onError, this);
|
16776
17592
|
hls.off(Events.SUBTITLE_TRACKS_UPDATED, this.onSubtitleTracksUpdated, this);
|
16777
17593
|
hls.off(Events.SUBTITLE_TRACK_SWITCH, this.onSubtitleTrackSwitch, this);
|
16778
17594
|
hls.off(Events.SUBTITLE_TRACK_LOADED, this.onSubtitleTrackLoaded, this);
|
@@ -16999,10 +17815,10 @@ class SubtitleStreamController extends BaseStreamController {
|
|
16999
17815
|
return;
|
17000
17816
|
}
|
17001
17817
|
// check to see if the payload needs to be decrypted
|
17002
|
-
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method
|
17818
|
+
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
|
17003
17819
|
const startTime = performance.now();
|
17004
17820
|
// decrypt the subtitles
|
17005
|
-
this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
|
17821
|
+
this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch(err => {
|
17006
17822
|
hls.trigger(Events.ERROR, {
|
17007
17823
|
type: ErrorTypes.MEDIA_ERROR,
|
17008
17824
|
details: ErrorDetails.FRAG_DECRYPT_ERROR,
|
@@ -17136,7 +17952,7 @@ class BufferableInstance {
|
|
17136
17952
|
|
17137
17953
|
class SubtitleTrackController extends BasePlaylistController {
|
17138
17954
|
constructor(hls) {
|
17139
|
-
super(hls, '
|
17955
|
+
super(hls, 'subtitle-track-controller');
|
17140
17956
|
this.media = null;
|
17141
17957
|
this.tracks = [];
|
17142
17958
|
this.groupIds = null;
|
@@ -17145,10 +17961,10 @@ class SubtitleTrackController extends BasePlaylistController {
|
|
17145
17961
|
this.currentTrack = null;
|
17146
17962
|
this.selectDefaultTrack = true;
|
17147
17963
|
this.queuedDefaultTrack = -1;
|
17148
|
-
this.asyncPollTrackChange = () => this.pollTrackChange(0);
|
17149
17964
|
this.useTextTrackPolling = false;
|
17150
17965
|
this.subtitlePollingInterval = -1;
|
17151
17966
|
this._subtitleDisplay = true;
|
17967
|
+
this.asyncPollTrackChange = () => this.pollTrackChange(0);
|
17152
17968
|
this.onTextTracksChanged = () => {
|
17153
17969
|
if (!this.useTextTrackPolling) {
|
17154
17970
|
self.clearInterval(this.subtitlePollingInterval);
|
@@ -17182,6 +17998,7 @@ class SubtitleTrackController extends BasePlaylistController {
|
|
17182
17998
|
this.tracks.length = 0;
|
17183
17999
|
this.tracksInGroup.length = 0;
|
17184
18000
|
this.currentTrack = null;
|
18001
|
+
// @ts-ignore
|
17185
18002
|
this.onTextTracksChanged = this.asyncPollTrackChange = null;
|
17186
18003
|
super.destroy();
|
17187
18004
|
}
|
@@ -17642,8 +18459,9 @@ class BufferOperationQueue {
|
|
17642
18459
|
}
|
17643
18460
|
|
17644
18461
|
const VIDEO_CODEC_PROFILE_REPLACE = /(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\.[^.,]+)+/;
|
17645
|
-
class BufferController {
|
18462
|
+
class BufferController extends Logger {
|
17646
18463
|
constructor(hls) {
|
18464
|
+
super('buffer-controller', hls.logger);
|
17647
18465
|
// The level details used to determine duration, target-duration and live
|
17648
18466
|
this.details = null;
|
17649
18467
|
// cache the self generated object url to detect hijack of video tag
|
@@ -17673,9 +18491,6 @@ class BufferController {
|
|
17673
18491
|
this.tracks = {};
|
17674
18492
|
this.pendingTracks = {};
|
17675
18493
|
this.sourceBuffer = void 0;
|
17676
|
-
this.log = void 0;
|
17677
|
-
this.warn = void 0;
|
17678
|
-
this.error = void 0;
|
17679
18494
|
this._onEndStreaming = event => {
|
17680
18495
|
if (!this.hls) {
|
17681
18496
|
return;
|
@@ -17721,15 +18536,11 @@ class BufferController {
|
|
17721
18536
|
_objectUrl
|
17722
18537
|
} = this;
|
17723
18538
|
if (mediaSrc !== _objectUrl) {
|
17724
|
-
|
18539
|
+
this.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
|
17725
18540
|
}
|
17726
18541
|
};
|
17727
18542
|
this.hls = hls;
|
17728
|
-
const logPrefix = '[buffer-controller]';
|
17729
18543
|
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
18544
|
this._initSourceBuffer();
|
17734
18545
|
this.registerListeners();
|
17735
18546
|
}
|
@@ -17742,6 +18553,12 @@ class BufferController {
|
|
17742
18553
|
this.lastMpegAudioChunk = null;
|
17743
18554
|
// @ts-ignore
|
17744
18555
|
this.hls = null;
|
18556
|
+
// @ts-ignore
|
18557
|
+
this._onMediaSourceOpen = this._onMediaSourceClose = null;
|
18558
|
+
// @ts-ignore
|
18559
|
+
this._onMediaSourceEnded = null;
|
18560
|
+
// @ts-ignore
|
18561
|
+
this._onStartStreaming = this._onEndStreaming = null;
|
17745
18562
|
}
|
17746
18563
|
registerListeners() {
|
17747
18564
|
const {
|
@@ -17904,6 +18721,7 @@ class BufferController {
|
|
17904
18721
|
this.resetBuffer(type);
|
17905
18722
|
});
|
17906
18723
|
this._initSourceBuffer();
|
18724
|
+
this.hls.resumeBuffering();
|
17907
18725
|
}
|
17908
18726
|
resetBuffer(type) {
|
17909
18727
|
const sb = this.sourceBuffer[type];
|
@@ -21006,14 +21824,12 @@ class TimelineController {
|
|
21006
21824
|
this.cea608Parser1 = this.cea608Parser2 = undefined;
|
21007
21825
|
}
|
21008
21826
|
initCea608Parsers() {
|
21009
|
-
|
21010
|
-
|
21011
|
-
|
21012
|
-
|
21013
|
-
|
21014
|
-
|
21015
|
-
this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
|
21016
|
-
}
|
21827
|
+
const channel1 = new OutputFilter(this, 'textTrack1');
|
21828
|
+
const channel2 = new OutputFilter(this, 'textTrack2');
|
21829
|
+
const channel3 = new OutputFilter(this, 'textTrack3');
|
21830
|
+
const channel4 = new OutputFilter(this, 'textTrack4');
|
21831
|
+
this.cea608Parser1 = new Cea608Parser(1, channel1, channel2);
|
21832
|
+
this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
|
21017
21833
|
}
|
21018
21834
|
addCues(trackName, startTime, endTime, screen, cueRanges) {
|
21019
21835
|
// skip cues which overlap more than 50% with previously parsed time ranges
|
@@ -21251,7 +22067,7 @@ class TimelineController {
|
|
21251
22067
|
if (inUseTracks != null && inUseTracks.length) {
|
21252
22068
|
const unusedTextTracks = inUseTracks.filter(t => t !== null).map(t => t.label);
|
21253
22069
|
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.`);
|
22070
|
+
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
22071
|
}
|
21256
22072
|
}
|
21257
22073
|
} else if (this.tracks.length) {
|
@@ -21296,26 +22112,23 @@ class TimelineController {
|
|
21296
22112
|
return level == null ? void 0 : level.attrs['CLOSED-CAPTIONS'];
|
21297
22113
|
}
|
21298
22114
|
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
22115
|
// 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) {
|
22116
|
+
if (this.enabled && data.frag.type === PlaylistLevelType.MAIN) {
|
21312
22117
|
var _data$part$index, _data$part;
|
22118
|
+
const {
|
22119
|
+
cea608Parser1,
|
22120
|
+
cea608Parser2,
|
22121
|
+
lastSn
|
22122
|
+
} = this;
|
22123
|
+
if (!cea608Parser1 || !cea608Parser2) {
|
22124
|
+
return;
|
22125
|
+
}
|
21313
22126
|
const {
|
21314
22127
|
cc,
|
21315
22128
|
sn
|
21316
22129
|
} = data.frag;
|
21317
|
-
const partIndex = (_data$part$index =
|
21318
|
-
if (!(sn === lastSn + 1 || sn === lastSn && partIndex === lastPartIndex + 1 || cc === lastCc)) {
|
22130
|
+
const partIndex = (_data$part$index = (_data$part = data.part) == null ? void 0 : _data$part.index) != null ? _data$part$index : -1;
|
22131
|
+
if (!(sn === lastSn + 1 || sn === lastSn && partIndex === this.lastPartIndex + 1 || cc === this.lastCc)) {
|
21319
22132
|
cea608Parser1.reset();
|
21320
22133
|
cea608Parser2.reset();
|
21321
22134
|
}
|
@@ -21372,7 +22185,7 @@ class TimelineController {
|
|
21372
22185
|
frag: frag
|
21373
22186
|
});
|
21374
22187
|
}, error => {
|
21375
|
-
logger.log(`Failed to parse IMSC1: ${error}`);
|
22188
|
+
hls.logger.log(`Failed to parse IMSC1: ${error}`);
|
21376
22189
|
hls.trigger(Events.SUBTITLE_FRAG_PROCESSED, {
|
21377
22190
|
success: false,
|
21378
22191
|
frag: frag,
|
@@ -21413,7 +22226,7 @@ class TimelineController {
|
|
21413
22226
|
this._fallbackToIMSC1(frag, payload);
|
21414
22227
|
}
|
21415
22228
|
// Something went wrong while parsing. Trigger event with success false.
|
21416
|
-
logger.log(`Failed to parse VTT cue: ${error}`);
|
22229
|
+
hls.logger.log(`Failed to parse VTT cue: ${error}`);
|
21417
22230
|
if (missingInitPTS && maxAvCC > frag.cc) {
|
21418
22231
|
return;
|
21419
22232
|
}
|
@@ -21474,12 +22287,7 @@ class TimelineController {
|
|
21474
22287
|
this.captionsTracks = {};
|
21475
22288
|
}
|
21476
22289
|
onFragParsingUserdata(event, data) {
|
21477
|
-
this.
|
21478
|
-
const {
|
21479
|
-
cea608Parser1,
|
21480
|
-
cea608Parser2
|
21481
|
-
} = this;
|
21482
|
-
if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
|
22290
|
+
if (!this.enabled || !this.config.enableCEA708Captions) {
|
21483
22291
|
return;
|
21484
22292
|
}
|
21485
22293
|
const {
|
@@ -21494,9 +22302,12 @@ class TimelineController {
|
|
21494
22302
|
for (let i = 0; i < samples.length; i++) {
|
21495
22303
|
const ccBytes = samples[i].bytes;
|
21496
22304
|
if (ccBytes) {
|
22305
|
+
if (!this.cea608Parser1) {
|
22306
|
+
this.initCea608Parsers();
|
22307
|
+
}
|
21497
22308
|
const ccdatas = this.extractCea608Data(ccBytes);
|
21498
|
-
cea608Parser1.addData(samples[i].pts, ccdatas[0]);
|
21499
|
-
cea608Parser2.addData(samples[i].pts, ccdatas[1]);
|
22309
|
+
this.cea608Parser1.addData(samples[i].pts, ccdatas[0]);
|
22310
|
+
this.cea608Parser2.addData(samples[i].pts, ccdatas[1]);
|
21500
22311
|
}
|
21501
22312
|
}
|
21502
22313
|
}
|
@@ -21692,7 +22503,7 @@ class CapLevelController {
|
|
21692
22503
|
const hls = this.hls;
|
21693
22504
|
const maxLevel = this.getMaxLevel(levels.length - 1);
|
21694
22505
|
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}`);
|
22506
|
+
hls.logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
|
21696
22507
|
}
|
21697
22508
|
hls.autoLevelCapping = maxLevel;
|
21698
22509
|
if (hls.autoLevelCapping > this.autoLevelCapping && this.streamController) {
|
@@ -21870,10 +22681,10 @@ class FPSController {
|
|
21870
22681
|
totalDroppedFrames: droppedFrames
|
21871
22682
|
});
|
21872
22683
|
if (droppedFPS > 0) {
|
21873
|
-
// logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
|
22684
|
+
// hls.logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
|
21874
22685
|
if (currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
|
21875
22686
|
let currentLevel = hls.currentLevel;
|
21876
|
-
logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
|
22687
|
+
hls.logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
|
21877
22688
|
if (currentLevel > 0 && (hls.autoLevelCapping === -1 || hls.autoLevelCapping >= currentLevel)) {
|
21878
22689
|
currentLevel = currentLevel - 1;
|
21879
22690
|
hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, {
|
@@ -21905,7 +22716,6 @@ class FPSController {
|
|
21905
22716
|
}
|
21906
22717
|
}
|
21907
22718
|
|
21908
|
-
const LOGGER_PREFIX = '[eme]';
|
21909
22719
|
/**
|
21910
22720
|
* Controller to deal with encrypted media extensions (EME)
|
21911
22721
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/Encrypted_Media_Extensions_API
|
@@ -21913,8 +22723,9 @@ const LOGGER_PREFIX = '[eme]';
|
|
21913
22723
|
* @class
|
21914
22724
|
* @constructor
|
21915
22725
|
*/
|
21916
|
-
class EMEController {
|
22726
|
+
class EMEController extends Logger {
|
21917
22727
|
constructor(hls) {
|
22728
|
+
super('eme', hls.logger);
|
21918
22729
|
this.hls = void 0;
|
21919
22730
|
this.config = void 0;
|
21920
22731
|
this.media = null;
|
@@ -21924,12 +22735,100 @@ class EMEController {
|
|
21924
22735
|
this.mediaKeySessions = [];
|
21925
22736
|
this.keyIdToKeySessionPromise = {};
|
21926
22737
|
this.setMediaKeysQueue = EMEController.CDMCleanupPromise ? [EMEController.CDMCleanupPromise] : [];
|
21927
|
-
this.onMediaEncrypted =
|
21928
|
-
|
21929
|
-
|
21930
|
-
|
21931
|
-
|
21932
|
-
|
22738
|
+
this.onMediaEncrypted = event => {
|
22739
|
+
const {
|
22740
|
+
initDataType,
|
22741
|
+
initData
|
22742
|
+
} = event;
|
22743
|
+
this.debug(`"${event.type}" event: init data type: "${initDataType}"`);
|
22744
|
+
|
22745
|
+
// Ignore event when initData is null
|
22746
|
+
if (initData === null) {
|
22747
|
+
return;
|
22748
|
+
}
|
22749
|
+
let keyId;
|
22750
|
+
let keySystemDomain;
|
22751
|
+
if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) {
|
22752
|
+
// Match sinf keyId to playlist skd://keyId=
|
22753
|
+
const json = bin2str(new Uint8Array(initData));
|
22754
|
+
try {
|
22755
|
+
const sinf = base64Decode(JSON.parse(json).sinf);
|
22756
|
+
const tenc = parseSinf(new Uint8Array(sinf));
|
22757
|
+
if (!tenc) {
|
22758
|
+
return;
|
22759
|
+
}
|
22760
|
+
keyId = tenc.subarray(8, 24);
|
22761
|
+
keySystemDomain = KeySystems.FAIRPLAY;
|
22762
|
+
} catch (error) {
|
22763
|
+
this.warn('Failed to parse sinf "encrypted" event message initData');
|
22764
|
+
return;
|
22765
|
+
}
|
22766
|
+
} else {
|
22767
|
+
// Support clear-lead key-session creation (otherwise depend on playlist keys)
|
22768
|
+
const psshInfo = parsePssh(initData);
|
22769
|
+
if (psshInfo === null) {
|
22770
|
+
return;
|
22771
|
+
}
|
22772
|
+
if (psshInfo.version === 0 && psshInfo.systemId === KeySystemIds.WIDEVINE && psshInfo.data) {
|
22773
|
+
keyId = psshInfo.data.subarray(8, 24);
|
22774
|
+
}
|
22775
|
+
keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
|
22776
|
+
}
|
22777
|
+
if (!keySystemDomain || !keyId) {
|
22778
|
+
return;
|
22779
|
+
}
|
22780
|
+
const keyIdHex = Hex.hexDump(keyId);
|
22781
|
+
const {
|
22782
|
+
keyIdToKeySessionPromise,
|
22783
|
+
mediaKeySessions
|
22784
|
+
} = this;
|
22785
|
+
let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
|
22786
|
+
for (let i = 0; i < mediaKeySessions.length; i++) {
|
22787
|
+
// Match playlist key
|
22788
|
+
const keyContext = mediaKeySessions[i];
|
22789
|
+
const decryptdata = keyContext.decryptdata;
|
22790
|
+
if (decryptdata.pssh || !decryptdata.keyId) {
|
22791
|
+
continue;
|
22792
|
+
}
|
22793
|
+
const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
|
22794
|
+
if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
|
22795
|
+
keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
|
22796
|
+
delete keyIdToKeySessionPromise[oldKeyIdHex];
|
22797
|
+
decryptdata.pssh = new Uint8Array(initData);
|
22798
|
+
decryptdata.keyId = keyId;
|
22799
|
+
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
|
22800
|
+
return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
|
22801
|
+
});
|
22802
|
+
break;
|
22803
|
+
}
|
22804
|
+
}
|
22805
|
+
if (!keySessionContextPromise) {
|
22806
|
+
// Clear-lead key (not encountered in playlist)
|
22807
|
+
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
|
22808
|
+
keySystem,
|
22809
|
+
mediaKeys
|
22810
|
+
}) => {
|
22811
|
+
var _keySystemToKeySystem;
|
22812
|
+
this.throwIfDestroyed();
|
22813
|
+
const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
|
22814
|
+
decryptdata.pssh = new Uint8Array(initData);
|
22815
|
+
decryptdata.keyId = keyId;
|
22816
|
+
return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
|
22817
|
+
this.throwIfDestroyed();
|
22818
|
+
const keySessionContext = this.createMediaKeySessionContext({
|
22819
|
+
decryptdata,
|
22820
|
+
keySystem,
|
22821
|
+
mediaKeys
|
22822
|
+
});
|
22823
|
+
return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
|
22824
|
+
});
|
22825
|
+
});
|
22826
|
+
}
|
22827
|
+
keySessionContextPromise.catch(error => this.handleError(error));
|
22828
|
+
};
|
22829
|
+
this.onWaitingForKey = event => {
|
22830
|
+
this.log(`"${event.type}" event`);
|
22831
|
+
};
|
21933
22832
|
this.hls = hls;
|
21934
22833
|
this.config = hls.config;
|
21935
22834
|
this.registerListeners();
|
@@ -21943,9 +22842,9 @@ class EMEController {
|
|
21943
22842
|
config.licenseXhrSetup = config.licenseResponseCallback = undefined;
|
21944
22843
|
config.drmSystems = config.drmSystemOptions = {};
|
21945
22844
|
// @ts-ignore
|
21946
|
-
this.hls = this.
|
22845
|
+
this.hls = this.config = this.keyIdToKeySessionPromise = null;
|
21947
22846
|
// @ts-ignore
|
21948
|
-
this.
|
22847
|
+
this.onMediaEncrypted = this.onWaitingForKey = null;
|
21949
22848
|
}
|
21950
22849
|
registerListeners() {
|
21951
22850
|
this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
@@ -22209,100 +23108,6 @@ class EMEController {
|
|
22209
23108
|
}
|
22210
23109
|
return this.attemptKeySystemAccess(keySystemsToAttempt);
|
22211
23110
|
}
|
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
23111
|
attemptSetMediaKeys(keySystem, mediaKeys) {
|
22307
23112
|
const queue = this.setMediaKeysQueue.slice();
|
22308
23113
|
this.log(`Setting media-keys for "${keySystem}"`);
|
@@ -22895,20 +23700,6 @@ class SfItem {
|
|
22895
23700
|
}
|
22896
23701
|
}
|
22897
23702
|
|
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
23703
|
const DICT = 'Dict';
|
22913
23704
|
|
22914
23705
|
function format(value) {
|
@@ -22932,29 +23723,27 @@ function throwError(action, src, type, cause) {
|
|
22932
23723
|
});
|
22933
23724
|
}
|
22934
23725
|
|
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;
|
23726
|
+
function serializeError(src, type, cause) {
|
23727
|
+
return throwError('serialize', src, type, cause);
|
22947
23728
|
}
|
22948
23729
|
|
22949
|
-
|
22950
|
-
|
22951
|
-
|
23730
|
+
/**
|
23731
|
+
* A class to represent structured field tokens when `Symbol` is not available.
|
23732
|
+
*
|
23733
|
+
* @group Structured Field
|
23734
|
+
*
|
23735
|
+
* @beta
|
23736
|
+
*/
|
23737
|
+
class SfToken {
|
23738
|
+
constructor(description) {
|
23739
|
+
this.description = void 0;
|
23740
|
+
this.description = description;
|
23741
|
+
}
|
23742
|
+
}
|
22952
23743
|
|
22953
|
-
const
|
23744
|
+
const BARE_ITEM = 'Bare Item';
|
22954
23745
|
|
22955
|
-
|
22956
|
-
return throwError('serialize', src, type, cause);
|
22957
|
-
}
|
23746
|
+
const BOOLEAN = 'Boolean';
|
22958
23747
|
|
22959
23748
|
// 4.1.9. Serializing a Boolean
|
22960
23749
|
//
|
@@ -22993,6 +23782,8 @@ function base64encode(binary) {
|
|
22993
23782
|
return btoa(String.fromCharCode(...binary));
|
22994
23783
|
}
|
22995
23784
|
|
23785
|
+
const BYTES = 'Byte Sequence';
|
23786
|
+
|
22996
23787
|
// 4.1.8. Serializing a Byte Sequence
|
22997
23788
|
//
|
22998
23789
|
// Given a Byte Sequence as input_bytes, return an ASCII string suitable
|
@@ -23024,6 +23815,12 @@ function serializeByteSequence(value) {
|
|
23024
23815
|
return `:${base64encode(value)}:`;
|
23025
23816
|
}
|
23026
23817
|
|
23818
|
+
const INTEGER = 'Integer';
|
23819
|
+
|
23820
|
+
function isInvalidInt(value) {
|
23821
|
+
return value < -999999999999999 || 999999999999999 < value;
|
23822
|
+
}
|
23823
|
+
|
23027
23824
|
// 4.1.4. Serializing an Integer
|
23028
23825
|
//
|
23029
23826
|
// Given an Integer as input_integer, return an ASCII string suitable
|
@@ -23089,6 +23886,8 @@ function roundToEven(value, precision) {
|
|
23089
23886
|
}
|
23090
23887
|
}
|
23091
23888
|
|
23889
|
+
const DECIMAL = 'Decimal';
|
23890
|
+
|
23092
23891
|
// 4.1.5. Serializing a Decimal
|
23093
23892
|
//
|
23094
23893
|
// Given a decimal number as input_decimal, return an ASCII string
|
@@ -23134,6 +23933,8 @@ function serializeDecimal(value) {
|
|
23134
23933
|
|
23135
23934
|
const STRING = 'String';
|
23136
23935
|
|
23936
|
+
const STRING_REGEX = /[\x00-\x1f\x7f]+/; // eslint-disable-line no-control-regex
|
23937
|
+
|
23137
23938
|
// 4.1.6. Serializing a String
|
23138
23939
|
//
|
23139
23940
|
// Given a String as input_string, return an ASCII string suitable for
|
@@ -23169,6 +23970,8 @@ function symbolToStr(symbol) {
|
|
23169
23970
|
return symbol.description || symbol.toString().slice(7, -1);
|
23170
23971
|
}
|
23171
23972
|
|
23973
|
+
const TOKEN = 'Token';
|
23974
|
+
|
23172
23975
|
function serializeToken(token) {
|
23173
23976
|
const value = symbolToStr(token);
|
23174
23977
|
if (/^([a-zA-Z*])([!#$%&'*+\-.^_`|~\w:/]*)$/.test(value) === false) {
|
@@ -23236,6 +24039,8 @@ function serializeBareItem(value) {
|
|
23236
24039
|
}
|
23237
24040
|
}
|
23238
24041
|
|
24042
|
+
const KEY = 'Key';
|
24043
|
+
|
23239
24044
|
// 4.1.1.3. Serializing a Key
|
23240
24045
|
//
|
23241
24046
|
// Given a key as input_key, return an ASCII string suitable for use in
|
@@ -23477,36 +24282,6 @@ function urlToRelativePath(url, base) {
|
|
23477
24282
|
return toPath.join('/');
|
23478
24283
|
}
|
23479
24284
|
|
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
24285
|
const toRounded = value => Math.round(value);
|
23511
24286
|
const toUrlSafe = (value, options) => {
|
23512
24287
|
if (options != null && options.baseUrl) {
|
@@ -23732,6 +24507,36 @@ function appendCmcdQuery(url, cmcd, options) {
|
|
23732
24507
|
return `${url}${separator}${query}`;
|
23733
24508
|
}
|
23734
24509
|
|
24510
|
+
/**
|
24511
|
+
* Generate a random v4 UUID
|
24512
|
+
*
|
24513
|
+
* @returns A random v4 UUID
|
24514
|
+
*
|
24515
|
+
* @group Utils
|
24516
|
+
*
|
24517
|
+
* @beta
|
24518
|
+
*/
|
24519
|
+
function uuid() {
|
24520
|
+
try {
|
24521
|
+
return crypto.randomUUID();
|
24522
|
+
} catch (error) {
|
24523
|
+
try {
|
24524
|
+
const url = URL.createObjectURL(new Blob());
|
24525
|
+
const uuid = url.toString();
|
24526
|
+
URL.revokeObjectURL(url);
|
24527
|
+
return uuid.slice(uuid.lastIndexOf('/') + 1);
|
24528
|
+
} catch (error) {
|
24529
|
+
let dt = new Date().getTime();
|
24530
|
+
const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
24531
|
+
const r = (dt + Math.random() * 16) % 16 | 0;
|
24532
|
+
dt = Math.floor(dt / 16);
|
24533
|
+
return (c == 'x' ? r : r & 0x3 | 0x8).toString(16);
|
24534
|
+
});
|
24535
|
+
return uuid;
|
24536
|
+
}
|
24537
|
+
}
|
24538
|
+
}
|
24539
|
+
|
23735
24540
|
/**
|
23736
24541
|
* Controller to deal with Common Media Client Data (CMCD)
|
23737
24542
|
* @see https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf
|
@@ -23795,6 +24600,12 @@ class CMCDController {
|
|
23795
24600
|
data.tb = this.getTopBandwidth(ot) / 1000;
|
23796
24601
|
data.bl = this.getBufferLength(ot);
|
23797
24602
|
}
|
24603
|
+
const next = this.getNextFrag(fragment);
|
24604
|
+
if (next) {
|
24605
|
+
if (next.url && next.url !== fragment.url) {
|
24606
|
+
data.nor = next.url;
|
24607
|
+
}
|
24608
|
+
}
|
23798
24609
|
this.apply(context, data);
|
23799
24610
|
} catch (error) {
|
23800
24611
|
logger.warn('Could not generate segment CMCD data.', error);
|
@@ -23887,7 +24698,7 @@ class CMCDController {
|
|
23887
24698
|
data.su = this.buffering;
|
23888
24699
|
}
|
23889
24700
|
|
23890
|
-
// TODO: Implement rtp, nrr,
|
24701
|
+
// TODO: Implement rtp, nrr, dl
|
23891
24702
|
|
23892
24703
|
const {
|
23893
24704
|
includeKeys
|
@@ -23898,15 +24709,28 @@ class CMCDController {
|
|
23898
24709
|
return acc;
|
23899
24710
|
}, {});
|
23900
24711
|
}
|
24712
|
+
const options = {
|
24713
|
+
baseUrl: context.url
|
24714
|
+
};
|
23901
24715
|
if (this.useHeaders) {
|
23902
24716
|
if (!context.headers) {
|
23903
24717
|
context.headers = {};
|
23904
24718
|
}
|
23905
|
-
appendCmcdHeaders(context.headers, data);
|
24719
|
+
appendCmcdHeaders(context.headers, data, options);
|
23906
24720
|
} else {
|
23907
|
-
context.url = appendCmcdQuery(context.url, data);
|
24721
|
+
context.url = appendCmcdQuery(context.url, data, options);
|
24722
|
+
}
|
24723
|
+
}
|
24724
|
+
getNextFrag(fragment) {
|
24725
|
+
var _this$hls$levels$frag;
|
24726
|
+
const levelDetails = (_this$hls$levels$frag = this.hls.levels[fragment.level]) == null ? void 0 : _this$hls$levels$frag.details;
|
24727
|
+
if (levelDetails) {
|
24728
|
+
const index = fragment.sn - levelDetails.startSN;
|
24729
|
+
return levelDetails.fragments[index + 1];
|
23908
24730
|
}
|
24731
|
+
return undefined;
|
23909
24732
|
}
|
24733
|
+
|
23910
24734
|
/**
|
23911
24735
|
* The CMCD object type.
|
23912
24736
|
*/
|
@@ -24035,10 +24859,10 @@ class CMCDController {
|
|
24035
24859
|
}
|
24036
24860
|
|
24037
24861
|
const PATHWAY_PENALTY_DURATION_MS = 300000;
|
24038
|
-
class ContentSteeringController {
|
24862
|
+
class ContentSteeringController extends Logger {
|
24039
24863
|
constructor(hls) {
|
24864
|
+
super('content-steering', hls.logger);
|
24040
24865
|
this.hls = void 0;
|
24041
|
-
this.log = void 0;
|
24042
24866
|
this.loader = null;
|
24043
24867
|
this.uri = null;
|
24044
24868
|
this.pathwayId = '.';
|
@@ -24053,7 +24877,6 @@ class ContentSteeringController {
|
|
24053
24877
|
this.subtitleTracks = null;
|
24054
24878
|
this.penalizedPathways = {};
|
24055
24879
|
this.hls = hls;
|
24056
|
-
this.log = logger.log.bind(logger, `[content-steering]:`);
|
24057
24880
|
this.registerListeners();
|
24058
24881
|
}
|
24059
24882
|
registerListeners() {
|
@@ -24177,7 +25000,7 @@ class ContentSteeringController {
|
|
24177
25000
|
errorAction.resolved = this.pathwayId !== errorPathway;
|
24178
25001
|
}
|
24179
25002
|
if (!errorAction.resolved) {
|
24180
|
-
|
25003
|
+
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
25004
|
}
|
24182
25005
|
}
|
24183
25006
|
}
|
@@ -24348,7 +25171,7 @@ class ContentSteeringController {
|
|
24348
25171
|
onSuccess: (response, stats, context, networkDetails) => {
|
24349
25172
|
this.log(`Loaded steering manifest: "${url}"`);
|
24350
25173
|
const steeringData = response.data;
|
24351
|
-
if (steeringData.VERSION !== 1) {
|
25174
|
+
if ((steeringData == null ? void 0 : steeringData.VERSION) !== 1) {
|
24352
25175
|
this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
|
24353
25176
|
return;
|
24354
25177
|
}
|
@@ -25318,7 +26141,7 @@ function timelineConfig() {
|
|
25318
26141
|
/**
|
25319
26142
|
* @ignore
|
25320
26143
|
*/
|
25321
|
-
function mergeConfig(defaultConfig, userConfig) {
|
26144
|
+
function mergeConfig(defaultConfig, userConfig, logger) {
|
25322
26145
|
if ((userConfig.liveSyncDurationCount || userConfig.liveMaxLatencyDurationCount) && (userConfig.liveSyncDuration || userConfig.liveMaxLatencyDuration)) {
|
25323
26146
|
throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration");
|
25324
26147
|
}
|
@@ -25388,7 +26211,7 @@ function deepCpy(obj) {
|
|
25388
26211
|
/**
|
25389
26212
|
* @ignore
|
25390
26213
|
*/
|
25391
|
-
function enableStreamingMode(config) {
|
26214
|
+
function enableStreamingMode(config, logger) {
|
25392
26215
|
const currentLoader = config.loader;
|
25393
26216
|
if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
|
25394
26217
|
// If a developer has configured their own loader, respect that choice
|
@@ -25405,10 +26228,9 @@ function enableStreamingMode(config) {
|
|
25405
26228
|
}
|
25406
26229
|
}
|
25407
26230
|
|
25408
|
-
let chromeOrFirefox;
|
25409
26231
|
class LevelController extends BasePlaylistController {
|
25410
26232
|
constructor(hls, contentSteeringController) {
|
25411
|
-
super(hls, '
|
26233
|
+
super(hls, 'level-controller');
|
25412
26234
|
this._levels = [];
|
25413
26235
|
this._firstLevel = -1;
|
25414
26236
|
this._maxAutoLevel = -1;
|
@@ -25479,23 +26301,15 @@ class LevelController extends BasePlaylistController {
|
|
25479
26301
|
let videoCodecFound = false;
|
25480
26302
|
let audioCodecFound = false;
|
25481
26303
|
data.levels.forEach(levelParsed => {
|
25482
|
-
var
|
26304
|
+
var _videoCodec;
|
25483
26305
|
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
26306
|
let {
|
25488
26307
|
audioCodec,
|
25489
26308
|
videoCodec
|
25490
26309
|
} = 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
26310
|
if (audioCodec) {
|
25498
|
-
|
26311
|
+
// Returns empty and set to undefined for 'mp4a.40.34' with fallback to 'audio/mpeg' SourceBuffer
|
26312
|
+
levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource) || undefined;
|
25499
26313
|
}
|
25500
26314
|
if (((_videoCodec = videoCodec) == null ? void 0 : _videoCodec.indexOf('avc1')) === 0) {
|
25501
26315
|
videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
|
@@ -26081,6 +26895,8 @@ class KeyLoader {
|
|
26081
26895
|
}
|
26082
26896
|
return this.loadKeyEME(keyInfo, frag);
|
26083
26897
|
case 'AES-128':
|
26898
|
+
case 'AES-256':
|
26899
|
+
case 'AES-256-CTR':
|
26084
26900
|
return this.loadKeyHTTP(keyInfo, frag);
|
26085
26901
|
default:
|
26086
26902
|
return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`)));
|
@@ -26218,8 +27034,9 @@ const STALL_MINIMUM_DURATION_MS = 250;
|
|
26218
27034
|
const MAX_START_GAP_JUMP = 2.0;
|
26219
27035
|
const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
|
26220
27036
|
const SKIP_BUFFER_RANGE_START = 0.05;
|
26221
|
-
class GapController {
|
27037
|
+
class GapController extends Logger {
|
26222
27038
|
constructor(config, media, fragmentTracker, hls) {
|
27039
|
+
super('gap-controller', hls.logger);
|
26223
27040
|
this.config = void 0;
|
26224
27041
|
this.media = null;
|
26225
27042
|
this.fragmentTracker = void 0;
|
@@ -26229,6 +27046,7 @@ class GapController {
|
|
26229
27046
|
this.stalled = null;
|
26230
27047
|
this.moved = false;
|
26231
27048
|
this.seeking = false;
|
27049
|
+
this.ended = 0;
|
26232
27050
|
this.config = config;
|
26233
27051
|
this.media = media;
|
26234
27052
|
this.fragmentTracker = fragmentTracker;
|
@@ -26246,7 +27064,7 @@ class GapController {
|
|
26246
27064
|
*
|
26247
27065
|
* @param lastCurrentTime - Previously read playhead position
|
26248
27066
|
*/
|
26249
|
-
poll(lastCurrentTime, activeFrag) {
|
27067
|
+
poll(lastCurrentTime, activeFrag, levelDetails, state) {
|
26250
27068
|
const {
|
26251
27069
|
config,
|
26252
27070
|
media,
|
@@ -26265,6 +27083,7 @@ class GapController {
|
|
26265
27083
|
|
26266
27084
|
// The playhead is moving, no-op
|
26267
27085
|
if (currentTime !== lastCurrentTime) {
|
27086
|
+
this.ended = 0;
|
26268
27087
|
this.moved = true;
|
26269
27088
|
if (!seeking) {
|
26270
27089
|
this.nudgeRetry = 0;
|
@@ -26273,7 +27092,7 @@ class GapController {
|
|
26273
27092
|
// The playhead is now moving, but was previously stalled
|
26274
27093
|
if (this.stallReported) {
|
26275
27094
|
const _stalledDuration = self.performance.now() - stalled;
|
26276
|
-
|
27095
|
+
this.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
|
26277
27096
|
this.stallReported = false;
|
26278
27097
|
}
|
26279
27098
|
this.stalled = null;
|
@@ -26309,7 +27128,6 @@ class GapController {
|
|
26309
27128
|
// Skip start gaps if we haven't played, but the last poll detected the start of a stall
|
26310
27129
|
// The addition poll gives the browser a chance to jump the gap for us
|
26311
27130
|
if (!this.moved && this.stalled !== null) {
|
26312
|
-
var _level$details;
|
26313
27131
|
// There is no playable buffer (seeked, waiting for buffer)
|
26314
27132
|
const isBuffered = bufferInfo.len > 0;
|
26315
27133
|
if (!isBuffered && !nextStart) {
|
@@ -26321,9 +27139,8 @@ class GapController {
|
|
26321
27139
|
// When joining a live stream with audio tracks, account for live playlist window sliding by allowing
|
26322
27140
|
// a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
|
26323
27141
|
// 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;
|
27142
|
+
const isLive = !!(levelDetails != null && levelDetails.live);
|
27143
|
+
const maxStartGapJump = isLive ? levelDetails.targetduration * 2 : MAX_START_GAP_JUMP;
|
26327
27144
|
const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
|
26328
27145
|
if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
|
26329
27146
|
if (!media.paused) {
|
@@ -26341,6 +27158,17 @@ class GapController {
|
|
26341
27158
|
}
|
26342
27159
|
const stalledDuration = tnow - stalled;
|
26343
27160
|
if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) {
|
27161
|
+
// Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
|
27162
|
+
if (state === State.ENDED && !(levelDetails && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? void 0 : levelDetails.edge) || 0)) < 1) {
|
27163
|
+
if (stalledDuration < 1000 || this.ended) {
|
27164
|
+
return;
|
27165
|
+
}
|
27166
|
+
this.ended = currentTime;
|
27167
|
+
this.hls.trigger(Events.MEDIA_ENDED, {
|
27168
|
+
stalled: true
|
27169
|
+
});
|
27170
|
+
return;
|
27171
|
+
}
|
26344
27172
|
// Report stalling after trying to fix
|
26345
27173
|
this._reportStall(bufferInfo);
|
26346
27174
|
if (!this.media) {
|
@@ -26384,7 +27212,7 @@ class GapController {
|
|
26384
27212
|
// needs to cross some sort of threshold covering all source-buffers content
|
26385
27213
|
// to start playing properly.
|
26386
27214
|
if ((bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
|
26387
|
-
|
27215
|
+
this.warn('Trying to nudge playhead over buffer-hole');
|
26388
27216
|
// Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
|
26389
27217
|
// We only try to jump the hole if it's under the configured size
|
26390
27218
|
// Reset stalled so to rearm watchdog timer
|
@@ -26408,7 +27236,7 @@ class GapController {
|
|
26408
27236
|
// Report stalled error once
|
26409
27237
|
this.stallReported = true;
|
26410
27238
|
const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${JSON.stringify(bufferInfo)})`);
|
26411
|
-
|
27239
|
+
this.warn(error.message);
|
26412
27240
|
hls.trigger(Events.ERROR, {
|
26413
27241
|
type: ErrorTypes.MEDIA_ERROR,
|
26414
27242
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
@@ -26476,7 +27304,7 @@ class GapController {
|
|
26476
27304
|
}
|
26477
27305
|
}
|
26478
27306
|
const targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
|
26479
|
-
|
27307
|
+
this.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
|
26480
27308
|
this.moved = true;
|
26481
27309
|
this.stalled = null;
|
26482
27310
|
media.currentTime = targetTime;
|
@@ -26517,7 +27345,7 @@ class GapController {
|
|
26517
27345
|
const targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset;
|
26518
27346
|
// playback stalled in buffered area ... let's nudge currentTime to try to overcome this
|
26519
27347
|
const error = new Error(`Nudging 'currentTime' from ${currentTime} to ${targetTime}`);
|
26520
|
-
|
27348
|
+
this.warn(error.message);
|
26521
27349
|
media.currentTime = targetTime;
|
26522
27350
|
hls.trigger(Events.ERROR, {
|
26523
27351
|
type: ErrorTypes.MEDIA_ERROR,
|
@@ -26527,7 +27355,7 @@ class GapController {
|
|
26527
27355
|
});
|
26528
27356
|
} else {
|
26529
27357
|
const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`);
|
26530
|
-
|
27358
|
+
this.error(error.message);
|
26531
27359
|
hls.trigger(Events.ERROR, {
|
26532
27360
|
type: ErrorTypes.MEDIA_ERROR,
|
26533
27361
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
@@ -26542,7 +27370,7 @@ const TICK_INTERVAL = 100; // how often to tick in ms
|
|
26542
27370
|
|
26543
27371
|
class StreamController extends BaseStreamController {
|
26544
27372
|
constructor(hls, fragmentTracker, keyLoader) {
|
26545
|
-
super(hls, fragmentTracker, keyLoader, '
|
27373
|
+
super(hls, fragmentTracker, keyLoader, 'stream-controller', PlaylistLevelType.MAIN);
|
26546
27374
|
this.audioCodecSwap = false;
|
26547
27375
|
this.gapController = null;
|
26548
27376
|
this.level = -1;
|
@@ -26550,27 +27378,43 @@ class StreamController extends BaseStreamController {
|
|
26550
27378
|
this.altAudio = false;
|
26551
27379
|
this.audioOnly = false;
|
26552
27380
|
this.fragPlaying = null;
|
26553
|
-
this.onvplaying = null;
|
26554
|
-
this.onvseeked = null;
|
26555
27381
|
this.fragLastKbps = 0;
|
26556
27382
|
this.couldBacktrack = false;
|
26557
27383
|
this.backtrackFragment = null;
|
26558
27384
|
this.audioCodecSwitch = false;
|
26559
27385
|
this.videoBuffer = null;
|
26560
|
-
this.
|
27386
|
+
this.onMediaPlaying = () => {
|
27387
|
+
// tick to speed up FRAG_CHANGED triggering
|
27388
|
+
this.tick();
|
27389
|
+
};
|
27390
|
+
this.onMediaSeeked = () => {
|
27391
|
+
const media = this.media;
|
27392
|
+
const currentTime = media ? media.currentTime : null;
|
27393
|
+
if (isFiniteNumber(currentTime)) {
|
27394
|
+
this.log(`Media seeked to ${currentTime.toFixed(3)}`);
|
27395
|
+
}
|
27396
|
+
|
27397
|
+
// If seeked was issued before buffer was appended do not tick immediately
|
27398
|
+
const bufferInfo = this.getMainFwdBufferInfo();
|
27399
|
+
if (bufferInfo === null || bufferInfo.len === 0) {
|
27400
|
+
this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
|
27401
|
+
return;
|
27402
|
+
}
|
27403
|
+
|
27404
|
+
// tick to speed up FRAG_CHANGED triggering
|
27405
|
+
this.tick();
|
27406
|
+
};
|
27407
|
+
this.registerListeners();
|
26561
27408
|
}
|
26562
|
-
|
27409
|
+
registerListeners() {
|
27410
|
+
super.registerListeners();
|
26563
27411
|
const {
|
26564
27412
|
hls
|
26565
27413
|
} = 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
27414
|
hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
26570
27415
|
hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
|
26571
27416
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
26572
27417
|
hls.on(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
|
26573
|
-
hls.on(Events.ERROR, this.onError, this);
|
26574
27418
|
hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
26575
27419
|
hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
26576
27420
|
hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -26578,17 +27422,14 @@ class StreamController extends BaseStreamController {
|
|
26578
27422
|
hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
|
26579
27423
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
26580
27424
|
}
|
26581
|
-
|
27425
|
+
unregisterListeners() {
|
27426
|
+
super.unregisterListeners();
|
26582
27427
|
const {
|
26583
27428
|
hls
|
26584
27429
|
} = 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
27430
|
hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
26589
27431
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
26590
27432
|
hls.off(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
|
26591
|
-
hls.off(Events.ERROR, this.onError, this);
|
26592
27433
|
hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
26593
27434
|
hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
26594
27435
|
hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -26597,7 +27438,9 @@ class StreamController extends BaseStreamController {
|
|
26597
27438
|
hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
26598
27439
|
}
|
26599
27440
|
onHandlerDestroying() {
|
26600
|
-
|
27441
|
+
// @ts-ignore
|
27442
|
+
this.onMediaPlaying = this.onMediaSeeked = null;
|
27443
|
+
this.unregisterListeners();
|
26601
27444
|
super.onHandlerDestroying();
|
26602
27445
|
}
|
26603
27446
|
startLoad(startPosition) {
|
@@ -26717,7 +27560,7 @@ class StreamController extends BaseStreamController {
|
|
26717
27560
|
if (this.altAudio && this.audioOnly) {
|
26718
27561
|
return;
|
26719
27562
|
}
|
26720
|
-
if (!(levels != null && levels[level])) {
|
27563
|
+
if (!this.buffering || !(levels != null && levels[level])) {
|
26721
27564
|
return;
|
26722
27565
|
}
|
26723
27566
|
const levelInfo = levels[level];
|
@@ -26925,20 +27768,17 @@ class StreamController extends BaseStreamController {
|
|
26925
27768
|
onMediaAttached(event, data) {
|
26926
27769
|
super.onMediaAttached(event, data);
|
26927
27770
|
const media = data.media;
|
26928
|
-
|
26929
|
-
|
26930
|
-
media.addEventListener('playing', this.onvplaying);
|
26931
|
-
media.addEventListener('seeked', this.onvseeked);
|
27771
|
+
media.addEventListener('playing', this.onMediaPlaying);
|
27772
|
+
media.addEventListener('seeked', this.onMediaSeeked);
|
26932
27773
|
this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
|
26933
27774
|
}
|
26934
27775
|
onMediaDetaching() {
|
26935
27776
|
const {
|
26936
27777
|
media
|
26937
27778
|
} = this;
|
26938
|
-
if (media
|
26939
|
-
media.removeEventListener('playing', this.
|
26940
|
-
media.removeEventListener('seeked', this.
|
26941
|
-
this.onvplaying = this.onvseeked = null;
|
27779
|
+
if (media) {
|
27780
|
+
media.removeEventListener('playing', this.onMediaPlaying);
|
27781
|
+
media.removeEventListener('seeked', this.onMediaSeeked);
|
26942
27782
|
this.videoBuffer = null;
|
26943
27783
|
}
|
26944
27784
|
this.fragPlaying = null;
|
@@ -26948,27 +27788,6 @@ class StreamController extends BaseStreamController {
|
|
26948
27788
|
}
|
26949
27789
|
super.onMediaDetaching();
|
26950
27790
|
}
|
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
27791
|
onManifestLoading() {
|
26973
27792
|
// reset buffer on manifest loading
|
26974
27793
|
this.log('Trigger BUFFER_RESET');
|
@@ -27260,8 +28079,10 @@ class StreamController extends BaseStreamController {
|
|
27260
28079
|
}
|
27261
28080
|
if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) {
|
27262
28081
|
// Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
|
27263
|
-
const
|
27264
|
-
|
28082
|
+
const state = this.state;
|
28083
|
+
const activeFrag = state !== State.IDLE ? this.fragCurrent : null;
|
28084
|
+
const levelDetails = this.getLevelDetails();
|
28085
|
+
gapController.poll(this.lastCurrentTime, activeFrag, levelDetails, state);
|
27265
28086
|
}
|
27266
28087
|
this.lastCurrentTime = media.currentTime;
|
27267
28088
|
}
|
@@ -27699,7 +28520,7 @@ class Hls {
|
|
27699
28520
|
* Get the video-dev/hls.js package version.
|
27700
28521
|
*/
|
27701
28522
|
static get version() {
|
27702
|
-
return "1.5.
|
28523
|
+
return "1.5.5-0.canary.9977";
|
27703
28524
|
}
|
27704
28525
|
|
27705
28526
|
/**
|
@@ -27762,9 +28583,12 @@ class Hls {
|
|
27762
28583
|
* The configuration object provided on player instantiation.
|
27763
28584
|
*/
|
27764
28585
|
this.userConfig = void 0;
|
28586
|
+
/**
|
28587
|
+
* The logger functions used by this player instance, configured on player instantiation.
|
28588
|
+
*/
|
28589
|
+
this.logger = void 0;
|
27765
28590
|
this.coreComponents = void 0;
|
27766
28591
|
this.networkControllers = void 0;
|
27767
|
-
this.started = false;
|
27768
28592
|
this._emitter = new EventEmitter();
|
27769
28593
|
this._autoLevelCapping = -1;
|
27770
28594
|
this._maxHdcpLevel = null;
|
@@ -27781,11 +28605,11 @@ class Hls {
|
|
27781
28605
|
this._media = null;
|
27782
28606
|
this.url = null;
|
27783
28607
|
this.triggeringException = void 0;
|
27784
|
-
enableLogs(userConfig.debug || false, 'Hls instance');
|
27785
|
-
const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig);
|
28608
|
+
const logger = this.logger = enableLogs(userConfig.debug || false, 'Hls instance');
|
28609
|
+
const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig, logger);
|
27786
28610
|
this.userConfig = userConfig;
|
27787
28611
|
if (config.progressive) {
|
27788
|
-
enableStreamingMode(config);
|
28612
|
+
enableStreamingMode(config, logger);
|
27789
28613
|
}
|
27790
28614
|
|
27791
28615
|
// core controllers and network loaders
|
@@ -27884,7 +28708,7 @@ class Hls {
|
|
27884
28708
|
try {
|
27885
28709
|
return this.emit(event, event, eventObject);
|
27886
28710
|
} catch (error) {
|
27887
|
-
logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
|
28711
|
+
this.logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
|
27888
28712
|
// Prevent recursion in error event handlers that throw #5497
|
27889
28713
|
if (!this.triggeringException) {
|
27890
28714
|
this.triggeringException = true;
|
@@ -27910,7 +28734,7 @@ class Hls {
|
|
27910
28734
|
* Dispose of the instance
|
27911
28735
|
*/
|
27912
28736
|
destroy() {
|
27913
|
-
logger.log('destroy');
|
28737
|
+
this.logger.log('destroy');
|
27914
28738
|
this.trigger(Events.DESTROYING, undefined);
|
27915
28739
|
this.detachMedia();
|
27916
28740
|
this.removeAllListeners();
|
@@ -27931,7 +28755,7 @@ class Hls {
|
|
27931
28755
|
* Attaches Hls.js to a media element
|
27932
28756
|
*/
|
27933
28757
|
attachMedia(media) {
|
27934
|
-
logger.log('attachMedia');
|
28758
|
+
this.logger.log('attachMedia');
|
27935
28759
|
this._media = media;
|
27936
28760
|
this.trigger(Events.MEDIA_ATTACHING, {
|
27937
28761
|
media: media
|
@@ -27942,7 +28766,7 @@ class Hls {
|
|
27942
28766
|
* Detach Hls.js from the media
|
27943
28767
|
*/
|
27944
28768
|
detachMedia() {
|
27945
|
-
logger.log('detachMedia');
|
28769
|
+
this.logger.log('detachMedia');
|
27946
28770
|
this.trigger(Events.MEDIA_DETACHING, undefined);
|
27947
28771
|
this._media = null;
|
27948
28772
|
}
|
@@ -27959,7 +28783,7 @@ class Hls {
|
|
27959
28783
|
});
|
27960
28784
|
this._autoLevelCapping = -1;
|
27961
28785
|
this._maxHdcpLevel = null;
|
27962
|
-
logger.log(`loadSource:${loadingSource}`);
|
28786
|
+
this.logger.log(`loadSource:${loadingSource}`);
|
27963
28787
|
if (media && loadedSource && (loadedSource !== loadingSource || this.bufferController.hasSourceTypes())) {
|
27964
28788
|
this.detachMedia();
|
27965
28789
|
this.attachMedia(media);
|
@@ -27978,8 +28802,7 @@ class Hls {
|
|
27978
28802
|
* Defaults to -1 (None: starts from earliest point)
|
27979
28803
|
*/
|
27980
28804
|
startLoad(startPosition = -1) {
|
27981
|
-
logger.log(`startLoad(${startPosition})`);
|
27982
|
-
this.started = true;
|
28805
|
+
this.logger.log(`startLoad(${startPosition})`);
|
27983
28806
|
this.networkControllers.forEach(controller => {
|
27984
28807
|
controller.startLoad(startPosition);
|
27985
28808
|
});
|
@@ -27989,34 +28812,31 @@ class Hls {
|
|
27989
28812
|
* Stop loading of any stream data.
|
27990
28813
|
*/
|
27991
28814
|
stopLoad() {
|
27992
|
-
logger.log('stopLoad');
|
27993
|
-
this.started = false;
|
28815
|
+
this.logger.log('stopLoad');
|
27994
28816
|
this.networkControllers.forEach(controller => {
|
27995
28817
|
controller.stopLoad();
|
27996
28818
|
});
|
27997
28819
|
}
|
27998
28820
|
|
27999
28821
|
/**
|
28000
|
-
* Resumes stream controller segment loading
|
28822
|
+
* Resumes stream controller segment loading after `pauseBuffering` has been called.
|
28001
28823
|
*/
|
28002
28824
|
resumeBuffering() {
|
28003
|
-
|
28004
|
-
|
28005
|
-
|
28006
|
-
|
28007
|
-
|
28008
|
-
});
|
28009
|
-
}
|
28825
|
+
this.networkControllers.forEach(controller => {
|
28826
|
+
if (controller.resumeBuffering) {
|
28827
|
+
controller.resumeBuffering();
|
28828
|
+
}
|
28829
|
+
});
|
28010
28830
|
}
|
28011
28831
|
|
28012
28832
|
/**
|
28013
|
-
*
|
28833
|
+
* Prevents stream controller from loading new segments until `resumeBuffering` is called.
|
28014
28834
|
* This allows for media buffering to be paused without interupting playlist loading.
|
28015
28835
|
*/
|
28016
28836
|
pauseBuffering() {
|
28017
28837
|
this.networkControllers.forEach(controller => {
|
28018
|
-
if (
|
28019
|
-
controller.
|
28838
|
+
if (controller.pauseBuffering) {
|
28839
|
+
controller.pauseBuffering();
|
28020
28840
|
}
|
28021
28841
|
});
|
28022
28842
|
}
|
@@ -28025,7 +28845,7 @@ class Hls {
|
|
28025
28845
|
* Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
|
28026
28846
|
*/
|
28027
28847
|
swapAudioCodec() {
|
28028
|
-
logger.log('swapAudioCodec');
|
28848
|
+
this.logger.log('swapAudioCodec');
|
28029
28849
|
this.streamController.swapAudioCodec();
|
28030
28850
|
}
|
28031
28851
|
|
@@ -28036,7 +28856,7 @@ class Hls {
|
|
28036
28856
|
* Automatic recovery of media-errors by this process is configurable.
|
28037
28857
|
*/
|
28038
28858
|
recoverMediaError() {
|
28039
|
-
logger.log('recoverMediaError');
|
28859
|
+
this.logger.log('recoverMediaError');
|
28040
28860
|
const media = this._media;
|
28041
28861
|
this.detachMedia();
|
28042
28862
|
if (media) {
|
@@ -28066,7 +28886,7 @@ class Hls {
|
|
28066
28886
|
* 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
28887
|
*/
|
28068
28888
|
set currentLevel(newLevel) {
|
28069
|
-
logger.log(`set currentLevel:${newLevel}`);
|
28889
|
+
this.logger.log(`set currentLevel:${newLevel}`);
|
28070
28890
|
this.levelController.manualLevel = newLevel;
|
28071
28891
|
this.streamController.immediateLevelSwitch();
|
28072
28892
|
}
|
@@ -28085,7 +28905,7 @@ class Hls {
|
|
28085
28905
|
* @param newLevel - Pass -1 for automatic level selection
|
28086
28906
|
*/
|
28087
28907
|
set nextLevel(newLevel) {
|
28088
|
-
logger.log(`set nextLevel:${newLevel}`);
|
28908
|
+
this.logger.log(`set nextLevel:${newLevel}`);
|
28089
28909
|
this.levelController.manualLevel = newLevel;
|
28090
28910
|
this.streamController.nextLevelSwitch();
|
28091
28911
|
}
|
@@ -28104,7 +28924,7 @@ class Hls {
|
|
28104
28924
|
* @param newLevel - Pass -1 for automatic level selection
|
28105
28925
|
*/
|
28106
28926
|
set loadLevel(newLevel) {
|
28107
|
-
logger.log(`set loadLevel:${newLevel}`);
|
28927
|
+
this.logger.log(`set loadLevel:${newLevel}`);
|
28108
28928
|
this.levelController.manualLevel = newLevel;
|
28109
28929
|
}
|
28110
28930
|
|
@@ -28135,7 +28955,7 @@ class Hls {
|
|
28135
28955
|
* Sets "first-level", see getter.
|
28136
28956
|
*/
|
28137
28957
|
set firstLevel(newLevel) {
|
28138
|
-
logger.log(`set firstLevel:${newLevel}`);
|
28958
|
+
this.logger.log(`set firstLevel:${newLevel}`);
|
28139
28959
|
this.levelController.firstLevel = newLevel;
|
28140
28960
|
}
|
28141
28961
|
|
@@ -28160,7 +28980,7 @@ class Hls {
|
|
28160
28980
|
* (determined from download of first segment)
|
28161
28981
|
*/
|
28162
28982
|
set startLevel(newLevel) {
|
28163
|
-
logger.log(`set startLevel:${newLevel}`);
|
28983
|
+
this.logger.log(`set startLevel:${newLevel}`);
|
28164
28984
|
// if not in automatic start level detection, ensure startLevel is greater than minAutoLevel
|
28165
28985
|
if (newLevel !== -1) {
|
28166
28986
|
newLevel = Math.max(newLevel, this.minAutoLevel);
|
@@ -28235,7 +29055,7 @@ class Hls {
|
|
28235
29055
|
*/
|
28236
29056
|
set autoLevelCapping(newLevel) {
|
28237
29057
|
if (this._autoLevelCapping !== newLevel) {
|
28238
|
-
logger.log(`set autoLevelCapping:${newLevel}`);
|
29058
|
+
this.logger.log(`set autoLevelCapping:${newLevel}`);
|
28239
29059
|
this._autoLevelCapping = newLevel;
|
28240
29060
|
this.levelController.checkMaxAutoUpdated();
|
28241
29061
|
}
|