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