hls.js 1.5.4 → 1.5.5-0.canary.9978
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/hls-demo.js +10 -0
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +1935 -1094
- package/dist/hls.js.d.ts +63 -50
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +1059 -838
- package/dist/hls.light.js.map +1 -1
- package/dist/hls.light.min.js +1 -1
- package/dist/hls.light.min.js.map +1 -1
- package/dist/hls.light.mjs +846 -626
- package/dist/hls.light.mjs.map +1 -1
- package/dist/hls.min.js +1 -1
- package/dist/hls.min.js.map +1 -1
- package/dist/hls.mjs +1640 -814
- package/dist/hls.mjs.map +1 -1
- package/dist/hls.worker.js +1 -1
- package/dist/hls.worker.js.map +1 -1
- package/package.json +18 -18
- package/src/config.ts +3 -2
- package/src/controller/abr-controller.ts +21 -20
- package/src/controller/audio-stream-controller.ts +15 -16
- package/src/controller/audio-track-controller.ts +1 -1
- package/src/controller/base-playlist-controller.ts +7 -7
- package/src/controller/base-stream-controller.ts +56 -29
- package/src/controller/buffer-controller.ts +11 -11
- package/src/controller/cap-level-controller.ts +1 -2
- package/src/controller/cmcd-controller.ts +25 -3
- package/src/controller/content-steering-controller.ts +8 -6
- package/src/controller/eme-controller.ts +9 -22
- package/src/controller/error-controller.ts +6 -8
- package/src/controller/fps-controller.ts +2 -3
- package/src/controller/gap-controller.ts +43 -16
- package/src/controller/latency-controller.ts +9 -11
- package/src/controller/level-controller.ts +5 -17
- package/src/controller/stream-controller.ts +25 -32
- package/src/controller/subtitle-stream-controller.ts +13 -14
- package/src/controller/subtitle-track-controller.ts +5 -3
- package/src/controller/timeline-controller.ts +23 -30
- package/src/crypt/aes-crypto.ts +21 -2
- package/src/crypt/decrypter-aes-mode.ts +4 -0
- package/src/crypt/decrypter.ts +32 -18
- package/src/crypt/fast-aes-key.ts +24 -5
- package/src/demux/audio/adts.ts +9 -4
- package/src/demux/sample-aes.ts +2 -0
- package/src/demux/transmuxer-interface.ts +4 -12
- package/src/demux/transmuxer-worker.ts +4 -4
- package/src/demux/transmuxer.ts +16 -3
- package/src/demux/tsdemuxer.ts +71 -37
- package/src/demux/video/avc-video-parser.ts +208 -119
- package/src/demux/video/base-video-parser.ts +134 -2
- package/src/demux/video/exp-golomb.ts +0 -208
- package/src/demux/video/hevc-video-parser.ts +746 -0
- package/src/events.ts +7 -0
- package/src/hls.ts +42 -34
- package/src/loader/fragment-loader.ts +9 -2
- package/src/loader/key-loader.ts +2 -0
- package/src/loader/level-key.ts +10 -9
- package/src/remux/mp4-generator.ts +196 -1
- package/src/remux/mp4-remuxer.ts +23 -7
- package/src/task-loop.ts +5 -2
- package/src/types/component-api.ts +2 -0
- package/src/types/demuxer.ts +3 -0
- package/src/types/events.ts +4 -0
- package/src/utils/codecs.ts +33 -4
- package/src/utils/encryption-methods-util.ts +21 -0
- package/src/utils/logger.ts +53 -24
package/dist/hls.light.mjs
CHANGED
@@ -256,6 +256,7 @@ let Events = /*#__PURE__*/function (Events) {
|
|
256
256
|
Events["MEDIA_ATTACHED"] = "hlsMediaAttached";
|
257
257
|
Events["MEDIA_DETACHING"] = "hlsMediaDetaching";
|
258
258
|
Events["MEDIA_DETACHED"] = "hlsMediaDetached";
|
259
|
+
Events["MEDIA_ENDED"] = "hlsMediaEnded";
|
259
260
|
Events["BUFFER_RESET"] = "hlsBufferReset";
|
260
261
|
Events["BUFFER_CODECS"] = "hlsBufferCodecs";
|
261
262
|
Events["BUFFER_CREATED"] = "hlsBufferCreated";
|
@@ -369,6 +370,23 @@ let ErrorDetails = /*#__PURE__*/function (ErrorDetails) {
|
|
369
370
|
return ErrorDetails;
|
370
371
|
}({});
|
371
372
|
|
373
|
+
class Logger {
|
374
|
+
constructor(label, logger) {
|
375
|
+
this.trace = void 0;
|
376
|
+
this.debug = void 0;
|
377
|
+
this.log = void 0;
|
378
|
+
this.warn = void 0;
|
379
|
+
this.info = void 0;
|
380
|
+
this.error = void 0;
|
381
|
+
const lb = `[${label}]:`;
|
382
|
+
this.trace = noop;
|
383
|
+
this.debug = logger.debug.bind(null, lb);
|
384
|
+
this.log = logger.log.bind(null, lb);
|
385
|
+
this.warn = logger.warn.bind(null, lb);
|
386
|
+
this.info = logger.info.bind(null, lb);
|
387
|
+
this.error = logger.error.bind(null, lb);
|
388
|
+
}
|
389
|
+
}
|
372
390
|
const noop = function noop() {};
|
373
391
|
const fakeLogger = {
|
374
392
|
trace: noop,
|
@@ -378,7 +396,9 @@ const fakeLogger = {
|
|
378
396
|
info: noop,
|
379
397
|
error: noop
|
380
398
|
};
|
381
|
-
|
399
|
+
function createLogger() {
|
400
|
+
return _extends({}, fakeLogger);
|
401
|
+
}
|
382
402
|
|
383
403
|
// let lastCallTime;
|
384
404
|
// function formatMsgWithTimeInfo(type, msg) {
|
@@ -389,35 +409,36 @@ let exportedLogger = fakeLogger;
|
|
389
409
|
// return msg;
|
390
410
|
// }
|
391
411
|
|
392
|
-
function consolePrintFn(type) {
|
412
|
+
function consolePrintFn(type, id) {
|
393
413
|
const func = self.console[type];
|
394
|
-
|
395
|
-
return func.bind(self.console, `[${type}] >`);
|
396
|
-
}
|
397
|
-
return noop;
|
414
|
+
return func ? func.bind(self.console, `${id ? '[' + id + '] ' : ''}[${type}] >`) : noop;
|
398
415
|
}
|
399
|
-
function
|
400
|
-
|
401
|
-
exportedLogger[type] = debugConfig[type] ? debugConfig[type].bind(debugConfig) : consolePrintFn(type);
|
402
|
-
});
|
416
|
+
function getLoggerFn(key, debugConfig, id) {
|
417
|
+
return debugConfig[key] ? debugConfig[key].bind(debugConfig) : consolePrintFn(key, id);
|
403
418
|
}
|
404
|
-
|
419
|
+
let exportedLogger = createLogger();
|
420
|
+
function enableLogs(debugConfig, context, id) {
|
405
421
|
// check that console is available
|
422
|
+
const newLogger = createLogger();
|
406
423
|
if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
|
407
|
-
|
424
|
+
const keys = [
|
408
425
|
// Remove out from list here to hard-disable a log-level
|
409
426
|
// 'trace',
|
410
|
-
'debug', 'log', 'info', 'warn', 'error'
|
427
|
+
'debug', 'log', 'info', 'warn', 'error'];
|
428
|
+
keys.forEach(key => {
|
429
|
+
newLogger[key] = getLoggerFn(key, debugConfig, id);
|
430
|
+
});
|
411
431
|
// Some browsers don't allow to use bind on console object anyway
|
412
432
|
// fallback to default if needed
|
413
433
|
try {
|
414
|
-
|
434
|
+
newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.5.5-0.canary.9978"}`);
|
415
435
|
} catch (e) {
|
416
|
-
|
436
|
+
/* log fn threw an exception. All logger methods are no-ops. */
|
437
|
+
return createLogger();
|
417
438
|
}
|
418
|
-
} else {
|
419
|
-
exportedLogger = fakeLogger;
|
420
439
|
}
|
440
|
+
exportedLogger = newLogger;
|
441
|
+
return newLogger;
|
421
442
|
}
|
422
443
|
const logger = exportedLogger;
|
423
444
|
|
@@ -991,10 +1012,30 @@ class LevelDetails {
|
|
991
1012
|
}
|
992
1013
|
}
|
993
1014
|
|
1015
|
+
var DecrypterAesMode = {
|
1016
|
+
cbc: 0,
|
1017
|
+
ctr: 1
|
1018
|
+
};
|
1019
|
+
|
1020
|
+
function isFullSegmentEncryption(method) {
|
1021
|
+
return method === 'AES-128' || method === 'AES-256' || method === 'AES-256-CTR';
|
1022
|
+
}
|
1023
|
+
function getAesModeFromFullSegmentMethod(method) {
|
1024
|
+
switch (method) {
|
1025
|
+
case 'AES-128':
|
1026
|
+
case 'AES-256':
|
1027
|
+
return DecrypterAesMode.cbc;
|
1028
|
+
case 'AES-256-CTR':
|
1029
|
+
return DecrypterAesMode.ctr;
|
1030
|
+
default:
|
1031
|
+
throw new Error(`invalid full segment method ${method}`);
|
1032
|
+
}
|
1033
|
+
}
|
1034
|
+
|
994
1035
|
// This file is inserted as a shim for modules which we do not want to include into the distro.
|
995
1036
|
// This replacement is done in the "alias" plugin of the rollup config.
|
996
1037
|
var empty = undefined;
|
997
|
-
var
|
1038
|
+
var HevcVideoParser = /*@__PURE__*/getDefaultExportFromCjs(empty);
|
998
1039
|
|
999
1040
|
function sliceUint8(array, start, end) {
|
1000
1041
|
// @ts-expect-error This polyfills IE11 usage of Uint8Array slice.
|
@@ -2431,12 +2472,12 @@ class LevelKey {
|
|
2431
2472
|
this.keyFormatVersions = formatversions;
|
2432
2473
|
this.iv = iv;
|
2433
2474
|
this.encrypted = method ? method !== 'NONE' : false;
|
2434
|
-
this.isCommonEncryption = this.encrypted && method
|
2475
|
+
this.isCommonEncryption = this.encrypted && !isFullSegmentEncryption(method);
|
2435
2476
|
}
|
2436
2477
|
isSupported() {
|
2437
2478
|
// If it's Segment encryption or No encryption, just select that key system
|
2438
2479
|
if (this.method) {
|
2439
|
-
if (this.method
|
2480
|
+
if (isFullSegmentEncryption(this.method) || this.method === 'NONE') {
|
2440
2481
|
return true;
|
2441
2482
|
}
|
2442
2483
|
if (this.keyFormat === 'identity') {
|
@@ -2450,14 +2491,13 @@ class LevelKey {
|
|
2450
2491
|
if (!this.encrypted || !this.uri) {
|
2451
2492
|
return null;
|
2452
2493
|
}
|
2453
|
-
if (this.method
|
2494
|
+
if (isFullSegmentEncryption(this.method) && this.uri && !this.iv) {
|
2454
2495
|
if (typeof sn !== 'number') {
|
2455
2496
|
// We are fetching decryption data for a initialization segment
|
2456
|
-
// If the segment was encrypted with AES-128
|
2497
|
+
// If the segment was encrypted with AES-128/256
|
2457
2498
|
// It must have an IV defined. We cannot substitute the Segment Number in.
|
2458
|
-
|
2459
|
-
|
2460
|
-
}
|
2499
|
+
logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
|
2500
|
+
|
2461
2501
|
// Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
|
2462
2502
|
sn = 0;
|
2463
2503
|
}
|
@@ -2604,23 +2644,28 @@ function getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource =
|
|
2604
2644
|
if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) {
|
2605
2645
|
return CODEC_COMPATIBLE_NAMES[lowerCaseCodec];
|
2606
2646
|
}
|
2607
|
-
|
2608
|
-
// Idealy fLaC and Opus would be first (spec-compliant) but
|
2609
|
-
// some browsers will report that fLaC is supported then fail.
|
2610
|
-
// see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
|
2611
2647
|
const codecsToCheck = {
|
2648
|
+
// Idealy fLaC and Opus would be first (spec-compliant) but
|
2649
|
+
// some browsers will report that fLaC is supported then fail.
|
2650
|
+
// see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
|
2612
2651
|
flac: ['flac', 'fLaC', 'FLAC'],
|
2613
|
-
opus: ['opus', 'Opus']
|
2652
|
+
opus: ['opus', 'Opus'],
|
2653
|
+
// Replace audio codec info if browser does not support mp4a.40.34,
|
2654
|
+
// and demuxer can fallback to 'audio/mpeg' or 'audio/mp4;codecs="mp3"'
|
2655
|
+
'mp4a.40.34': ['mp3']
|
2614
2656
|
}[lowerCaseCodec];
|
2615
2657
|
for (let i = 0; i < codecsToCheck.length; i++) {
|
2658
|
+
var _getMediaSource;
|
2616
2659
|
if (isCodecMediaSourceSupported(codecsToCheck[i], 'audio', preferManagedMediaSource)) {
|
2617
2660
|
CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
|
2618
2661
|
return codecsToCheck[i];
|
2662
|
+
} else if (codecsToCheck[i] === 'mp3' && (_getMediaSource = getMediaSource(preferManagedMediaSource)) != null && _getMediaSource.isTypeSupported('audio/mpeg')) {
|
2663
|
+
return '';
|
2619
2664
|
}
|
2620
2665
|
}
|
2621
2666
|
return lowerCaseCodec;
|
2622
2667
|
}
|
2623
|
-
const AUDIO_CODEC_REGEXP = /flac|opus/i;
|
2668
|
+
const AUDIO_CODEC_REGEXP = /flac|opus|mp4a\.40\.34/i;
|
2624
2669
|
function getCodecCompatibleName(codec, preferManagedMediaSource = true) {
|
2625
2670
|
return codec.replace(AUDIO_CODEC_REGEXP, m => getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource));
|
2626
2671
|
}
|
@@ -2643,6 +2688,16 @@ function convertAVC1ToAVCOTI(codec) {
|
|
2643
2688
|
}
|
2644
2689
|
return codec;
|
2645
2690
|
}
|
2691
|
+
function getM2TSSupportedAudioTypes(preferManagedMediaSource) {
|
2692
|
+
const MediaSource = getMediaSource(preferManagedMediaSource) || {
|
2693
|
+
isTypeSupported: () => false
|
2694
|
+
};
|
2695
|
+
return {
|
2696
|
+
mpeg: MediaSource.isTypeSupported('audio/mpeg'),
|
2697
|
+
mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
|
2698
|
+
ac3: false
|
2699
|
+
};
|
2700
|
+
}
|
2646
2701
|
|
2647
2702
|
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;
|
2648
2703
|
const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g;
|
@@ -4220,7 +4275,47 @@ class LatencyController {
|
|
4220
4275
|
this.currentTime = 0;
|
4221
4276
|
this.stallCount = 0;
|
4222
4277
|
this._latency = null;
|
4223
|
-
this.
|
4278
|
+
this.onTimeupdate = () => {
|
4279
|
+
const {
|
4280
|
+
media,
|
4281
|
+
levelDetails
|
4282
|
+
} = this;
|
4283
|
+
if (!media || !levelDetails) {
|
4284
|
+
return;
|
4285
|
+
}
|
4286
|
+
this.currentTime = media.currentTime;
|
4287
|
+
const latency = this.computeLatency();
|
4288
|
+
if (latency === null) {
|
4289
|
+
return;
|
4290
|
+
}
|
4291
|
+
this._latency = latency;
|
4292
|
+
|
4293
|
+
// Adapt playbackRate to meet target latency in low-latency mode
|
4294
|
+
const {
|
4295
|
+
lowLatencyMode,
|
4296
|
+
maxLiveSyncPlaybackRate
|
4297
|
+
} = this.config;
|
4298
|
+
if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
|
4299
|
+
return;
|
4300
|
+
}
|
4301
|
+
const targetLatency = this.targetLatency;
|
4302
|
+
if (targetLatency === null) {
|
4303
|
+
return;
|
4304
|
+
}
|
4305
|
+
const distanceFromTarget = latency - targetLatency;
|
4306
|
+
// Only adjust playbackRate when within one target duration of targetLatency
|
4307
|
+
// and more than one second from under-buffering.
|
4308
|
+
// Playback further than one target duration from target can be considered DVR playback.
|
4309
|
+
const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
|
4310
|
+
const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
|
4311
|
+
if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
|
4312
|
+
const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
|
4313
|
+
const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
|
4314
|
+
media.playbackRate = Math.min(max, Math.max(1, rate));
|
4315
|
+
} else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
|
4316
|
+
media.playbackRate = 1;
|
4317
|
+
}
|
4318
|
+
};
|
4224
4319
|
this.hls = hls;
|
4225
4320
|
this.config = hls.config;
|
4226
4321
|
this.registerListeners();
|
@@ -4312,7 +4407,7 @@ class LatencyController {
|
|
4312
4407
|
this.onMediaDetaching();
|
4313
4408
|
this.levelDetails = null;
|
4314
4409
|
// @ts-ignore
|
4315
|
-
this.hls =
|
4410
|
+
this.hls = null;
|
4316
4411
|
}
|
4317
4412
|
registerListeners() {
|
4318
4413
|
this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
@@ -4330,11 +4425,11 @@ class LatencyController {
|
|
4330
4425
|
}
|
4331
4426
|
onMediaAttached(event, data) {
|
4332
4427
|
this.media = data.media;
|
4333
|
-
this.media.addEventListener('timeupdate', this.
|
4428
|
+
this.media.addEventListener('timeupdate', this.onTimeupdate);
|
4334
4429
|
}
|
4335
4430
|
onMediaDetaching() {
|
4336
4431
|
if (this.media) {
|
4337
|
-
this.media.removeEventListener('timeupdate', this.
|
4432
|
+
this.media.removeEventListener('timeupdate', this.onTimeupdate);
|
4338
4433
|
this.media = null;
|
4339
4434
|
}
|
4340
4435
|
}
|
@@ -4348,10 +4443,10 @@ class LatencyController {
|
|
4348
4443
|
}) {
|
4349
4444
|
this.levelDetails = details;
|
4350
4445
|
if (details.advanced) {
|
4351
|
-
this.
|
4446
|
+
this.onTimeupdate();
|
4352
4447
|
}
|
4353
4448
|
if (!details.live && this.media) {
|
4354
|
-
this.media.removeEventListener('timeupdate', this.
|
4449
|
+
this.media.removeEventListener('timeupdate', this.onTimeupdate);
|
4355
4450
|
}
|
4356
4451
|
}
|
4357
4452
|
onError(event, data) {
|
@@ -4361,48 +4456,7 @@ class LatencyController {
|
|
4361
4456
|
}
|
4362
4457
|
this.stallCount++;
|
4363
4458
|
if ((_this$levelDetails = this.levelDetails) != null && _this$levelDetails.live) {
|
4364
|
-
logger.warn('[
|
4365
|
-
}
|
4366
|
-
}
|
4367
|
-
timeupdate() {
|
4368
|
-
const {
|
4369
|
-
media,
|
4370
|
-
levelDetails
|
4371
|
-
} = this;
|
4372
|
-
if (!media || !levelDetails) {
|
4373
|
-
return;
|
4374
|
-
}
|
4375
|
-
this.currentTime = media.currentTime;
|
4376
|
-
const latency = this.computeLatency();
|
4377
|
-
if (latency === null) {
|
4378
|
-
return;
|
4379
|
-
}
|
4380
|
-
this._latency = latency;
|
4381
|
-
|
4382
|
-
// Adapt playbackRate to meet target latency in low-latency mode
|
4383
|
-
const {
|
4384
|
-
lowLatencyMode,
|
4385
|
-
maxLiveSyncPlaybackRate
|
4386
|
-
} = this.config;
|
4387
|
-
if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
|
4388
|
-
return;
|
4389
|
-
}
|
4390
|
-
const targetLatency = this.targetLatency;
|
4391
|
-
if (targetLatency === null) {
|
4392
|
-
return;
|
4393
|
-
}
|
4394
|
-
const distanceFromTarget = latency - targetLatency;
|
4395
|
-
// Only adjust playbackRate when within one target duration of targetLatency
|
4396
|
-
// and more than one second from under-buffering.
|
4397
|
-
// Playback further than one target duration from target can be considered DVR playback.
|
4398
|
-
const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
|
4399
|
-
const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
|
4400
|
-
if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
|
4401
|
-
const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
|
4402
|
-
const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
|
4403
|
-
media.playbackRate = Math.min(max, Math.max(1, rate));
|
4404
|
-
} else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
|
4405
|
-
media.playbackRate = 1;
|
4459
|
+
this.hls.logger.warn('[latency-controller]: Stall detected, adjusting target latency');
|
4406
4460
|
}
|
4407
4461
|
}
|
4408
4462
|
estimateLiveEdge() {
|
@@ -5174,18 +5228,13 @@ var ErrorActionFlags = {
|
|
5174
5228
|
MoveAllAlternatesMatchingHDCP: 2,
|
5175
5229
|
SwitchToSDR: 4
|
5176
5230
|
}; // Reserved for future use
|
5177
|
-
class ErrorController {
|
5231
|
+
class ErrorController extends Logger {
|
5178
5232
|
constructor(hls) {
|
5233
|
+
super('error-controller', hls.logger);
|
5179
5234
|
this.hls = void 0;
|
5180
5235
|
this.playlistError = 0;
|
5181
5236
|
this.penalizedRenditions = {};
|
5182
|
-
this.log = void 0;
|
5183
|
-
this.warn = void 0;
|
5184
|
-
this.error = void 0;
|
5185
5237
|
this.hls = hls;
|
5186
|
-
this.log = logger.log.bind(logger, `[info]:`);
|
5187
|
-
this.warn = logger.warn.bind(logger, `[warning]:`);
|
5188
|
-
this.error = logger.error.bind(logger, `[error]:`);
|
5189
5238
|
this.registerListeners();
|
5190
5239
|
}
|
5191
5240
|
registerListeners() {
|
@@ -5537,16 +5586,13 @@ class ErrorController {
|
|
5537
5586
|
}
|
5538
5587
|
}
|
5539
5588
|
|
5540
|
-
class BasePlaylistController {
|
5589
|
+
class BasePlaylistController extends Logger {
|
5541
5590
|
constructor(hls, logPrefix) {
|
5591
|
+
super(logPrefix, hls.logger);
|
5542
5592
|
this.hls = void 0;
|
5543
5593
|
this.timer = -1;
|
5544
5594
|
this.requestScheduled = -1;
|
5545
5595
|
this.canLoad = false;
|
5546
|
-
this.log = void 0;
|
5547
|
-
this.warn = void 0;
|
5548
|
-
this.log = logger.log.bind(logger, `${logPrefix}:`);
|
5549
|
-
this.warn = logger.warn.bind(logger, `${logPrefix}:`);
|
5550
5596
|
this.hls = hls;
|
5551
5597
|
}
|
5552
5598
|
destroy() {
|
@@ -5579,7 +5625,7 @@ class BasePlaylistController {
|
|
5579
5625
|
try {
|
5580
5626
|
uri = new self.URL(attr.URI, previous.url).href;
|
5581
5627
|
} catch (error) {
|
5582
|
-
|
5628
|
+
this.warn(`Could not construct new URL for Rendition Report: ${error}`);
|
5583
5629
|
uri = attr.URI || '';
|
5584
5630
|
}
|
5585
5631
|
// Use exact match. Otherwise, the last partial match, if any, will be used
|
@@ -6125,8 +6171,9 @@ function getCodecTiers(levels, audioTracksByGroup, minAutoLevel, maxAutoLevel) {
|
|
6125
6171
|
}, {});
|
6126
6172
|
}
|
6127
6173
|
|
6128
|
-
class AbrController {
|
6174
|
+
class AbrController extends Logger {
|
6129
6175
|
constructor(_hls) {
|
6176
|
+
super('abr', _hls.logger);
|
6130
6177
|
this.hls = void 0;
|
6131
6178
|
this.lastLevelLoadSec = 0;
|
6132
6179
|
this.lastLoadedFragLevel = -1;
|
@@ -6240,7 +6287,7 @@ class AbrController {
|
|
6240
6287
|
this.resetEstimator(nextLoadLevelBitrate);
|
6241
6288
|
}
|
6242
6289
|
this.clearTimer();
|
6243
|
-
|
6290
|
+
this.warn(`Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
|
6244
6291
|
Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s
|
6245
6292
|
Estimated load time for current fragment: ${fragLoadedDelay.toFixed(3)} s
|
6246
6293
|
Estimated load time for down switch fragment: ${fragLevelNextLoadedDelay.toFixed(3)} s
|
@@ -6260,7 +6307,7 @@ class AbrController {
|
|
6260
6307
|
}
|
6261
6308
|
resetEstimator(abrEwmaDefaultEstimate) {
|
6262
6309
|
if (abrEwmaDefaultEstimate) {
|
6263
|
-
|
6310
|
+
this.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
|
6264
6311
|
this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate;
|
6265
6312
|
}
|
6266
6313
|
this.firstSelection = -1;
|
@@ -6492,7 +6539,7 @@ class AbrController {
|
|
6492
6539
|
}
|
6493
6540
|
const firstLevel = this.hls.firstLevel;
|
6494
6541
|
const clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel);
|
6495
|
-
|
6542
|
+
this.warn(`Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
|
6496
6543
|
return clamped;
|
6497
6544
|
}
|
6498
6545
|
get forcedAutoLevel() {
|
@@ -6577,13 +6624,13 @@ class AbrController {
|
|
6577
6624
|
// cap maxLoadingDelay and ensure it is not bigger 'than bitrate test' frag duration
|
6578
6625
|
const maxLoadingDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxLoadingDelay) : config.maxLoadingDelay;
|
6579
6626
|
maxStarvationDelay = maxLoadingDelay - bitrateTestDelay;
|
6580
|
-
|
6627
|
+
this.info(`bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
|
6581
6628
|
// don't use conservative factor on bitrate test
|
6582
6629
|
bwFactor = bwUpFactor = 1;
|
6583
6630
|
}
|
6584
6631
|
}
|
6585
6632
|
const bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor);
|
6586
|
-
|
6633
|
+
this.info(`${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
|
6587
6634
|
if (bestLevel > -1) {
|
6588
6635
|
return bestLevel;
|
6589
6636
|
}
|
@@ -6645,7 +6692,7 @@ class AbrController {
|
|
6645
6692
|
currentVideoRange = preferHDR ? videoRanges[videoRanges.length - 1] : videoRanges[0];
|
6646
6693
|
currentFrameRate = minFramerate;
|
6647
6694
|
currentBw = Math.max(currentBw, minBitrate);
|
6648
|
-
|
6695
|
+
this.log(`picked start tier ${JSON.stringify(startTier)}`);
|
6649
6696
|
} else {
|
6650
6697
|
currentCodecSet = level == null ? void 0 : level.codecSet;
|
6651
6698
|
currentVideoRange = level == null ? void 0 : level.videoRange;
|
@@ -6698,9 +6745,9 @@ class AbrController {
|
|
6698
6745
|
const forcedAutoLevel = this.forcedAutoLevel;
|
6699
6746
|
if (i !== loadLevel && (forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)) {
|
6700
6747
|
if (levelsSkipped.length) {
|
6701
|
-
|
6748
|
+
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}`);
|
6702
6749
|
}
|
6703
|
-
|
6750
|
+
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}`);
|
6704
6751
|
}
|
6705
6752
|
if (firstSelection) {
|
6706
6753
|
this.firstSelection = i;
|
@@ -6936,8 +6983,9 @@ class BufferOperationQueue {
|
|
6936
6983
|
}
|
6937
6984
|
|
6938
6985
|
const VIDEO_CODEC_PROFILE_REPLACE = /(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\.[^.,]+)+/;
|
6939
|
-
class BufferController {
|
6986
|
+
class BufferController extends Logger {
|
6940
6987
|
constructor(hls) {
|
6988
|
+
super('buffer-controller', hls.logger);
|
6941
6989
|
// The level details used to determine duration, target-duration and live
|
6942
6990
|
this.details = null;
|
6943
6991
|
// cache the self generated object url to detect hijack of video tag
|
@@ -6967,9 +7015,6 @@ class BufferController {
|
|
6967
7015
|
this.tracks = {};
|
6968
7016
|
this.pendingTracks = {};
|
6969
7017
|
this.sourceBuffer = void 0;
|
6970
|
-
this.log = void 0;
|
6971
|
-
this.warn = void 0;
|
6972
|
-
this.error = void 0;
|
6973
7018
|
this._onEndStreaming = event => {
|
6974
7019
|
if (!this.hls) {
|
6975
7020
|
return;
|
@@ -7015,15 +7060,11 @@ class BufferController {
|
|
7015
7060
|
_objectUrl
|
7016
7061
|
} = this;
|
7017
7062
|
if (mediaSrc !== _objectUrl) {
|
7018
|
-
|
7063
|
+
this.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
|
7019
7064
|
}
|
7020
7065
|
};
|
7021
7066
|
this.hls = hls;
|
7022
|
-
const logPrefix = '[buffer-controller]';
|
7023
7067
|
this.appendSource = hls.config.preferManagedMediaSource;
|
7024
|
-
this.log = logger.log.bind(logger, logPrefix);
|
7025
|
-
this.warn = logger.warn.bind(logger, logPrefix);
|
7026
|
-
this.error = logger.error.bind(logger, logPrefix);
|
7027
7068
|
this._initSourceBuffer();
|
7028
7069
|
this.registerListeners();
|
7029
7070
|
}
|
@@ -7036,6 +7077,12 @@ class BufferController {
|
|
7036
7077
|
this.lastMpegAudioChunk = null;
|
7037
7078
|
// @ts-ignore
|
7038
7079
|
this.hls = null;
|
7080
|
+
// @ts-ignore
|
7081
|
+
this._onMediaSourceOpen = this._onMediaSourceClose = null;
|
7082
|
+
// @ts-ignore
|
7083
|
+
this._onMediaSourceEnded = null;
|
7084
|
+
// @ts-ignore
|
7085
|
+
this._onStartStreaming = this._onEndStreaming = null;
|
7039
7086
|
}
|
7040
7087
|
registerListeners() {
|
7041
7088
|
const {
|
@@ -7198,6 +7245,7 @@ class BufferController {
|
|
7198
7245
|
this.resetBuffer(type);
|
7199
7246
|
});
|
7200
7247
|
this._initSourceBuffer();
|
7248
|
+
this.hls.resumeBuffering();
|
7201
7249
|
}
|
7202
7250
|
resetBuffer(type) {
|
7203
7251
|
const sb = this.sourceBuffer[type];
|
@@ -8035,7 +8083,7 @@ class CapLevelController {
|
|
8035
8083
|
const hls = this.hls;
|
8036
8084
|
const maxLevel = this.getMaxLevel(levels.length - 1);
|
8037
8085
|
if (maxLevel !== this.autoLevelCapping) {
|
8038
|
-
logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
|
8086
|
+
hls.logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
|
8039
8087
|
}
|
8040
8088
|
hls.autoLevelCapping = maxLevel;
|
8041
8089
|
if (hls.autoLevelCapping > this.autoLevelCapping && this.streamController) {
|
@@ -8213,10 +8261,10 @@ class FPSController {
|
|
8213
8261
|
totalDroppedFrames: droppedFrames
|
8214
8262
|
});
|
8215
8263
|
if (droppedFPS > 0) {
|
8216
|
-
// logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
|
8264
|
+
// hls.logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
|
8217
8265
|
if (currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
|
8218
8266
|
let currentLevel = hls.currentLevel;
|
8219
|
-
logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
|
8267
|
+
hls.logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
|
8220
8268
|
if (currentLevel > 0 && (hls.autoLevelCapping === -1 || hls.autoLevelCapping >= currentLevel)) {
|
8221
8269
|
currentLevel = currentLevel - 1;
|
8222
8270
|
hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, {
|
@@ -8249,10 +8297,10 @@ class FPSController {
|
|
8249
8297
|
}
|
8250
8298
|
|
8251
8299
|
const PATHWAY_PENALTY_DURATION_MS = 300000;
|
8252
|
-
class ContentSteeringController {
|
8300
|
+
class ContentSteeringController extends Logger {
|
8253
8301
|
constructor(hls) {
|
8302
|
+
super('content-steering', hls.logger);
|
8254
8303
|
this.hls = void 0;
|
8255
|
-
this.log = void 0;
|
8256
8304
|
this.loader = null;
|
8257
8305
|
this.uri = null;
|
8258
8306
|
this.pathwayId = '.';
|
@@ -8267,7 +8315,6 @@ class ContentSteeringController {
|
|
8267
8315
|
this.subtitleTracks = null;
|
8268
8316
|
this.penalizedPathways = {};
|
8269
8317
|
this.hls = hls;
|
8270
|
-
this.log = logger.log.bind(logger, `[content-steering]:`);
|
8271
8318
|
this.registerListeners();
|
8272
8319
|
}
|
8273
8320
|
registerListeners() {
|
@@ -8391,7 +8438,7 @@ class ContentSteeringController {
|
|
8391
8438
|
errorAction.resolved = this.pathwayId !== errorPathway;
|
8392
8439
|
}
|
8393
8440
|
if (!errorAction.resolved) {
|
8394
|
-
|
8441
|
+
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)}`);
|
8395
8442
|
}
|
8396
8443
|
}
|
8397
8444
|
}
|
@@ -8562,7 +8609,7 @@ class ContentSteeringController {
|
|
8562
8609
|
onSuccess: (response, stats, context, networkDetails) => {
|
8563
8610
|
this.log(`Loaded steering manifest: "${url}"`);
|
8564
8611
|
const steeringData = response.data;
|
8565
|
-
if (steeringData.VERSION !== 1) {
|
8612
|
+
if ((steeringData == null ? void 0 : steeringData.VERSION) !== 1) {
|
8566
8613
|
this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
|
8567
8614
|
return;
|
8568
8615
|
}
|
@@ -9470,7 +9517,7 @@ const hlsDefaultConfig = _objectSpread2(_objectSpread2({
|
|
9470
9517
|
});
|
9471
9518
|
function timelineConfig() {
|
9472
9519
|
return {
|
9473
|
-
cueHandler:
|
9520
|
+
cueHandler: HevcVideoParser,
|
9474
9521
|
// used by timeline-controller
|
9475
9522
|
enableWebVTT: false,
|
9476
9523
|
// used by timeline-controller
|
@@ -9501,7 +9548,7 @@ function timelineConfig() {
|
|
9501
9548
|
/**
|
9502
9549
|
* @ignore
|
9503
9550
|
*/
|
9504
|
-
function mergeConfig(defaultConfig, userConfig) {
|
9551
|
+
function mergeConfig(defaultConfig, userConfig, logger) {
|
9505
9552
|
if ((userConfig.liveSyncDurationCount || userConfig.liveMaxLatencyDurationCount) && (userConfig.liveSyncDuration || userConfig.liveMaxLatencyDuration)) {
|
9506
9553
|
throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration");
|
9507
9554
|
}
|
@@ -9571,7 +9618,7 @@ function deepCpy(obj) {
|
|
9571
9618
|
/**
|
9572
9619
|
* @ignore
|
9573
9620
|
*/
|
9574
|
-
function enableStreamingMode(config) {
|
9621
|
+
function enableStreamingMode(config, logger) {
|
9575
9622
|
const currentLoader = config.loader;
|
9576
9623
|
if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
|
9577
9624
|
// If a developer has configured their own loader, respect that choice
|
@@ -9588,10 +9635,9 @@ function enableStreamingMode(config) {
|
|
9588
9635
|
}
|
9589
9636
|
}
|
9590
9637
|
|
9591
|
-
let chromeOrFirefox;
|
9592
9638
|
class LevelController extends BasePlaylistController {
|
9593
9639
|
constructor(hls, contentSteeringController) {
|
9594
|
-
super(hls, '
|
9640
|
+
super(hls, 'level-controller');
|
9595
9641
|
this._levels = [];
|
9596
9642
|
this._firstLevel = -1;
|
9597
9643
|
this._maxAutoLevel = -1;
|
@@ -9662,23 +9708,15 @@ class LevelController extends BasePlaylistController {
|
|
9662
9708
|
let videoCodecFound = false;
|
9663
9709
|
let audioCodecFound = false;
|
9664
9710
|
data.levels.forEach(levelParsed => {
|
9665
|
-
var
|
9711
|
+
var _videoCodec;
|
9666
9712
|
const attributes = levelParsed.attrs;
|
9667
|
-
|
9668
|
-
// erase audio codec info if browser does not support mp4a.40.34.
|
9669
|
-
// demuxer will autodetect codec and fallback to mpeg/audio
|
9670
9713
|
let {
|
9671
9714
|
audioCodec,
|
9672
9715
|
videoCodec
|
9673
9716
|
} = levelParsed;
|
9674
|
-
if (((_audioCodec = audioCodec) == null ? void 0 : _audioCodec.indexOf('mp4a.40.34')) !== -1) {
|
9675
|
-
chromeOrFirefox || (chromeOrFirefox = /chrome|firefox/i.test(navigator.userAgent));
|
9676
|
-
if (chromeOrFirefox) {
|
9677
|
-
levelParsed.audioCodec = audioCodec = undefined;
|
9678
|
-
}
|
9679
|
-
}
|
9680
9717
|
if (audioCodec) {
|
9681
|
-
|
9718
|
+
// Returns empty and set to undefined for 'mp4a.40.34' with fallback to 'audio/mpeg' SourceBuffer
|
9719
|
+
levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource) || undefined;
|
9682
9720
|
}
|
9683
9721
|
if (((_videoCodec = videoCodec) == null ? void 0 : _videoCodec.indexOf('avc1')) === 0) {
|
9684
9722
|
videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
|
@@ -10806,8 +10844,8 @@ function createLoaderContext(frag, part = null) {
|
|
10806
10844
|
var _frag$decryptdata;
|
10807
10845
|
let byteRangeStart = start;
|
10808
10846
|
let byteRangeEnd = end;
|
10809
|
-
if (frag.sn === 'initSegment' && ((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method)
|
10810
|
-
// MAP segment encrypted with method 'AES-128', when served with HTTP Range,
|
10847
|
+
if (frag.sn === 'initSegment' && isMethodFullSegmentAesCbc((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method)) {
|
10848
|
+
// MAP segment encrypted with method 'AES-128' or 'AES-256' (cbc), when served with HTTP Range,
|
10811
10849
|
// has the unencrypted size specified in the range.
|
10812
10850
|
// Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
|
10813
10851
|
const fragmentLen = end - start;
|
@@ -10840,6 +10878,9 @@ function createGapLoadError(frag, part) {
|
|
10840
10878
|
(part ? part : frag).stats.aborted = true;
|
10841
10879
|
return new LoadError(errorData);
|
10842
10880
|
}
|
10881
|
+
function isMethodFullSegmentAesCbc(method) {
|
10882
|
+
return method === 'AES-128' || method === 'AES-256';
|
10883
|
+
}
|
10843
10884
|
class LoadError extends Error {
|
10844
10885
|
constructor(data) {
|
10845
10886
|
super(data.error.message);
|
@@ -10985,6 +11026,8 @@ class KeyLoader {
|
|
10985
11026
|
}
|
10986
11027
|
return this.loadKeyEME(keyInfo, frag);
|
10987
11028
|
case 'AES-128':
|
11029
|
+
case 'AES-256':
|
11030
|
+
case 'AES-256-CTR':
|
10988
11031
|
return this.loadKeyHTTP(keyInfo, frag);
|
10989
11032
|
default:
|
10990
11033
|
return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`)));
|
@@ -11120,8 +11163,9 @@ class KeyLoader {
|
|
11120
11163
|
* we are limiting the task execution per call stack to exactly one, but scheduling/post-poning further
|
11121
11164
|
* task processing on the next main loop iteration (also known as "next tick" in the Node/JS runtime lingo).
|
11122
11165
|
*/
|
11123
|
-
class TaskLoop {
|
11124
|
-
constructor() {
|
11166
|
+
class TaskLoop extends Logger {
|
11167
|
+
constructor(label, logger) {
|
11168
|
+
super(label, logger);
|
11125
11169
|
this._boundTick = void 0;
|
11126
11170
|
this._tickTimer = null;
|
11127
11171
|
this._tickInterval = null;
|
@@ -11389,33 +11433,61 @@ function alignMediaPlaylistByPDT(details, refDetails) {
|
|
11389
11433
|
}
|
11390
11434
|
|
11391
11435
|
class AESCrypto {
|
11392
|
-
constructor(subtle, iv) {
|
11436
|
+
constructor(subtle, iv, aesMode) {
|
11393
11437
|
this.subtle = void 0;
|
11394
11438
|
this.aesIV = void 0;
|
11439
|
+
this.aesMode = void 0;
|
11395
11440
|
this.subtle = subtle;
|
11396
11441
|
this.aesIV = iv;
|
11442
|
+
this.aesMode = aesMode;
|
11397
11443
|
}
|
11398
11444
|
decrypt(data, key) {
|
11399
|
-
|
11400
|
-
|
11401
|
-
|
11402
|
-
|
11445
|
+
switch (this.aesMode) {
|
11446
|
+
case DecrypterAesMode.cbc:
|
11447
|
+
return this.subtle.decrypt({
|
11448
|
+
name: 'AES-CBC',
|
11449
|
+
iv: this.aesIV
|
11450
|
+
}, key, data);
|
11451
|
+
case DecrypterAesMode.ctr:
|
11452
|
+
return this.subtle.decrypt({
|
11453
|
+
name: 'AES-CTR',
|
11454
|
+
counter: this.aesIV,
|
11455
|
+
length: 64
|
11456
|
+
},
|
11457
|
+
//64 : NIST SP800-38A standard suggests that the counter should occupy half of the counter block
|
11458
|
+
key, data);
|
11459
|
+
default:
|
11460
|
+
throw new Error(`[AESCrypto] invalid aes mode ${this.aesMode}`);
|
11461
|
+
}
|
11403
11462
|
}
|
11404
11463
|
}
|
11405
11464
|
|
11406
11465
|
class FastAESKey {
|
11407
|
-
constructor(subtle, key) {
|
11466
|
+
constructor(subtle, key, aesMode) {
|
11408
11467
|
this.subtle = void 0;
|
11409
11468
|
this.key = void 0;
|
11469
|
+
this.aesMode = void 0;
|
11410
11470
|
this.subtle = subtle;
|
11411
11471
|
this.key = key;
|
11472
|
+
this.aesMode = aesMode;
|
11412
11473
|
}
|
11413
11474
|
expandKey() {
|
11475
|
+
const subtleAlgoName = getSubtleAlgoName(this.aesMode);
|
11414
11476
|
return this.subtle.importKey('raw', this.key, {
|
11415
|
-
name:
|
11477
|
+
name: subtleAlgoName
|
11416
11478
|
}, false, ['encrypt', 'decrypt']);
|
11417
11479
|
}
|
11418
11480
|
}
|
11481
|
+
function getSubtleAlgoName(aesMode) {
|
11482
|
+
switch (aesMode) {
|
11483
|
+
case DecrypterAesMode.cbc:
|
11484
|
+
return 'AES-CBC';
|
11485
|
+
case DecrypterAesMode.ctr:
|
11486
|
+
return 'AES-CTR';
|
11487
|
+
default:
|
11488
|
+
throw new Error(`[FastAESKey] invalid aes mode ${aesMode}`);
|
11489
|
+
}
|
11490
|
+
}
|
11419
11491
|
|
11420
11492
|
// PKCS7
|
11421
11493
|
function removePadding(array) {
|
@@ -11665,7 +11737,8 @@ class Decrypter {
|
|
11665
11737
|
this.currentIV = null;
|
11666
11738
|
this.currentResult = null;
|
11667
11739
|
this.useSoftware = void 0;
|
11668
|
-
this.
|
11740
|
+
this.enableSoftwareAES = void 0;
|
11741
|
+
this.enableSoftwareAES = config.enableSoftwareAES;
|
11669
11742
|
this.removePKCS7Padding = removePKCS7Padding;
|
11670
11743
|
// built in decryptor expects PKCS7 padding
|
11671
11744
|
if (removePKCS7Padding) {
|
@@ -11678,9 +11751,7 @@ class Decrypter {
|
|
11678
11751
|
/* no-op */
|
11679
11752
|
}
|
11680
11753
|
}
|
11681
|
-
|
11682
|
-
this.useSoftware = true;
|
11683
|
-
}
|
11754
|
+
this.useSoftware = this.subtle === null;
|
11684
11755
|
}
|
11685
11756
|
destroy() {
|
11686
11757
|
this.subtle = null;
|
@@ -11718,10 +11789,10 @@ class Decrypter {
|
|
11718
11789
|
this.softwareDecrypter = null;
|
11719
11790
|
}
|
11720
11791
|
}
|
11721
|
-
decrypt(data, key, iv) {
|
11792
|
+
decrypt(data, key, iv, aesMode) {
|
11722
11793
|
if (this.useSoftware) {
|
11723
11794
|
return new Promise((resolve, reject) => {
|
11724
|
-
this.softwareDecrypt(new Uint8Array(data), key, iv);
|
11795
|
+
this.softwareDecrypt(new Uint8Array(data), key, iv, aesMode);
|
11725
11796
|
const decryptResult = this.flush();
|
11726
11797
|
if (decryptResult) {
|
11727
11798
|
resolve(decryptResult.buffer);
|
@@ -11730,17 +11801,21 @@ class Decrypter {
|
|
11730
11801
|
}
|
11731
11802
|
});
|
11732
11803
|
}
|
11733
|
-
return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
|
11804
|
+
return this.webCryptoDecrypt(new Uint8Array(data), key, iv, aesMode);
|
11734
11805
|
}
|
11735
11806
|
|
11736
11807
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
11737
11808
|
// data is handled in the flush() call
|
11738
|
-
softwareDecrypt(data, key, iv) {
|
11809
|
+
softwareDecrypt(data, key, iv, aesMode) {
|
11739
11810
|
const {
|
11740
11811
|
currentIV,
|
11741
11812
|
currentResult,
|
11742
11813
|
remainderData
|
11743
11814
|
} = this;
|
11815
|
+
if (aesMode !== DecrypterAesMode.cbc || key.byteLength !== 16) {
|
11816
|
+
logger.warn('SoftwareDecrypt: can only handle AES-128-CBC');
|
11817
|
+
return null;
|
11818
|
+
}
|
11744
11819
|
this.logOnce('JS AES decrypt');
|
11745
11820
|
// The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
|
11746
11821
|
// This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached
|
@@ -11773,11 +11848,11 @@ class Decrypter {
|
|
11773
11848
|
}
|
11774
11849
|
return result;
|
11775
11850
|
}
|
11776
|
-
webCryptoDecrypt(data, key, iv) {
|
11851
|
+
webCryptoDecrypt(data, key, iv, aesMode) {
|
11777
11852
|
const subtle = this.subtle;
|
11778
11853
|
if (this.key !== key || !this.fastAesKey) {
|
11779
11854
|
this.key = key;
|
11780
|
-
this.fastAesKey = new FastAESKey(subtle, key);
|
11855
|
+
this.fastAesKey = new FastAESKey(subtle, key, aesMode);
|
11781
11856
|
}
|
11782
11857
|
return this.fastAesKey.expandKey().then(aesKey => {
|
11783
11858
|
// decrypt using web crypto
|
@@ -11785,22 +11860,25 @@ class Decrypter {
|
|
11785
11860
|
return Promise.reject(new Error('web crypto not initialized'));
|
11786
11861
|
}
|
11787
11862
|
this.logOnce('WebCrypto AES decrypt');
|
11788
|
-
const crypto = new AESCrypto(subtle, new Uint8Array(iv));
|
11863
|
+
const crypto = new AESCrypto(subtle, new Uint8Array(iv), aesMode);
|
11789
11864
|
return crypto.decrypt(data.buffer, aesKey);
|
11790
11865
|
}).catch(err => {
|
11791
11866
|
logger.warn(`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`);
|
11792
|
-
return this.onWebCryptoError(data, key, iv);
|
11867
|
+
return this.onWebCryptoError(data, key, iv, aesMode);
|
11793
11868
|
});
|
11794
11869
|
}
|
11795
|
-
onWebCryptoError(data, key, iv) {
|
11796
|
-
|
11797
|
-
|
11798
|
-
|
11799
|
-
|
11800
|
-
|
11801
|
-
|
11870
|
+
onWebCryptoError(data, key, iv, aesMode) {
|
11871
|
+
const enableSoftwareAES = this.enableSoftwareAES;
|
11872
|
+
if (enableSoftwareAES) {
|
11873
|
+
this.useSoftware = true;
|
11874
|
+
this.logEnabled = true;
|
11875
|
+
this.softwareDecrypt(data, key, iv, aesMode);
|
11876
|
+
const decryptResult = this.flush();
|
11877
|
+
if (decryptResult) {
|
11878
|
+
return decryptResult.buffer;
|
11879
|
+
}
|
11802
11880
|
}
|
11803
|
-
throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data');
|
11881
|
+
throw new Error('WebCrypto' + (enableSoftwareAES ? ' and softwareDecrypt' : '') + ': failed to decrypt data');
|
11804
11882
|
}
|
11805
11883
|
getValidChunk(data) {
|
11806
11884
|
let currentChunk = data;
|
@@ -11851,7 +11929,7 @@ const State = {
|
|
11851
11929
|
};
|
11852
11930
|
class BaseStreamController extends TaskLoop {
|
11853
11931
|
constructor(hls, fragmentTracker, keyLoader, logPrefix, playlistType) {
|
11854
|
-
super();
|
11932
|
+
super(logPrefix, hls.logger);
|
11855
11933
|
this.hls = void 0;
|
11856
11934
|
this.fragPrevious = null;
|
11857
11935
|
this.fragCurrent = null;
|
@@ -11876,22 +11954,89 @@ class BaseStreamController extends TaskLoop {
|
|
11876
11954
|
this.startFragRequested = false;
|
11877
11955
|
this.decrypter = void 0;
|
11878
11956
|
this.initPTS = [];
|
11879
|
-
this.
|
11880
|
-
this.
|
11881
|
-
|
11882
|
-
|
11883
|
-
|
11957
|
+
this.buffering = true;
|
11958
|
+
this.onMediaSeeking = () => {
|
11959
|
+
const {
|
11960
|
+
config,
|
11961
|
+
fragCurrent,
|
11962
|
+
media,
|
11963
|
+
mediaBuffer,
|
11964
|
+
state
|
11965
|
+
} = this;
|
11966
|
+
const currentTime = media ? media.currentTime : 0;
|
11967
|
+
const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
|
11968
|
+
this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
|
11969
|
+
if (this.state === State.ENDED) {
|
11970
|
+
this.resetLoadingState();
|
11971
|
+
} else if (fragCurrent) {
|
11972
|
+
// Seeking while frag load is in progress
|
11973
|
+
const tolerance = config.maxFragLookUpTolerance;
|
11974
|
+
const fragStartOffset = fragCurrent.start - tolerance;
|
11975
|
+
const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
|
11976
|
+
// if seeking out of buffered range or into new one
|
11977
|
+
if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
|
11978
|
+
const pastFragment = currentTime > fragEndOffset;
|
11979
|
+
// if the seek position is outside the current fragment range
|
11980
|
+
if (currentTime < fragStartOffset || pastFragment) {
|
11981
|
+
if (pastFragment && fragCurrent.loader) {
|
11982
|
+
this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
|
11983
|
+
fragCurrent.abortRequests();
|
11984
|
+
this.resetLoadingState();
|
11985
|
+
}
|
11986
|
+
this.fragPrevious = null;
|
11987
|
+
}
|
11988
|
+
}
|
11989
|
+
}
|
11990
|
+
if (media) {
|
11991
|
+
// Remove gap fragments
|
11992
|
+
this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
|
11993
|
+
this.lastCurrentTime = currentTime;
|
11994
|
+
}
|
11995
|
+
|
11996
|
+
// in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
|
11997
|
+
if (!this.loadedmetadata && !bufferInfo.len) {
|
11998
|
+
this.nextLoadPosition = this.startPosition = currentTime;
|
11999
|
+
}
|
12000
|
+
|
12001
|
+
// Async tick to speed up processing
|
12002
|
+
this.tickImmediate();
|
12003
|
+
};
|
12004
|
+
this.onMediaEnded = () => {
|
12005
|
+
// reset startPosition and lastCurrentTime to restart playback @ stream beginning
|
12006
|
+
this.startPosition = this.lastCurrentTime = 0;
|
12007
|
+
if (this.playlistType === PlaylistLevelType.MAIN) {
|
12008
|
+
this.hls.trigger(Events.MEDIA_ENDED, {
|
12009
|
+
stalled: false
|
12010
|
+
});
|
12011
|
+
}
|
12012
|
+
};
|
11884
12013
|
this.playlistType = playlistType;
|
11885
|
-
this.logPrefix = logPrefix;
|
11886
|
-
this.log = logger.log.bind(logger, `${logPrefix}:`);
|
11887
|
-
this.warn = logger.warn.bind(logger, `${logPrefix}:`);
|
11888
12014
|
this.hls = hls;
|
11889
12015
|
this.fragmentLoader = new FragmentLoader(hls.config);
|
11890
12016
|
this.keyLoader = keyLoader;
|
11891
12017
|
this.fragmentTracker = fragmentTracker;
|
11892
12018
|
this.config = hls.config;
|
11893
12019
|
this.decrypter = new Decrypter(hls.config);
|
12020
|
+
}
|
12021
|
+
registerListeners() {
|
12022
|
+
const {
|
12023
|
+
hls
|
12024
|
+
} = this;
|
12025
|
+
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
12026
|
+
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
12027
|
+
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
11894
12028
|
hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
12029
|
+
hls.on(Events.ERROR, this.onError, this);
|
12030
|
+
}
|
12031
|
+
unregisterListeners() {
|
12032
|
+
const {
|
12033
|
+
hls
|
12034
|
+
} = this;
|
12035
|
+
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
12036
|
+
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
12037
|
+
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
12038
|
+
hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
12039
|
+
hls.off(Events.ERROR, this.onError, this);
|
11895
12040
|
}
|
11896
12041
|
doTick() {
|
11897
12042
|
this.onTickEnd();
|
@@ -11915,6 +12060,12 @@ class BaseStreamController extends TaskLoop {
|
|
11915
12060
|
this.clearNextTick();
|
11916
12061
|
this.state = State.STOPPED;
|
11917
12062
|
}
|
12063
|
+
pauseBuffering() {
|
12064
|
+
this.buffering = false;
|
12065
|
+
}
|
12066
|
+
resumeBuffering() {
|
12067
|
+
this.buffering = true;
|
12068
|
+
}
|
11918
12069
|
_streamEnded(bufferInfo, levelDetails) {
|
11919
12070
|
// If playlist is live, there is another buffered range after the current range, nothing buffered, media is detached,
|
11920
12071
|
// of nothing loading/loaded return false
|
@@ -11945,10 +12096,8 @@ class BaseStreamController extends TaskLoop {
|
|
11945
12096
|
}
|
11946
12097
|
onMediaAttached(event, data) {
|
11947
12098
|
const media = this.media = this.mediaBuffer = data.media;
|
11948
|
-
|
11949
|
-
|
11950
|
-
media.addEventListener('seeking', this.onvseeking);
|
11951
|
-
media.addEventListener('ended', this.onvended);
|
12099
|
+
media.addEventListener('seeking', this.onMediaSeeking);
|
12100
|
+
media.addEventListener('ended', this.onMediaEnded);
|
11952
12101
|
const config = this.config;
|
11953
12102
|
if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
|
11954
12103
|
this.startLoad(config.startPosition);
|
@@ -11962,10 +12111,9 @@ class BaseStreamController extends TaskLoop {
|
|
11962
12111
|
}
|
11963
12112
|
|
11964
12113
|
// remove video listeners
|
11965
|
-
if (media
|
11966
|
-
media.removeEventListener('seeking', this.
|
11967
|
-
media.removeEventListener('ended', this.
|
11968
|
-
this.onvseeking = this.onvended = null;
|
12114
|
+
if (media) {
|
12115
|
+
media.removeEventListener('seeking', this.onMediaSeeking);
|
12116
|
+
media.removeEventListener('ended', this.onMediaEnded);
|
11969
12117
|
}
|
11970
12118
|
if (this.keyLoader) {
|
11971
12119
|
this.keyLoader.detach();
|
@@ -11975,56 +12123,8 @@ class BaseStreamController extends TaskLoop {
|
|
11975
12123
|
this.fragmentTracker.removeAllFragments();
|
11976
12124
|
this.stopLoad();
|
11977
12125
|
}
|
11978
|
-
|
11979
|
-
|
11980
|
-
config,
|
11981
|
-
fragCurrent,
|
11982
|
-
media,
|
11983
|
-
mediaBuffer,
|
11984
|
-
state
|
11985
|
-
} = this;
|
11986
|
-
const currentTime = media ? media.currentTime : 0;
|
11987
|
-
const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
|
11988
|
-
this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
|
11989
|
-
if (this.state === State.ENDED) {
|
11990
|
-
this.resetLoadingState();
|
11991
|
-
} else if (fragCurrent) {
|
11992
|
-
// Seeking while frag load is in progress
|
11993
|
-
const tolerance = config.maxFragLookUpTolerance;
|
11994
|
-
const fragStartOffset = fragCurrent.start - tolerance;
|
11995
|
-
const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
|
11996
|
-
// if seeking out of buffered range or into new one
|
11997
|
-
if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
|
11998
|
-
const pastFragment = currentTime > fragEndOffset;
|
11999
|
-
// if the seek position is outside the current fragment range
|
12000
|
-
if (currentTime < fragStartOffset || pastFragment) {
|
12001
|
-
if (pastFragment && fragCurrent.loader) {
|
12002
|
-
this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
|
12003
|
-
fragCurrent.abortRequests();
|
12004
|
-
this.resetLoadingState();
|
12005
|
-
}
|
12006
|
-
this.fragPrevious = null;
|
12007
|
-
}
|
12008
|
-
}
|
12009
|
-
}
|
12010
|
-
if (media) {
|
12011
|
-
// Remove gap fragments
|
12012
|
-
this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
|
12013
|
-
this.lastCurrentTime = currentTime;
|
12014
|
-
}
|
12015
|
-
|
12016
|
-
// in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
|
12017
|
-
if (!this.loadedmetadata && !bufferInfo.len) {
|
12018
|
-
this.nextLoadPosition = this.startPosition = currentTime;
|
12019
|
-
}
|
12020
|
-
|
12021
|
-
// Async tick to speed up processing
|
12022
|
-
this.tickImmediate();
|
12023
|
-
}
|
12024
|
-
onMediaEnded() {
|
12025
|
-
// reset startPosition and lastCurrentTime to restart playback @ stream beginning
|
12026
|
-
this.startPosition = this.lastCurrentTime = 0;
|
12027
|
-
}
|
12126
|
+
onManifestLoading() {}
|
12127
|
+
onError(event, data) {}
|
12028
12128
|
onManifestLoaded(event, data) {
|
12029
12129
|
this.startTimeOffset = data.startTimeOffset;
|
12030
12130
|
this.initPTS = [];
|
@@ -12034,7 +12134,7 @@ class BaseStreamController extends TaskLoop {
|
|
12034
12134
|
this.stopLoad();
|
12035
12135
|
super.onHandlerDestroying();
|
12036
12136
|
// @ts-ignore
|
12037
|
-
this.hls = null;
|
12137
|
+
this.hls = this.onMediaSeeking = this.onMediaEnded = null;
|
12038
12138
|
}
|
12039
12139
|
onHandlerDestroyed() {
|
12040
12140
|
this.state = State.STOPPED;
|
@@ -12165,10 +12265,10 @@ class BaseStreamController extends TaskLoop {
|
|
12165
12265
|
const decryptData = frag.decryptdata;
|
12166
12266
|
|
12167
12267
|
// check to see if the payload needs to be decrypted
|
12168
|
-
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method
|
12268
|
+
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
|
12169
12269
|
const startTime = self.performance.now();
|
12170
12270
|
// decrypt init segment data
|
12171
|
-
return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
|
12271
|
+
return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch(err => {
|
12172
12272
|
hls.trigger(Events.ERROR, {
|
12173
12273
|
type: ErrorTypes.MEDIA_ERROR,
|
12174
12274
|
details: ErrorDetails.FRAG_DECRYPT_ERROR,
|
@@ -12280,7 +12380,7 @@ class BaseStreamController extends TaskLoop {
|
|
12280
12380
|
}
|
12281
12381
|
let keyLoadingPromise = null;
|
12282
12382
|
if (frag.encrypted && !((_frag$decryptdata = frag.decryptdata) != null && _frag$decryptdata.key)) {
|
12283
|
-
this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.
|
12383
|
+
this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'} ${frag.level}`);
|
12284
12384
|
this.state = State.KEY_LOADING;
|
12285
12385
|
this.fragCurrent = frag;
|
12286
12386
|
keyLoadingPromise = this.keyLoader.load(frag).then(keyLoadedData => {
|
@@ -12311,7 +12411,7 @@ class BaseStreamController extends TaskLoop {
|
|
12311
12411
|
const partIndex = this.getNextPart(partList, frag, targetBufferTime);
|
12312
12412
|
if (partIndex > -1) {
|
12313
12413
|
const part = partList[partIndex];
|
12314
|
-
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.
|
12414
|
+
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))}`);
|
12315
12415
|
this.nextLoadPosition = part.start + part.duration;
|
12316
12416
|
this.state = State.FRAG_LOADING;
|
12317
12417
|
let _result;
|
@@ -12340,7 +12440,7 @@ class BaseStreamController extends TaskLoop {
|
|
12340
12440
|
}
|
12341
12441
|
}
|
12342
12442
|
}
|
12343
|
-
this.log(`Loading fragment ${frag.sn} cc: ${frag.cc} ${details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''}${this.
|
12443
|
+
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))}`);
|
12344
12444
|
// Don't update nextLoadPosition for fragments which are not buffered
|
12345
12445
|
if (isFiniteNumber(frag.sn) && !this.bitrateTest) {
|
12346
12446
|
this.nextLoadPosition = frag.start + frag.duration;
|
@@ -12925,7 +13025,7 @@ class BaseStreamController extends TaskLoop {
|
|
12925
13025
|
errorAction.resolved = true;
|
12926
13026
|
}
|
12927
13027
|
} else {
|
12928
|
-
|
13028
|
+
this.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
|
12929
13029
|
return;
|
12930
13030
|
}
|
12931
13031
|
} else if ((errorAction == null ? void 0 : errorAction.action) === NetworkErrorAction.SendAlternateToPenaltyBox) {
|
@@ -13320,6 +13420,7 @@ const initPTSFn = (timestamp, timeOffset, initPTS) => {
|
|
13320
13420
|
*/
|
13321
13421
|
function getAudioConfig(observer, data, offset, audioCodec) {
|
13322
13422
|
let adtsObjectType;
|
13423
|
+
let originalAdtsObjectType;
|
13323
13424
|
let adtsExtensionSamplingIndex;
|
13324
13425
|
let adtsChannelConfig;
|
13325
13426
|
let config;
|
@@ -13327,7 +13428,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
13327
13428
|
const manifestCodec = audioCodec;
|
13328
13429
|
const adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
|
13329
13430
|
// byte 2
|
13330
|
-
adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
|
13431
|
+
adtsObjectType = originalAdtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
|
13331
13432
|
const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
|
13332
13433
|
if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
|
13333
13434
|
const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
|
@@ -13344,8 +13445,8 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
13344
13445
|
// byte 3
|
13345
13446
|
adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6;
|
13346
13447
|
logger.log(`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`);
|
13347
|
-
//
|
13348
|
-
if (/firefox/i.test(userAgent)) {
|
13448
|
+
// Firefox and Pale Moon: freq less than 24kHz = AAC SBR (HE-AAC)
|
13449
|
+
if (/firefox|palemoon/i.test(userAgent)) {
|
13349
13450
|
if (adtsSamplingIndex >= 6) {
|
13350
13451
|
adtsObjectType = 5;
|
13351
13452
|
config = new Array(4);
|
@@ -13439,6 +13540,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
13439
13540
|
samplerate: adtsSamplingRates[adtsSamplingIndex],
|
13440
13541
|
channelCount: adtsChannelConfig,
|
13441
13542
|
codec: 'mp4a.40.' + adtsObjectType,
|
13543
|
+
parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
|
13442
13544
|
manifestCodec
|
13443
13545
|
};
|
13444
13546
|
}
|
@@ -13493,7 +13595,8 @@ function initTrackConfig(track, observer, data, offset, audioCodec) {
|
|
13493
13595
|
track.channelCount = config.channelCount;
|
13494
13596
|
track.codec = config.codec;
|
13495
13597
|
track.manifestCodec = config.manifestCodec;
|
13496
|
-
|
13598
|
+
track.parsedCodec = config.parsedCodec;
|
13599
|
+
logger.log(`parsed codec:${track.parsedCodec}, codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
|
13497
13600
|
}
|
13498
13601
|
}
|
13499
13602
|
function getFrameDuration(samplerate) {
|
@@ -13971,6 +14074,110 @@ class BaseVideoParser {
|
|
13971
14074
|
logger.log(VideoSample.pts + '/' + VideoSample.dts + ':' + VideoSample.debug);
|
13972
14075
|
}
|
13973
14076
|
}
|
14077
|
+
parseNALu(track, array) {
|
14078
|
+
const len = array.byteLength;
|
14079
|
+
let state = track.naluState || 0;
|
14080
|
+
const lastState = state;
|
14081
|
+
const units = [];
|
14082
|
+
let i = 0;
|
14083
|
+
let value;
|
14084
|
+
let overflow;
|
14085
|
+
let unitType;
|
14086
|
+
let lastUnitStart = -1;
|
14087
|
+
let lastUnitType = 0;
|
14088
|
+
// logger.log('PES:' + Hex.hexDump(array));
|
14089
|
+
|
14090
|
+
if (state === -1) {
|
14091
|
+
// special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
|
14092
|
+
lastUnitStart = 0;
|
14093
|
+
// NALu type is value read from offset 0
|
14094
|
+
lastUnitType = this.getNALuType(array, 0);
|
14095
|
+
state = 0;
|
14096
|
+
i = 1;
|
14097
|
+
}
|
14098
|
+
while (i < len) {
|
14099
|
+
value = array[i++];
|
14100
|
+
// optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
|
14101
|
+
if (!state) {
|
14102
|
+
state = value ? 0 : 1;
|
14103
|
+
continue;
|
14104
|
+
}
|
14105
|
+
if (state === 1) {
|
14106
|
+
state = value ? 0 : 2;
|
14107
|
+
continue;
|
14108
|
+
}
|
14109
|
+
// here we have state either equal to 2 or 3
|
14110
|
+
if (!value) {
|
14111
|
+
state = 3;
|
14112
|
+
} else if (value === 1) {
|
14113
|
+
overflow = i - state - 1;
|
14114
|
+
if (lastUnitStart >= 0) {
|
14115
|
+
const unit = {
|
14116
|
+
data: array.subarray(lastUnitStart, overflow),
|
14117
|
+
type: lastUnitType
|
14118
|
+
};
|
14119
|
+
// logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
|
14120
|
+
units.push(unit);
|
14121
|
+
} else {
|
14122
|
+
// lastUnitStart is undefined => this is the first start code found in this PES packet
|
14123
|
+
// first check if start code delimiter is overlapping between 2 PES packets,
|
14124
|
+
// ie it started in last packet (lastState not zero)
|
14125
|
+
// and ended at the beginning of this PES packet (i <= 4 - lastState)
|
14126
|
+
const lastUnit = this.getLastNalUnit(track.samples);
|
14127
|
+
if (lastUnit) {
|
14128
|
+
if (lastState && i <= 4 - lastState) {
|
14129
|
+
// start delimiter overlapping between PES packets
|
14130
|
+
// strip start delimiter bytes from the end of last NAL unit
|
14131
|
+
// check if lastUnit had a state different from zero
|
14132
|
+
if (lastUnit.state) {
|
14133
|
+
// strip last bytes
|
14134
|
+
lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
|
14135
|
+
}
|
14136
|
+
}
|
14137
|
+
// If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
|
14138
|
+
|
14139
|
+
if (overflow > 0) {
|
14140
|
+
// logger.log('first NALU found with overflow:' + overflow);
|
14141
|
+
lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
|
14142
|
+
lastUnit.state = 0;
|
14143
|
+
}
|
14144
|
+
}
|
14145
|
+
}
|
14146
|
+
// check if we can read unit type
|
14147
|
+
if (i < len) {
|
14148
|
+
unitType = this.getNALuType(array, i);
|
14149
|
+
// logger.log('find NALU @ offset:' + i + ',type:' + unitType);
|
14150
|
+
lastUnitStart = i;
|
14151
|
+
lastUnitType = unitType;
|
14152
|
+
state = 0;
|
14153
|
+
} else {
|
14154
|
+
// not enough byte to read unit type. let's read it on next PES parsing
|
14155
|
+
state = -1;
|
14156
|
+
}
|
14157
|
+
} else {
|
14158
|
+
state = 0;
|
14159
|
+
}
|
14160
|
+
}
|
14161
|
+
if (lastUnitStart >= 0 && state >= 0) {
|
14162
|
+
const unit = {
|
14163
|
+
data: array.subarray(lastUnitStart, len),
|
14164
|
+
type: lastUnitType,
|
14165
|
+
state: state
|
14166
|
+
};
|
14167
|
+
units.push(unit);
|
14168
|
+
// logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
|
14169
|
+
}
|
14170
|
+
// no NALu found
|
14171
|
+
if (units.length === 0) {
|
14172
|
+
// append pes.data to previous NAL unit
|
14173
|
+
const lastUnit = this.getLastNalUnit(track.samples);
|
14174
|
+
if (lastUnit) {
|
14175
|
+
lastUnit.data = appendUint8Array(lastUnit.data, array);
|
14176
|
+
}
|
14177
|
+
}
|
14178
|
+
track.naluState = state;
|
14179
|
+
return units;
|
14180
|
+
}
|
13974
14181
|
}
|
13975
14182
|
|
13976
14183
|
/**
|
@@ -14113,21 +14320,171 @@ class ExpGolomb {
|
|
14113
14320
|
readUInt() {
|
14114
14321
|
return this.readBits(32);
|
14115
14322
|
}
|
14323
|
+
}
|
14324
|
+
|
14325
|
+
class AvcVideoParser extends BaseVideoParser {
|
14326
|
+
parsePES(track, textTrack, pes, last, duration) {
|
14327
|
+
const units = this.parseNALu(track, pes.data);
|
14328
|
+
let VideoSample = this.VideoSample;
|
14329
|
+
let push;
|
14330
|
+
let spsfound = false;
|
14331
|
+
// free pes.data to save up some memory
|
14332
|
+
pes.data = null;
|
14333
|
+
|
14334
|
+
// if new NAL units found and last sample still there, let's push ...
|
14335
|
+
// this helps parsing streams with missing AUD (only do this if AUD never found)
|
14336
|
+
if (VideoSample && units.length && !track.audFound) {
|
14337
|
+
this.pushAccessUnit(VideoSample, track);
|
14338
|
+
VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
|
14339
|
+
}
|
14340
|
+
units.forEach(unit => {
|
14341
|
+
var _VideoSample2;
|
14342
|
+
switch (unit.type) {
|
14343
|
+
// NDR
|
14344
|
+
case 1:
|
14345
|
+
{
|
14346
|
+
let iskey = false;
|
14347
|
+
push = true;
|
14348
|
+
const data = unit.data;
|
14349
|
+
// only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
|
14350
|
+
if (spsfound && data.length > 4) {
|
14351
|
+
// retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
|
14352
|
+
const sliceType = this.readSliceType(data);
|
14353
|
+
// 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
|
14354
|
+
// SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
|
14355
|
+
// An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
|
14356
|
+
// I slice: A slice that is not an SI slice that is decoded using intra prediction only.
|
14357
|
+
// if (sliceType === 2 || sliceType === 7) {
|
14358
|
+
if (sliceType === 2 || sliceType === 4 || sliceType === 7 || sliceType === 9) {
|
14359
|
+
iskey = true;
|
14360
|
+
}
|
14361
|
+
}
|
14362
|
+
if (iskey) {
|
14363
|
+
var _VideoSample;
|
14364
|
+
// if we have non-keyframe data already, that cannot belong to the same frame as a keyframe, so force a push
|
14365
|
+
if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
|
14366
|
+
this.pushAccessUnit(VideoSample, track);
|
14367
|
+
VideoSample = this.VideoSample = null;
|
14368
|
+
}
|
14369
|
+
}
|
14370
|
+
if (!VideoSample) {
|
14371
|
+
VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
|
14372
|
+
}
|
14373
|
+
VideoSample.frame = true;
|
14374
|
+
VideoSample.key = iskey;
|
14375
|
+
break;
|
14376
|
+
// IDR
|
14377
|
+
}
|
14378
|
+
case 5:
|
14379
|
+
push = true;
|
14380
|
+
// handle PES not starting with AUD
|
14381
|
+
// if we have frame data already, that cannot belong to the same frame, so force a push
|
14382
|
+
if ((_VideoSample2 = VideoSample) != null && _VideoSample2.frame && !VideoSample.key) {
|
14383
|
+
this.pushAccessUnit(VideoSample, track);
|
14384
|
+
VideoSample = this.VideoSample = null;
|
14385
|
+
}
|
14386
|
+
if (!VideoSample) {
|
14387
|
+
VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
|
14388
|
+
}
|
14389
|
+
VideoSample.key = true;
|
14390
|
+
VideoSample.frame = true;
|
14391
|
+
break;
|
14392
|
+
// SEI
|
14393
|
+
case 6:
|
14394
|
+
{
|
14395
|
+
push = true;
|
14396
|
+
parseSEIMessageFromNALu(unit.data, 1, pes.pts, textTrack.samples);
|
14397
|
+
break;
|
14398
|
+
// SPS
|
14399
|
+
}
|
14400
|
+
case 7:
|
14401
|
+
{
|
14402
|
+
var _track$pixelRatio, _track$pixelRatio2;
|
14403
|
+
push = true;
|
14404
|
+
spsfound = true;
|
14405
|
+
const sps = unit.data;
|
14406
|
+
const config = this.readSPS(sps);
|
14407
|
+
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]) {
|
14408
|
+
track.width = config.width;
|
14409
|
+
track.height = config.height;
|
14410
|
+
track.pixelRatio = config.pixelRatio;
|
14411
|
+
track.sps = [sps];
|
14412
|
+
track.duration = duration;
|
14413
|
+
const codecarray = sps.subarray(1, 4);
|
14414
|
+
let codecstring = 'avc1.';
|
14415
|
+
for (let i = 0; i < 3; i++) {
|
14416
|
+
let h = codecarray[i].toString(16);
|
14417
|
+
if (h.length < 2) {
|
14418
|
+
h = '0' + h;
|
14419
|
+
}
|
14420
|
+
codecstring += h;
|
14421
|
+
}
|
14422
|
+
track.codec = codecstring;
|
14423
|
+
}
|
14424
|
+
break;
|
14425
|
+
}
|
14426
|
+
// PPS
|
14427
|
+
case 8:
|
14428
|
+
push = true;
|
14429
|
+
track.pps = [unit.data];
|
14430
|
+
break;
|
14431
|
+
// AUD
|
14432
|
+
case 9:
|
14433
|
+
push = true;
|
14434
|
+
track.audFound = true;
|
14435
|
+
if (VideoSample) {
|
14436
|
+
this.pushAccessUnit(VideoSample, track);
|
14437
|
+
}
|
14438
|
+
VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
|
14439
|
+
break;
|
14440
|
+
// Filler Data
|
14441
|
+
case 12:
|
14442
|
+
push = true;
|
14443
|
+
break;
|
14444
|
+
default:
|
14445
|
+
push = false;
|
14446
|
+
if (VideoSample) {
|
14447
|
+
VideoSample.debug += 'unknown NAL ' + unit.type + ' ';
|
14448
|
+
}
|
14449
|
+
break;
|
14450
|
+
}
|
14451
|
+
if (VideoSample && push) {
|
14452
|
+
const units = VideoSample.units;
|
14453
|
+
units.push(unit);
|
14454
|
+
}
|
14455
|
+
});
|
14456
|
+
// if last PES packet, push samples
|
14457
|
+
if (last && VideoSample) {
|
14458
|
+
this.pushAccessUnit(VideoSample, track);
|
14459
|
+
this.VideoSample = null;
|
14460
|
+
}
|
14461
|
+
}
|
14462
|
+
getNALuType(data, offset) {
|
14463
|
+
return data[offset] & 0x1f;
|
14464
|
+
}
|
14465
|
+
readSliceType(data) {
|
14466
|
+
const eg = new ExpGolomb(data);
|
14467
|
+
// skip NALu type
|
14468
|
+
eg.readUByte();
|
14469
|
+
// discard first_mb_in_slice
|
14470
|
+
eg.readUEG();
|
14471
|
+
// return slice_type
|
14472
|
+
return eg.readUEG();
|
14473
|
+
}
|
14116
14474
|
|
14117
14475
|
/**
|
14118
|
-
*
|
14119
|
-
* list is optionally transmitted as part of a sequence parameter
|
14476
|
+
* The scaling list is optionally transmitted as part of a sequence parameter
|
14120
14477
|
* set and is not relevant to transmuxing.
|
14121
14478
|
* @param count the number of entries in this scaling list
|
14122
14479
|
* @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
|
14123
14480
|
*/
|
14124
|
-
skipScalingList(count) {
|
14481
|
+
skipScalingList(count, reader) {
|
14125
14482
|
let lastScale = 8;
|
14126
14483
|
let nextScale = 8;
|
14127
14484
|
let deltaScale;
|
14128
14485
|
for (let j = 0; j < count; j++) {
|
14129
14486
|
if (nextScale !== 0) {
|
14130
|
-
deltaScale =
|
14487
|
+
deltaScale = reader.readEG();
|
14131
14488
|
nextScale = (lastScale + deltaScale + 256) % 256;
|
14132
14489
|
}
|
14133
14490
|
lastScale = nextScale === 0 ? lastScale : nextScale;
|
@@ -14142,7 +14499,8 @@ class ExpGolomb {
|
|
14142
14499
|
* sequence parameter set, including the dimensions of the
|
14143
14500
|
* associated video frames.
|
14144
14501
|
*/
|
14145
|
-
readSPS() {
|
14502
|
+
readSPS(sps) {
|
14503
|
+
const eg = new ExpGolomb(sps);
|
14146
14504
|
let frameCropLeftOffset = 0;
|
14147
14505
|
let frameCropRightOffset = 0;
|
14148
14506
|
let frameCropTopOffset = 0;
|
@@ -14150,13 +14508,13 @@ class ExpGolomb {
|
|
14150
14508
|
let numRefFramesInPicOrderCntCycle;
|
14151
14509
|
let scalingListCount;
|
14152
14510
|
let i;
|
14153
|
-
const readUByte =
|
14154
|
-
const readBits =
|
14155
|
-
const readUEG =
|
14156
|
-
const readBoolean =
|
14157
|
-
const skipBits =
|
14158
|
-
const skipEG =
|
14159
|
-
const skipUEG =
|
14511
|
+
const readUByte = eg.readUByte.bind(eg);
|
14512
|
+
const readBits = eg.readBits.bind(eg);
|
14513
|
+
const readUEG = eg.readUEG.bind(eg);
|
14514
|
+
const readBoolean = eg.readBoolean.bind(eg);
|
14515
|
+
const skipBits = eg.skipBits.bind(eg);
|
14516
|
+
const skipEG = eg.skipEG.bind(eg);
|
14517
|
+
const skipUEG = eg.skipUEG.bind(eg);
|
14160
14518
|
const skipScalingList = this.skipScalingList.bind(this);
|
14161
14519
|
readUByte();
|
14162
14520
|
const profileIdc = readUByte(); // profile_idc
|
@@ -14181,9 +14539,9 @@ class ExpGolomb {
|
|
14181
14539
|
if (readBoolean()) {
|
14182
14540
|
// seq_scaling_list_present_flag[ i ]
|
14183
14541
|
if (i < 6) {
|
14184
|
-
skipScalingList(16);
|
14542
|
+
skipScalingList(16, eg);
|
14185
14543
|
} else {
|
14186
|
-
skipScalingList(64);
|
14544
|
+
skipScalingList(64, eg);
|
14187
14545
|
}
|
14188
14546
|
}
|
14189
14547
|
}
|
@@ -14288,258 +14646,6 @@ class ExpGolomb {
|
|
14288
14646
|
pixelRatio: pixelRatio
|
14289
14647
|
};
|
14290
14648
|
}
|
14291
|
-
readSliceType() {
|
14292
|
-
// skip NALu type
|
14293
|
-
this.readUByte();
|
14294
|
-
// discard first_mb_in_slice
|
14295
|
-
this.readUEG();
|
14296
|
-
// return slice_type
|
14297
|
-
return this.readUEG();
|
14298
|
-
}
|
14299
|
-
}
|
14300
|
-
|
14301
|
-
class AvcVideoParser extends BaseVideoParser {
|
14302
|
-
parseAVCPES(track, textTrack, pes, last, duration) {
|
14303
|
-
const units = this.parseAVCNALu(track, pes.data);
|
14304
|
-
let VideoSample = this.VideoSample;
|
14305
|
-
let push;
|
14306
|
-
let spsfound = false;
|
14307
|
-
// free pes.data to save up some memory
|
14308
|
-
pes.data = null;
|
14309
|
-
|
14310
|
-
// if new NAL units found and last sample still there, let's push ...
|
14311
|
-
// this helps parsing streams with missing AUD (only do this if AUD never found)
|
14312
|
-
if (VideoSample && units.length && !track.audFound) {
|
14313
|
-
this.pushAccessUnit(VideoSample, track);
|
14314
|
-
VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
|
14315
|
-
}
|
14316
|
-
units.forEach(unit => {
|
14317
|
-
var _VideoSample2;
|
14318
|
-
switch (unit.type) {
|
14319
|
-
// NDR
|
14320
|
-
case 1:
|
14321
|
-
{
|
14322
|
-
let iskey = false;
|
14323
|
-
push = true;
|
14324
|
-
const data = unit.data;
|
14325
|
-
// only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
|
14326
|
-
if (spsfound && data.length > 4) {
|
14327
|
-
// retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
|
14328
|
-
const sliceType = new ExpGolomb(data).readSliceType();
|
14329
|
-
// 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
|
14330
|
-
// SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
|
14331
|
-
// An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
|
14332
|
-
// I slice: A slice that is not an SI slice that is decoded using intra prediction only.
|
14333
|
-
// if (sliceType === 2 || sliceType === 7) {
|
14334
|
-
if (sliceType === 2 || sliceType === 4 || sliceType === 7 || sliceType === 9) {
|
14335
|
-
iskey = true;
|
14336
|
-
}
|
14337
|
-
}
|
14338
|
-
if (iskey) {
|
14339
|
-
var _VideoSample;
|
14340
|
-
// if we have non-keyframe data already, that cannot belong to the same frame as a keyframe, so force a push
|
14341
|
-
if ((_VideoSample = VideoSample) != null && _VideoSample.frame && !VideoSample.key) {
|
14342
|
-
this.pushAccessUnit(VideoSample, track);
|
14343
|
-
VideoSample = this.VideoSample = null;
|
14344
|
-
}
|
14345
|
-
}
|
14346
|
-
if (!VideoSample) {
|
14347
|
-
VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
|
14348
|
-
}
|
14349
|
-
VideoSample.frame = true;
|
14350
|
-
VideoSample.key = iskey;
|
14351
|
-
break;
|
14352
|
-
// IDR
|
14353
|
-
}
|
14354
|
-
case 5:
|
14355
|
-
push = true;
|
14356
|
-
// handle PES not starting with AUD
|
14357
|
-
// if we have frame data already, that cannot belong to the same frame, so force a push
|
14358
|
-
if ((_VideoSample2 = VideoSample) != null && _VideoSample2.frame && !VideoSample.key) {
|
14359
|
-
this.pushAccessUnit(VideoSample, track);
|
14360
|
-
VideoSample = this.VideoSample = null;
|
14361
|
-
}
|
14362
|
-
if (!VideoSample) {
|
14363
|
-
VideoSample = this.VideoSample = this.createVideoSample(true, pes.pts, pes.dts, '');
|
14364
|
-
}
|
14365
|
-
VideoSample.key = true;
|
14366
|
-
VideoSample.frame = true;
|
14367
|
-
break;
|
14368
|
-
// SEI
|
14369
|
-
case 6:
|
14370
|
-
{
|
14371
|
-
push = true;
|
14372
|
-
parseSEIMessageFromNALu(unit.data, 1, pes.pts, textTrack.samples);
|
14373
|
-
break;
|
14374
|
-
// SPS
|
14375
|
-
}
|
14376
|
-
case 7:
|
14377
|
-
{
|
14378
|
-
var _track$pixelRatio, _track$pixelRatio2;
|
14379
|
-
push = true;
|
14380
|
-
spsfound = true;
|
14381
|
-
const sps = unit.data;
|
14382
|
-
const expGolombDecoder = new ExpGolomb(sps);
|
14383
|
-
const config = expGolombDecoder.readSPS();
|
14384
|
-
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]) {
|
14385
|
-
track.width = config.width;
|
14386
|
-
track.height = config.height;
|
14387
|
-
track.pixelRatio = config.pixelRatio;
|
14388
|
-
track.sps = [sps];
|
14389
|
-
track.duration = duration;
|
14390
|
-
const codecarray = sps.subarray(1, 4);
|
14391
|
-
let codecstring = 'avc1.';
|
14392
|
-
for (let i = 0; i < 3; i++) {
|
14393
|
-
let h = codecarray[i].toString(16);
|
14394
|
-
if (h.length < 2) {
|
14395
|
-
h = '0' + h;
|
14396
|
-
}
|
14397
|
-
codecstring += h;
|
14398
|
-
}
|
14399
|
-
track.codec = codecstring;
|
14400
|
-
}
|
14401
|
-
break;
|
14402
|
-
}
|
14403
|
-
// PPS
|
14404
|
-
case 8:
|
14405
|
-
push = true;
|
14406
|
-
track.pps = [unit.data];
|
14407
|
-
break;
|
14408
|
-
// AUD
|
14409
|
-
case 9:
|
14410
|
-
push = true;
|
14411
|
-
track.audFound = true;
|
14412
|
-
if (VideoSample) {
|
14413
|
-
this.pushAccessUnit(VideoSample, track);
|
14414
|
-
}
|
14415
|
-
VideoSample = this.VideoSample = this.createVideoSample(false, pes.pts, pes.dts, '');
|
14416
|
-
break;
|
14417
|
-
// Filler Data
|
14418
|
-
case 12:
|
14419
|
-
push = true;
|
14420
|
-
break;
|
14421
|
-
default:
|
14422
|
-
push = false;
|
14423
|
-
if (VideoSample) {
|
14424
|
-
VideoSample.debug += 'unknown NAL ' + unit.type + ' ';
|
14425
|
-
}
|
14426
|
-
break;
|
14427
|
-
}
|
14428
|
-
if (VideoSample && push) {
|
14429
|
-
const units = VideoSample.units;
|
14430
|
-
units.push(unit);
|
14431
|
-
}
|
14432
|
-
});
|
14433
|
-
// if last PES packet, push samples
|
14434
|
-
if (last && VideoSample) {
|
14435
|
-
this.pushAccessUnit(VideoSample, track);
|
14436
|
-
this.VideoSample = null;
|
14437
|
-
}
|
14438
|
-
}
|
14439
|
-
parseAVCNALu(track, array) {
|
14440
|
-
const len = array.byteLength;
|
14441
|
-
let state = track.naluState || 0;
|
14442
|
-
const lastState = state;
|
14443
|
-
const units = [];
|
14444
|
-
let i = 0;
|
14445
|
-
let value;
|
14446
|
-
let overflow;
|
14447
|
-
let unitType;
|
14448
|
-
let lastUnitStart = -1;
|
14449
|
-
let lastUnitType = 0;
|
14450
|
-
// logger.log('PES:' + Hex.hexDump(array));
|
14451
|
-
|
14452
|
-
if (state === -1) {
|
14453
|
-
// special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
|
14454
|
-
lastUnitStart = 0;
|
14455
|
-
// NALu type is value read from offset 0
|
14456
|
-
lastUnitType = array[0] & 0x1f;
|
14457
|
-
state = 0;
|
14458
|
-
i = 1;
|
14459
|
-
}
|
14460
|
-
while (i < len) {
|
14461
|
-
value = array[i++];
|
14462
|
-
// optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
|
14463
|
-
if (!state) {
|
14464
|
-
state = value ? 0 : 1;
|
14465
|
-
continue;
|
14466
|
-
}
|
14467
|
-
if (state === 1) {
|
14468
|
-
state = value ? 0 : 2;
|
14469
|
-
continue;
|
14470
|
-
}
|
14471
|
-
// here we have state either equal to 2 or 3
|
14472
|
-
if (!value) {
|
14473
|
-
state = 3;
|
14474
|
-
} else if (value === 1) {
|
14475
|
-
overflow = i - state - 1;
|
14476
|
-
if (lastUnitStart >= 0) {
|
14477
|
-
const unit = {
|
14478
|
-
data: array.subarray(lastUnitStart, overflow),
|
14479
|
-
type: lastUnitType
|
14480
|
-
};
|
14481
|
-
// logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
|
14482
|
-
units.push(unit);
|
14483
|
-
} else {
|
14484
|
-
// lastUnitStart is undefined => this is the first start code found in this PES packet
|
14485
|
-
// first check if start code delimiter is overlapping between 2 PES packets,
|
14486
|
-
// ie it started in last packet (lastState not zero)
|
14487
|
-
// and ended at the beginning of this PES packet (i <= 4 - lastState)
|
14488
|
-
const lastUnit = this.getLastNalUnit(track.samples);
|
14489
|
-
if (lastUnit) {
|
14490
|
-
if (lastState && i <= 4 - lastState) {
|
14491
|
-
// start delimiter overlapping between PES packets
|
14492
|
-
// strip start delimiter bytes from the end of last NAL unit
|
14493
|
-
// check if lastUnit had a state different from zero
|
14494
|
-
if (lastUnit.state) {
|
14495
|
-
// strip last bytes
|
14496
|
-
lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
|
14497
|
-
}
|
14498
|
-
}
|
14499
|
-
// If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
|
14500
|
-
|
14501
|
-
if (overflow > 0) {
|
14502
|
-
// logger.log('first NALU found with overflow:' + overflow);
|
14503
|
-
lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
|
14504
|
-
lastUnit.state = 0;
|
14505
|
-
}
|
14506
|
-
}
|
14507
|
-
}
|
14508
|
-
// check if we can read unit type
|
14509
|
-
if (i < len) {
|
14510
|
-
unitType = array[i] & 0x1f;
|
14511
|
-
// logger.log('find NALU @ offset:' + i + ',type:' + unitType);
|
14512
|
-
lastUnitStart = i;
|
14513
|
-
lastUnitType = unitType;
|
14514
|
-
state = 0;
|
14515
|
-
} else {
|
14516
|
-
// not enough byte to read unit type. let's read it on next PES parsing
|
14517
|
-
state = -1;
|
14518
|
-
}
|
14519
|
-
} else {
|
14520
|
-
state = 0;
|
14521
|
-
}
|
14522
|
-
}
|
14523
|
-
if (lastUnitStart >= 0 && state >= 0) {
|
14524
|
-
const unit = {
|
14525
|
-
data: array.subarray(lastUnitStart, len),
|
14526
|
-
type: lastUnitType,
|
14527
|
-
state: state
|
14528
|
-
};
|
14529
|
-
units.push(unit);
|
14530
|
-
// logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
|
14531
|
-
}
|
14532
|
-
// no NALu found
|
14533
|
-
if (units.length === 0) {
|
14534
|
-
// append pes.data to previous NAL unit
|
14535
|
-
const lastUnit = this.getLastNalUnit(track.samples);
|
14536
|
-
if (lastUnit) {
|
14537
|
-
lastUnit.data = appendUint8Array(lastUnit.data, array);
|
14538
|
-
}
|
14539
|
-
}
|
14540
|
-
track.naluState = state;
|
14541
|
-
return units;
|
14542
|
-
}
|
14543
14649
|
}
|
14544
14650
|
|
14545
14651
|
/**
|
@@ -14556,7 +14662,7 @@ class SampleAesDecrypter {
|
|
14556
14662
|
});
|
14557
14663
|
}
|
14558
14664
|
decryptBuffer(encryptedData) {
|
14559
|
-
return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer);
|
14665
|
+
return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer, DecrypterAesMode.cbc);
|
14560
14666
|
}
|
14561
14667
|
|
14562
14668
|
// AAC - encrypt all full 16 bytes blocks starting from offset 16
|
@@ -14670,7 +14776,7 @@ class TSDemuxer {
|
|
14670
14776
|
this.observer = observer;
|
14671
14777
|
this.config = config;
|
14672
14778
|
this.typeSupported = typeSupported;
|
14673
|
-
this.videoParser =
|
14779
|
+
this.videoParser = null;
|
14674
14780
|
}
|
14675
14781
|
static probe(data) {
|
14676
14782
|
const syncOffset = TSDemuxer.syncOffset(data);
|
@@ -14835,7 +14941,16 @@ class TSDemuxer {
|
|
14835
14941
|
case videoPid:
|
14836
14942
|
if (stt) {
|
14837
14943
|
if (videoData && (pes = parsePES(videoData))) {
|
14838
|
-
this.videoParser
|
14944
|
+
if (this.videoParser === null) {
|
14945
|
+
switch (videoTrack.segmentCodec) {
|
14946
|
+
case 'avc':
|
14947
|
+
this.videoParser = new AvcVideoParser();
|
14948
|
+
break;
|
14949
|
+
}
|
14950
|
+
}
|
14951
|
+
if (this.videoParser !== null) {
|
14952
|
+
this.videoParser.parsePES(videoTrack, textTrack, pes, false, this._duration);
|
14953
|
+
}
|
14839
14954
|
}
|
14840
14955
|
videoData = {
|
14841
14956
|
data: [],
|
@@ -14997,8 +15112,17 @@ class TSDemuxer {
|
|
14997
15112
|
// try to parse last PES packets
|
14998
15113
|
let pes;
|
14999
15114
|
if (videoData && (pes = parsePES(videoData))) {
|
15000
|
-
this.videoParser
|
15001
|
-
|
15115
|
+
if (this.videoParser === null) {
|
15116
|
+
switch (videoTrack.segmentCodec) {
|
15117
|
+
case 'avc':
|
15118
|
+
this.videoParser = new AvcVideoParser();
|
15119
|
+
break;
|
15120
|
+
}
|
15121
|
+
}
|
15122
|
+
if (this.videoParser !== null) {
|
15123
|
+
this.videoParser.parsePES(videoTrack, textTrack, pes, true, this._duration);
|
15124
|
+
videoTrack.pesData = null;
|
15125
|
+
}
|
15002
15126
|
} else {
|
15003
15127
|
// either avcData null or PES truncated, keep it for next frag parsing
|
15004
15128
|
videoTrack.pesData = videoData;
|
@@ -15301,7 +15425,10 @@ function parsePMT(data, offset, typeSupported, isSampleAes) {
|
|
15301
15425
|
logger.warn('Unsupported EC-3 in M2TS found');
|
15302
15426
|
break;
|
15303
15427
|
case 0x24:
|
15304
|
-
|
15428
|
+
// ITU-T Rec. H.265 and ISO/IEC 23008-2 (HEVC)
|
15429
|
+
{
|
15430
|
+
logger.warn('Unsupported HEVC in M2TS found');
|
15431
|
+
}
|
15305
15432
|
break;
|
15306
15433
|
}
|
15307
15434
|
// move to the next table entry
|
@@ -15524,6 +15651,8 @@ class MP4 {
|
|
15524
15651
|
avc1: [],
|
15525
15652
|
// codingname
|
15526
15653
|
avcC: [],
|
15654
|
+
hvc1: [],
|
15655
|
+
hvcC: [],
|
15527
15656
|
btrt: [],
|
15528
15657
|
dinf: [],
|
15529
15658
|
dref: [],
|
@@ -15948,8 +16077,10 @@ class MP4 {
|
|
15948
16077
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.ac3(track));
|
15949
16078
|
}
|
15950
16079
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track));
|
15951
|
-
} else {
|
16080
|
+
} else if (track.segmentCodec === 'avc') {
|
15952
16081
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
|
16082
|
+
} else {
|
16083
|
+
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.hvc1(track));
|
15953
16084
|
}
|
15954
16085
|
}
|
15955
16086
|
static tkhd(track) {
|
@@ -16087,6 +16218,84 @@ class MP4 {
|
|
16087
16218
|
const result = appendUint8Array(MP4.FTYP, movie);
|
16088
16219
|
return result;
|
16089
16220
|
}
|
16221
|
+
static hvc1(track) {
|
16222
|
+
const ps = track.params;
|
16223
|
+
const units = [track.vps, track.sps, track.pps];
|
16224
|
+
const NALuLengthSize = 4;
|
16225
|
+
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]);
|
16226
|
+
|
16227
|
+
// compute hvcC size in bytes
|
16228
|
+
let length = config.length;
|
16229
|
+
for (let i = 0; i < units.length; i += 1) {
|
16230
|
+
length += 3;
|
16231
|
+
for (let j = 0; j < units[i].length; j += 1) {
|
16232
|
+
length += 2 + units[i][j].length;
|
16233
|
+
}
|
16234
|
+
}
|
16235
|
+
const hvcC = new Uint8Array(length);
|
16236
|
+
hvcC.set(config, 0);
|
16237
|
+
length = config.length;
|
16238
|
+
// append parameter set units: one vps, one or more sps and pps
|
16239
|
+
const iMax = units.length - 1;
|
16240
|
+
for (let i = 0; i < units.length; i += 1) {
|
16241
|
+
hvcC.set(new Uint8Array([32 + i | (i === iMax ? 128 : 0), 0x00, units[i].length]), length);
|
16242
|
+
length += 3;
|
16243
|
+
for (let j = 0; j < units[i].length; j += 1) {
|
16244
|
+
hvcC.set(new Uint8Array([units[i][j].length >> 8, units[i][j].length & 255]), length);
|
16245
|
+
length += 2;
|
16246
|
+
hvcC.set(units[i][j], length);
|
16247
|
+
length += units[i][j].length;
|
16248
|
+
}
|
16249
|
+
}
|
16250
|
+
const hvcc = MP4.box(MP4.types.hvcC, hvcC);
|
16251
|
+
const width = track.width;
|
16252
|
+
const height = track.height;
|
16253
|
+
const hSpacing = track.pixelRatio[0];
|
16254
|
+
const vSpacing = track.pixelRatio[1];
|
16255
|
+
return MP4.box(MP4.types.hvc1, new Uint8Array([0x00, 0x00, 0x00,
|
16256
|
+
// reserved
|
16257
|
+
0x00, 0x00, 0x00,
|
16258
|
+
// reserved
|
16259
|
+
0x00, 0x01,
|
16260
|
+
// data_reference_index
|
16261
|
+
0x00, 0x00,
|
16262
|
+
// pre_defined
|
16263
|
+
0x00, 0x00,
|
16264
|
+
// reserved
|
16265
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
16266
|
+
// pre_defined
|
16267
|
+
width >> 8 & 0xff, width & 0xff,
|
16268
|
+
// width
|
16269
|
+
height >> 8 & 0xff, height & 0xff,
|
16270
|
+
// height
|
16271
|
+
0x00, 0x48, 0x00, 0x00,
|
16272
|
+
// horizresolution
|
16273
|
+
0x00, 0x48, 0x00, 0x00,
|
16274
|
+
// vertresolution
|
16275
|
+
0x00, 0x00, 0x00, 0x00,
|
16276
|
+
// reserved
|
16277
|
+
0x00, 0x01,
|
16278
|
+
// frame_count
|
16279
|
+
0x12, 0x64, 0x61, 0x69, 0x6c,
|
16280
|
+
// dailymotion/hls.js
|
16281
|
+
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,
|
16282
|
+
// compressorname
|
16283
|
+
0x00, 0x18,
|
16284
|
+
// depth = 24
|
16285
|
+
0x11, 0x11]),
|
16286
|
+
// pre_defined = -1
|
16287
|
+
hvcc, MP4.box(MP4.types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80,
|
16288
|
+
// bufferSizeDB
|
16289
|
+
0x00, 0x2d, 0xc6, 0xc0,
|
16290
|
+
// maxBitrate
|
16291
|
+
0x00, 0x2d, 0xc6, 0xc0])),
|
16292
|
+
// avgBitrate
|
16293
|
+
MP4.box(MP4.types.pasp, new Uint8Array([hSpacing >> 24,
|
16294
|
+
// hSpacing
|
16295
|
+
hSpacing >> 16 & 0xff, hSpacing >> 8 & 0xff, hSpacing & 0xff, vSpacing >> 24,
|
16296
|
+
// vSpacing
|
16297
|
+
vSpacing >> 16 & 0xff, vSpacing >> 8 & 0xff, vSpacing & 0xff])));
|
16298
|
+
}
|
16090
16299
|
}
|
16091
16300
|
MP4.types = void 0;
|
16092
16301
|
MP4.HDLR_TYPES = void 0;
|
@@ -16462,9 +16671,9 @@ class MP4Remuxer {
|
|
16462
16671
|
const foundOverlap = delta < -1;
|
16463
16672
|
if (foundHole || foundOverlap) {
|
16464
16673
|
if (foundHole) {
|
16465
|
-
logger.warn(
|
16674
|
+
logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
|
16466
16675
|
} else {
|
16467
|
-
logger.warn(
|
16676
|
+
logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
|
16468
16677
|
}
|
16469
16678
|
if (!foundOverlap || nextAvcDts >= inputSamples[0].pts || chromeVersion) {
|
16470
16679
|
firstDTS = nextAvcDts;
|
@@ -16473,12 +16682,24 @@ class MP4Remuxer {
|
|
16473
16682
|
inputSamples[0].dts = firstDTS;
|
16474
16683
|
inputSamples[0].pts = firstPTS;
|
16475
16684
|
} else {
|
16685
|
+
let isPTSOrderRetained = true;
|
16476
16686
|
for (let i = 0; i < inputSamples.length; i++) {
|
16477
|
-
if (inputSamples[i].dts > firstPTS) {
|
16687
|
+
if (inputSamples[i].dts > firstPTS && isPTSOrderRetained) {
|
16478
16688
|
break;
|
16479
16689
|
}
|
16690
|
+
const prevPTS = inputSamples[i].pts;
|
16480
16691
|
inputSamples[i].dts -= delta;
|
16481
16692
|
inputSamples[i].pts -= delta;
|
16693
|
+
|
16694
|
+
// check to see if this sample's PTS order has changed
|
16695
|
+
// relative to the next one
|
16696
|
+
if (i < inputSamples.length - 1) {
|
16697
|
+
const nextSamplePTS = inputSamples[i + 1].pts;
|
16698
|
+
const currentSamplePTS = inputSamples[i].pts;
|
16699
|
+
const currentOrder = nextSamplePTS <= currentSamplePTS;
|
16700
|
+
const prevOrder = nextSamplePTS <= prevPTS;
|
16701
|
+
isPTSOrderRetained = currentOrder == prevOrder;
|
16702
|
+
}
|
16482
16703
|
}
|
16483
16704
|
}
|
16484
16705
|
logger.log(`Video: Initial PTS/DTS adjusted: ${toMsFromMpegTsClock(firstPTS, true)}/${toMsFromMpegTsClock(firstDTS, true)}, delta: ${toMsFromMpegTsClock(delta, true)} ms`);
|
@@ -16626,7 +16847,7 @@ class MP4Remuxer {
|
|
16626
16847
|
}
|
16627
16848
|
}
|
16628
16849
|
}
|
16629
|
-
// next AVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
|
16850
|
+
// next AVC/HEVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
|
16630
16851
|
mp4SampleDuration = stretchedLastFrame || !mp4SampleDuration ? averageSampleDuration : mp4SampleDuration;
|
16631
16852
|
this.nextAvcDts = nextAvcDts = lastDTS + mp4SampleDuration;
|
16632
16853
|
this.videoSampleDuration = mp4SampleDuration;
|
@@ -16759,7 +16980,7 @@ class MP4Remuxer {
|
|
16759
16980
|
logger.warn(`[mp4-remuxer]: Injecting ${missing} audio frame @ ${(nextPts / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`);
|
16760
16981
|
for (let j = 0; j < missing; j++) {
|
16761
16982
|
const newStamp = Math.max(nextPts, 0);
|
16762
|
-
let fillFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
|
16983
|
+
let fillFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
|
16763
16984
|
if (!fillFrame) {
|
16764
16985
|
logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.');
|
16765
16986
|
fillFrame = sample.unit.subarray();
|
@@ -16887,7 +17108,7 @@ class MP4Remuxer {
|
|
16887
17108
|
// samples count of this segment's duration
|
16888
17109
|
const nbSamples = Math.ceil((endDTS - startDTS) / frameDuration);
|
16889
17110
|
// silent frame
|
16890
|
-
const silentFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
|
17111
|
+
const silentFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
|
16891
17112
|
logger.warn('[mp4-remuxer]: remux empty Audio');
|
16892
17113
|
// Can't remux if we can't generate a silent frame...
|
16893
17114
|
if (!silentFrame) {
|
@@ -17278,13 +17499,15 @@ class Transmuxer {
|
|
17278
17499
|
initSegmentData
|
17279
17500
|
} = transmuxConfig;
|
17280
17501
|
const keyData = getEncryptionType(uintData, decryptdata);
|
17281
|
-
if (keyData && keyData.method
|
17502
|
+
if (keyData && isFullSegmentEncryption(keyData.method)) {
|
17282
17503
|
const decrypter = this.getDecrypter();
|
17504
|
+
const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
|
17505
|
+
|
17283
17506
|
// Software decryption is synchronous; webCrypto is not
|
17284
17507
|
if (decrypter.isSync()) {
|
17285
17508
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
17286
17509
|
// data is handled in the flush() call
|
17287
|
-
let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer);
|
17510
|
+
let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode);
|
17288
17511
|
// For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
|
17289
17512
|
const loadingParts = chunkMeta.part > -1;
|
17290
17513
|
if (loadingParts) {
|
@@ -17296,7 +17519,7 @@ class Transmuxer {
|
|
17296
17519
|
}
|
17297
17520
|
uintData = new Uint8Array(decryptedData);
|
17298
17521
|
} else {
|
17299
|
-
this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer).then(decryptedData => {
|
17522
|
+
this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode).then(decryptedData => {
|
17300
17523
|
// Calling push here is important; if flush() is called while this is still resolving, this ensures that
|
17301
17524
|
// the decrypted data has been transmuxed
|
17302
17525
|
const result = this.push(decryptedData, null, chunkMeta);
|
@@ -17950,14 +18173,7 @@ class TransmuxerInterface {
|
|
17950
18173
|
this.observer = new EventEmitter();
|
17951
18174
|
this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
|
17952
18175
|
this.observer.on(Events.ERROR, forwardMessage);
|
17953
|
-
const
|
17954
|
-
isTypeSupported: () => false
|
17955
|
-
};
|
17956
|
-
const m2tsTypeSupported = {
|
17957
|
-
mpeg: MediaSource.isTypeSupported('audio/mpeg'),
|
17958
|
-
mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
|
17959
|
-
ac3: false
|
17960
|
-
};
|
18176
|
+
const m2tsTypeSupported = getM2TSSupportedAudioTypes(config.preferManagedMediaSource);
|
17961
18177
|
|
17962
18178
|
// navigator.vendor is not always available in Web Worker
|
17963
18179
|
// refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
|
@@ -18221,8 +18437,9 @@ const STALL_MINIMUM_DURATION_MS = 250;
|
|
18221
18437
|
const MAX_START_GAP_JUMP = 2.0;
|
18222
18438
|
const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
|
18223
18439
|
const SKIP_BUFFER_RANGE_START = 0.05;
|
18224
|
-
class GapController {
|
18440
|
+
class GapController extends Logger {
|
18225
18441
|
constructor(config, media, fragmentTracker, hls) {
|
18442
|
+
super('gap-controller', hls.logger);
|
18226
18443
|
this.config = void 0;
|
18227
18444
|
this.media = null;
|
18228
18445
|
this.fragmentTracker = void 0;
|
@@ -18232,6 +18449,7 @@ class GapController {
|
|
18232
18449
|
this.stalled = null;
|
18233
18450
|
this.moved = false;
|
18234
18451
|
this.seeking = false;
|
18452
|
+
this.ended = 0;
|
18235
18453
|
this.config = config;
|
18236
18454
|
this.media = media;
|
18237
18455
|
this.fragmentTracker = fragmentTracker;
|
@@ -18249,7 +18467,7 @@ class GapController {
|
|
18249
18467
|
*
|
18250
18468
|
* @param lastCurrentTime - Previously read playhead position
|
18251
18469
|
*/
|
18252
|
-
poll(lastCurrentTime, activeFrag) {
|
18470
|
+
poll(lastCurrentTime, activeFrag, levelDetails, state) {
|
18253
18471
|
const {
|
18254
18472
|
config,
|
18255
18473
|
media,
|
@@ -18268,6 +18486,7 @@ class GapController {
|
|
18268
18486
|
|
18269
18487
|
// The playhead is moving, no-op
|
18270
18488
|
if (currentTime !== lastCurrentTime) {
|
18489
|
+
this.ended = 0;
|
18271
18490
|
this.moved = true;
|
18272
18491
|
if (!seeking) {
|
18273
18492
|
this.nudgeRetry = 0;
|
@@ -18276,7 +18495,7 @@ class GapController {
|
|
18276
18495
|
// The playhead is now moving, but was previously stalled
|
18277
18496
|
if (this.stallReported) {
|
18278
18497
|
const _stalledDuration = self.performance.now() - stalled;
|
18279
|
-
|
18498
|
+
this.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
|
18280
18499
|
this.stallReported = false;
|
18281
18500
|
}
|
18282
18501
|
this.stalled = null;
|
@@ -18312,7 +18531,6 @@ class GapController {
|
|
18312
18531
|
// Skip start gaps if we haven't played, but the last poll detected the start of a stall
|
18313
18532
|
// The addition poll gives the browser a chance to jump the gap for us
|
18314
18533
|
if (!this.moved && this.stalled !== null) {
|
18315
|
-
var _level$details;
|
18316
18534
|
// There is no playable buffer (seeked, waiting for buffer)
|
18317
18535
|
const isBuffered = bufferInfo.len > 0;
|
18318
18536
|
if (!isBuffered && !nextStart) {
|
@@ -18324,9 +18542,8 @@ class GapController {
|
|
18324
18542
|
// When joining a live stream with audio tracks, account for live playlist window sliding by allowing
|
18325
18543
|
// a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
|
18326
18544
|
// that begins over 1 target duration after the video start position.
|
18327
|
-
const
|
18328
|
-
const
|
18329
|
-
const maxStartGapJump = isLive ? level.details.targetduration * 2 : MAX_START_GAP_JUMP;
|
18545
|
+
const isLive = !!(levelDetails != null && levelDetails.live);
|
18546
|
+
const maxStartGapJump = isLive ? levelDetails.targetduration * 2 : MAX_START_GAP_JUMP;
|
18330
18547
|
const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
|
18331
18548
|
if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
|
18332
18549
|
if (!media.paused) {
|
@@ -18344,6 +18561,17 @@ class GapController {
|
|
18344
18561
|
}
|
18345
18562
|
const stalledDuration = tnow - stalled;
|
18346
18563
|
if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) {
|
18564
|
+
// Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
|
18565
|
+
if (state === State.ENDED && !(levelDetails && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? void 0 : levelDetails.edge) || 0)) < 1) {
|
18566
|
+
if (stalledDuration < 1000 || this.ended) {
|
18567
|
+
return;
|
18568
|
+
}
|
18569
|
+
this.ended = currentTime;
|
18570
|
+
this.hls.trigger(Events.MEDIA_ENDED, {
|
18571
|
+
stalled: true
|
18572
|
+
});
|
18573
|
+
return;
|
18574
|
+
}
|
18347
18575
|
// Report stalling after trying to fix
|
18348
18576
|
this._reportStall(bufferInfo);
|
18349
18577
|
if (!this.media) {
|
@@ -18387,7 +18615,7 @@ class GapController {
|
|
18387
18615
|
// needs to cross some sort of threshold covering all source-buffers content
|
18388
18616
|
// to start playing properly.
|
18389
18617
|
if ((bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
|
18390
|
-
|
18618
|
+
this.warn('Trying to nudge playhead over buffer-hole');
|
18391
18619
|
// Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
|
18392
18620
|
// We only try to jump the hole if it's under the configured size
|
18393
18621
|
// Reset stalled so to rearm watchdog timer
|
@@ -18411,7 +18639,7 @@ class GapController {
|
|
18411
18639
|
// Report stalled error once
|
18412
18640
|
this.stallReported = true;
|
18413
18641
|
const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${JSON.stringify(bufferInfo)})`);
|
18414
|
-
|
18642
|
+
this.warn(error.message);
|
18415
18643
|
hls.trigger(Events.ERROR, {
|
18416
18644
|
type: ErrorTypes.MEDIA_ERROR,
|
18417
18645
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
@@ -18479,7 +18707,7 @@ class GapController {
|
|
18479
18707
|
}
|
18480
18708
|
}
|
18481
18709
|
const targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
|
18482
|
-
|
18710
|
+
this.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
|
18483
18711
|
this.moved = true;
|
18484
18712
|
this.stalled = null;
|
18485
18713
|
media.currentTime = targetTime;
|
@@ -18520,7 +18748,7 @@ class GapController {
|
|
18520
18748
|
const targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset;
|
18521
18749
|
// playback stalled in buffered area ... let's nudge currentTime to try to overcome this
|
18522
18750
|
const error = new Error(`Nudging 'currentTime' from ${currentTime} to ${targetTime}`);
|
18523
|
-
|
18751
|
+
this.warn(error.message);
|
18524
18752
|
media.currentTime = targetTime;
|
18525
18753
|
hls.trigger(Events.ERROR, {
|
18526
18754
|
type: ErrorTypes.MEDIA_ERROR,
|
@@ -18530,7 +18758,7 @@ class GapController {
|
|
18530
18758
|
});
|
18531
18759
|
} else {
|
18532
18760
|
const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`);
|
18533
|
-
|
18761
|
+
this.error(error.message);
|
18534
18762
|
hls.trigger(Events.ERROR, {
|
18535
18763
|
type: ErrorTypes.MEDIA_ERROR,
|
18536
18764
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
@@ -18545,7 +18773,7 @@ const TICK_INTERVAL = 100; // how often to tick in ms
|
|
18545
18773
|
|
18546
18774
|
class StreamController extends BaseStreamController {
|
18547
18775
|
constructor(hls, fragmentTracker, keyLoader) {
|
18548
|
-
super(hls, fragmentTracker, keyLoader, '
|
18776
|
+
super(hls, fragmentTracker, keyLoader, 'stream-controller', PlaylistLevelType.MAIN);
|
18549
18777
|
this.audioCodecSwap = false;
|
18550
18778
|
this.gapController = null;
|
18551
18779
|
this.level = -1;
|
@@ -18553,27 +18781,43 @@ class StreamController extends BaseStreamController {
|
|
18553
18781
|
this.altAudio = false;
|
18554
18782
|
this.audioOnly = false;
|
18555
18783
|
this.fragPlaying = null;
|
18556
|
-
this.onvplaying = null;
|
18557
|
-
this.onvseeked = null;
|
18558
18784
|
this.fragLastKbps = 0;
|
18559
18785
|
this.couldBacktrack = false;
|
18560
18786
|
this.backtrackFragment = null;
|
18561
18787
|
this.audioCodecSwitch = false;
|
18562
18788
|
this.videoBuffer = null;
|
18563
|
-
this.
|
18789
|
+
this.onMediaPlaying = () => {
|
18790
|
+
// tick to speed up FRAG_CHANGED triggering
|
18791
|
+
this.tick();
|
18792
|
+
};
|
18793
|
+
this.onMediaSeeked = () => {
|
18794
|
+
const media = this.media;
|
18795
|
+
const currentTime = media ? media.currentTime : null;
|
18796
|
+
if (isFiniteNumber(currentTime)) {
|
18797
|
+
this.log(`Media seeked to ${currentTime.toFixed(3)}`);
|
18798
|
+
}
|
18799
|
+
|
18800
|
+
// If seeked was issued before buffer was appended do not tick immediately
|
18801
|
+
const bufferInfo = this.getMainFwdBufferInfo();
|
18802
|
+
if (bufferInfo === null || bufferInfo.len === 0) {
|
18803
|
+
this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
|
18804
|
+
return;
|
18805
|
+
}
|
18806
|
+
|
18807
|
+
// tick to speed up FRAG_CHANGED triggering
|
18808
|
+
this.tick();
|
18809
|
+
};
|
18810
|
+
this.registerListeners();
|
18564
18811
|
}
|
18565
|
-
|
18812
|
+
registerListeners() {
|
18813
|
+
super.registerListeners();
|
18566
18814
|
const {
|
18567
18815
|
hls
|
18568
18816
|
} = this;
|
18569
|
-
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
18570
|
-
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
18571
|
-
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
18572
18817
|
hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
18573
18818
|
hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
|
18574
18819
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
18575
18820
|
hls.on(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
|
18576
|
-
hls.on(Events.ERROR, this.onError, this);
|
18577
18821
|
hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
18578
18822
|
hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
18579
18823
|
hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -18581,17 +18825,14 @@ class StreamController extends BaseStreamController {
|
|
18581
18825
|
hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
|
18582
18826
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
18583
18827
|
}
|
18584
|
-
|
18828
|
+
unregisterListeners() {
|
18829
|
+
super.unregisterListeners();
|
18585
18830
|
const {
|
18586
18831
|
hls
|
18587
18832
|
} = this;
|
18588
|
-
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
18589
|
-
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
18590
|
-
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
18591
18833
|
hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
18592
18834
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
18593
18835
|
hls.off(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
|
18594
|
-
hls.off(Events.ERROR, this.onError, this);
|
18595
18836
|
hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
18596
18837
|
hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
18597
18838
|
hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -18600,7 +18841,9 @@ class StreamController extends BaseStreamController {
|
|
18600
18841
|
hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
18601
18842
|
}
|
18602
18843
|
onHandlerDestroying() {
|
18603
|
-
|
18844
|
+
// @ts-ignore
|
18845
|
+
this.onMediaPlaying = this.onMediaSeeked = null;
|
18846
|
+
this.unregisterListeners();
|
18604
18847
|
super.onHandlerDestroying();
|
18605
18848
|
}
|
18606
18849
|
startLoad(startPosition) {
|
@@ -18720,7 +18963,7 @@ class StreamController extends BaseStreamController {
|
|
18720
18963
|
if (this.altAudio && this.audioOnly) {
|
18721
18964
|
return;
|
18722
18965
|
}
|
18723
|
-
if (!(levels != null && levels[level])) {
|
18966
|
+
if (!this.buffering || !(levels != null && levels[level])) {
|
18724
18967
|
return;
|
18725
18968
|
}
|
18726
18969
|
const levelInfo = levels[level];
|
@@ -18928,20 +19171,17 @@ class StreamController extends BaseStreamController {
|
|
18928
19171
|
onMediaAttached(event, data) {
|
18929
19172
|
super.onMediaAttached(event, data);
|
18930
19173
|
const media = data.media;
|
18931
|
-
|
18932
|
-
|
18933
|
-
media.addEventListener('playing', this.onvplaying);
|
18934
|
-
media.addEventListener('seeked', this.onvseeked);
|
19174
|
+
media.addEventListener('playing', this.onMediaPlaying);
|
19175
|
+
media.addEventListener('seeked', this.onMediaSeeked);
|
18935
19176
|
this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
|
18936
19177
|
}
|
18937
19178
|
onMediaDetaching() {
|
18938
19179
|
const {
|
18939
19180
|
media
|
18940
19181
|
} = this;
|
18941
|
-
if (media
|
18942
|
-
media.removeEventListener('playing', this.
|
18943
|
-
media.removeEventListener('seeked', this.
|
18944
|
-
this.onvplaying = this.onvseeked = null;
|
19182
|
+
if (media) {
|
19183
|
+
media.removeEventListener('playing', this.onMediaPlaying);
|
19184
|
+
media.removeEventListener('seeked', this.onMediaSeeked);
|
18945
19185
|
this.videoBuffer = null;
|
18946
19186
|
}
|
18947
19187
|
this.fragPlaying = null;
|
@@ -18951,27 +19191,6 @@ class StreamController extends BaseStreamController {
|
|
18951
19191
|
}
|
18952
19192
|
super.onMediaDetaching();
|
18953
19193
|
}
|
18954
|
-
onMediaPlaying() {
|
18955
|
-
// tick to speed up FRAG_CHANGED triggering
|
18956
|
-
this.tick();
|
18957
|
-
}
|
18958
|
-
onMediaSeeked() {
|
18959
|
-
const media = this.media;
|
18960
|
-
const currentTime = media ? media.currentTime : null;
|
18961
|
-
if (isFiniteNumber(currentTime)) {
|
18962
|
-
this.log(`Media seeked to ${currentTime.toFixed(3)}`);
|
18963
|
-
}
|
18964
|
-
|
18965
|
-
// If seeked was issued before buffer was appended do not tick immediately
|
18966
|
-
const bufferInfo = this.getMainFwdBufferInfo();
|
18967
|
-
if (bufferInfo === null || bufferInfo.len === 0) {
|
18968
|
-
this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
|
18969
|
-
return;
|
18970
|
-
}
|
18971
|
-
|
18972
|
-
// tick to speed up FRAG_CHANGED triggering
|
18973
|
-
this.tick();
|
18974
|
-
}
|
18975
19194
|
onManifestLoading() {
|
18976
19195
|
// reset buffer on manifest loading
|
18977
19196
|
this.log('Trigger BUFFER_RESET');
|
@@ -19263,8 +19482,10 @@ class StreamController extends BaseStreamController {
|
|
19263
19482
|
}
|
19264
19483
|
if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) {
|
19265
19484
|
// Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
|
19266
|
-
const
|
19267
|
-
|
19485
|
+
const state = this.state;
|
19486
|
+
const activeFrag = state !== State.IDLE ? this.fragCurrent : null;
|
19487
|
+
const levelDetails = this.getLevelDetails();
|
19488
|
+
gapController.poll(this.lastCurrentTime, activeFrag, levelDetails, state);
|
19268
19489
|
}
|
19269
19490
|
this.lastCurrentTime = media.currentTime;
|
19270
19491
|
}
|
@@ -19702,7 +19923,7 @@ class Hls {
|
|
19702
19923
|
* Get the video-dev/hls.js package version.
|
19703
19924
|
*/
|
19704
19925
|
static get version() {
|
19705
|
-
return "1.5.
|
19926
|
+
return "1.5.5-0.canary.9978";
|
19706
19927
|
}
|
19707
19928
|
|
19708
19929
|
/**
|
@@ -19765,9 +19986,12 @@ class Hls {
|
|
19765
19986
|
* The configuration object provided on player instantiation.
|
19766
19987
|
*/
|
19767
19988
|
this.userConfig = void 0;
|
19989
|
+
/**
|
19990
|
+
* The logger functions used by this player instance, configured on player instantiation.
|
19991
|
+
*/
|
19992
|
+
this.logger = void 0;
|
19768
19993
|
this.coreComponents = void 0;
|
19769
19994
|
this.networkControllers = void 0;
|
19770
|
-
this.started = false;
|
19771
19995
|
this._emitter = new EventEmitter();
|
19772
19996
|
this._autoLevelCapping = -1;
|
19773
19997
|
this._maxHdcpLevel = null;
|
@@ -19784,11 +20008,11 @@ class Hls {
|
|
19784
20008
|
this._media = null;
|
19785
20009
|
this.url = null;
|
19786
20010
|
this.triggeringException = void 0;
|
19787
|
-
enableLogs(userConfig.debug || false, 'Hls instance');
|
19788
|
-
const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig);
|
20011
|
+
const logger = this.logger = enableLogs(userConfig.debug || false, 'Hls instance');
|
20012
|
+
const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig, logger);
|
19789
20013
|
this.userConfig = userConfig;
|
19790
20014
|
if (config.progressive) {
|
19791
|
-
enableStreamingMode(config);
|
20015
|
+
enableStreamingMode(config, logger);
|
19792
20016
|
}
|
19793
20017
|
|
19794
20018
|
// core controllers and network loaders
|
@@ -19887,7 +20111,7 @@ class Hls {
|
|
19887
20111
|
try {
|
19888
20112
|
return this.emit(event, event, eventObject);
|
19889
20113
|
} catch (error) {
|
19890
|
-
logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
|
20114
|
+
this.logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
|
19891
20115
|
// Prevent recursion in error event handlers that throw #5497
|
19892
20116
|
if (!this.triggeringException) {
|
19893
20117
|
this.triggeringException = true;
|
@@ -19913,7 +20137,7 @@ class Hls {
|
|
19913
20137
|
* Dispose of the instance
|
19914
20138
|
*/
|
19915
20139
|
destroy() {
|
19916
|
-
logger.log('destroy');
|
20140
|
+
this.logger.log('destroy');
|
19917
20141
|
this.trigger(Events.DESTROYING, undefined);
|
19918
20142
|
this.detachMedia();
|
19919
20143
|
this.removeAllListeners();
|
@@ -19934,7 +20158,7 @@ class Hls {
|
|
19934
20158
|
* Attaches Hls.js to a media element
|
19935
20159
|
*/
|
19936
20160
|
attachMedia(media) {
|
19937
|
-
logger.log('attachMedia');
|
20161
|
+
this.logger.log('attachMedia');
|
19938
20162
|
this._media = media;
|
19939
20163
|
this.trigger(Events.MEDIA_ATTACHING, {
|
19940
20164
|
media: media
|
@@ -19945,7 +20169,7 @@ class Hls {
|
|
19945
20169
|
* Detach Hls.js from the media
|
19946
20170
|
*/
|
19947
20171
|
detachMedia() {
|
19948
|
-
logger.log('detachMedia');
|
20172
|
+
this.logger.log('detachMedia');
|
19949
20173
|
this.trigger(Events.MEDIA_DETACHING, undefined);
|
19950
20174
|
this._media = null;
|
19951
20175
|
}
|
@@ -19962,7 +20186,7 @@ class Hls {
|
|
19962
20186
|
});
|
19963
20187
|
this._autoLevelCapping = -1;
|
19964
20188
|
this._maxHdcpLevel = null;
|
19965
|
-
logger.log(`loadSource:${loadingSource}`);
|
20189
|
+
this.logger.log(`loadSource:${loadingSource}`);
|
19966
20190
|
if (media && loadedSource && (loadedSource !== loadingSource || this.bufferController.hasSourceTypes())) {
|
19967
20191
|
this.detachMedia();
|
19968
20192
|
this.attachMedia(media);
|
@@ -19981,8 +20205,7 @@ class Hls {
|
|
19981
20205
|
* Defaults to -1 (None: starts from earliest point)
|
19982
20206
|
*/
|
19983
20207
|
startLoad(startPosition = -1) {
|
19984
|
-
logger.log(`startLoad(${startPosition})`);
|
19985
|
-
this.started = true;
|
20208
|
+
this.logger.log(`startLoad(${startPosition})`);
|
19986
20209
|
this.networkControllers.forEach(controller => {
|
19987
20210
|
controller.startLoad(startPosition);
|
19988
20211
|
});
|
@@ -19992,34 +20215,31 @@ class Hls {
|
|
19992
20215
|
* Stop loading of any stream data.
|
19993
20216
|
*/
|
19994
20217
|
stopLoad() {
|
19995
|
-
logger.log('stopLoad');
|
19996
|
-
this.started = false;
|
20218
|
+
this.logger.log('stopLoad');
|
19997
20219
|
this.networkControllers.forEach(controller => {
|
19998
20220
|
controller.stopLoad();
|
19999
20221
|
});
|
20000
20222
|
}
|
20001
20223
|
|
20002
20224
|
/**
|
20003
|
-
* Resumes stream controller segment loading
|
20225
|
+
* Resumes stream controller segment loading after `pauseBuffering` has been called.
|
20004
20226
|
*/
|
20005
20227
|
resumeBuffering() {
|
20006
|
-
|
20007
|
-
|
20008
|
-
|
20009
|
-
|
20010
|
-
|
20011
|
-
});
|
20012
|
-
}
|
20228
|
+
this.networkControllers.forEach(controller => {
|
20229
|
+
if (controller.resumeBuffering) {
|
20230
|
+
controller.resumeBuffering();
|
20231
|
+
}
|
20232
|
+
});
|
20013
20233
|
}
|
20014
20234
|
|
20015
20235
|
/**
|
20016
|
-
*
|
20236
|
+
* Prevents stream controller from loading new segments until `resumeBuffering` is called.
|
20017
20237
|
* This allows for media buffering to be paused without interupting playlist loading.
|
20018
20238
|
*/
|
20019
20239
|
pauseBuffering() {
|
20020
20240
|
this.networkControllers.forEach(controller => {
|
20021
|
-
if (
|
20022
|
-
controller.
|
20241
|
+
if (controller.pauseBuffering) {
|
20242
|
+
controller.pauseBuffering();
|
20023
20243
|
}
|
20024
20244
|
});
|
20025
20245
|
}
|
@@ -20028,7 +20248,7 @@ class Hls {
|
|
20028
20248
|
* Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
|
20029
20249
|
*/
|
20030
20250
|
swapAudioCodec() {
|
20031
|
-
logger.log('swapAudioCodec');
|
20251
|
+
this.logger.log('swapAudioCodec');
|
20032
20252
|
this.streamController.swapAudioCodec();
|
20033
20253
|
}
|
20034
20254
|
|
@@ -20039,7 +20259,7 @@ class Hls {
|
|
20039
20259
|
* Automatic recovery of media-errors by this process is configurable.
|
20040
20260
|
*/
|
20041
20261
|
recoverMediaError() {
|
20042
|
-
logger.log('recoverMediaError');
|
20262
|
+
this.logger.log('recoverMediaError');
|
20043
20263
|
const media = this._media;
|
20044
20264
|
this.detachMedia();
|
20045
20265
|
if (media) {
|
@@ -20069,7 +20289,7 @@ class Hls {
|
|
20069
20289
|
* 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.
|
20070
20290
|
*/
|
20071
20291
|
set currentLevel(newLevel) {
|
20072
|
-
logger.log(`set currentLevel:${newLevel}`);
|
20292
|
+
this.logger.log(`set currentLevel:${newLevel}`);
|
20073
20293
|
this.levelController.manualLevel = newLevel;
|
20074
20294
|
this.streamController.immediateLevelSwitch();
|
20075
20295
|
}
|
@@ -20088,7 +20308,7 @@ class Hls {
|
|
20088
20308
|
* @param newLevel - Pass -1 for automatic level selection
|
20089
20309
|
*/
|
20090
20310
|
set nextLevel(newLevel) {
|
20091
|
-
logger.log(`set nextLevel:${newLevel}`);
|
20311
|
+
this.logger.log(`set nextLevel:${newLevel}`);
|
20092
20312
|
this.levelController.manualLevel = newLevel;
|
20093
20313
|
this.streamController.nextLevelSwitch();
|
20094
20314
|
}
|
@@ -20107,7 +20327,7 @@ class Hls {
|
|
20107
20327
|
* @param newLevel - Pass -1 for automatic level selection
|
20108
20328
|
*/
|
20109
20329
|
set loadLevel(newLevel) {
|
20110
|
-
logger.log(`set loadLevel:${newLevel}`);
|
20330
|
+
this.logger.log(`set loadLevel:${newLevel}`);
|
20111
20331
|
this.levelController.manualLevel = newLevel;
|
20112
20332
|
}
|
20113
20333
|
|
@@ -20138,7 +20358,7 @@ class Hls {
|
|
20138
20358
|
* Sets "first-level", see getter.
|
20139
20359
|
*/
|
20140
20360
|
set firstLevel(newLevel) {
|
20141
|
-
logger.log(`set firstLevel:${newLevel}`);
|
20361
|
+
this.logger.log(`set firstLevel:${newLevel}`);
|
20142
20362
|
this.levelController.firstLevel = newLevel;
|
20143
20363
|
}
|
20144
20364
|
|
@@ -20163,7 +20383,7 @@ class Hls {
|
|
20163
20383
|
* (determined from download of first segment)
|
20164
20384
|
*/
|
20165
20385
|
set startLevel(newLevel) {
|
20166
|
-
logger.log(`set startLevel:${newLevel}`);
|
20386
|
+
this.logger.log(`set startLevel:${newLevel}`);
|
20167
20387
|
// if not in automatic start level detection, ensure startLevel is greater than minAutoLevel
|
20168
20388
|
if (newLevel !== -1) {
|
20169
20389
|
newLevel = Math.max(newLevel, this.minAutoLevel);
|
@@ -20238,7 +20458,7 @@ class Hls {
|
|
20238
20458
|
*/
|
20239
20459
|
set autoLevelCapping(newLevel) {
|
20240
20460
|
if (this._autoLevelCapping !== newLevel) {
|
20241
|
-
logger.log(`set autoLevelCapping:${newLevel}`);
|
20461
|
+
this.logger.log(`set autoLevelCapping:${newLevel}`);
|
20242
20462
|
this._autoLevelCapping = newLevel;
|
20243
20463
|
this.levelController.checkMaxAutoUpdated();
|
20244
20464
|
}
|
@@ -20517,5 +20737,5 @@ var KeySystemFormats = empty.KeySystemFormats;
|
|
20517
20737
|
var KeySystems = empty.KeySystems;
|
20518
20738
|
var SubtitleStreamController = empty.SubtitleStreamController;
|
20519
20739
|
var TimelineController = empty.TimelineController;
|
20520
|
-
export { AbrController, AttrList,
|
20740
|
+
export { AbrController, AttrList, HevcVideoParser as AudioStreamController, HevcVideoParser as AudioTrackController, BasePlaylistController, BaseSegment, BaseStreamController, BufferController, HevcVideoParser as CMCDController, CapLevelController, ChunkMetadata, ContentSteeringController, DateRange, HevcVideoParser as EMEController, ErrorActionFlags, ErrorController, ErrorDetails, ErrorTypes, Events, FPSController, Fragment, Hls, HlsSkip, HlsUrlParameters, KeySystemFormats, KeySystems, Level, LevelDetails, LevelKey, LoadStats, MetadataSchema, NetworkErrorAction, Part, PlaylistLevelType, SubtitleStreamController, HevcVideoParser as SubtitleTrackController, TimelineController, Hls as default, getMediaSource, isMSESupported, isSupported };
|
20521
20741
|
//# sourceMappingURL=hls.light.mjs.map
|