hls.js 1.5.2-0.canary.9934 → 1.5.3
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/dist/hls-demo.js +0 -5
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +757 -883
- package/dist/hls.js.d.ts +47 -56
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +477 -600
- 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 +335 -446
- 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 +572 -681
- 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 +11 -11
- package/src/config.ts +2 -3
- package/src/controller/abr-controller.ts +22 -22
- package/src/controller/audio-stream-controller.ts +14 -11
- 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 +29 -47
- package/src/controller/buffer-controller.ts +11 -10
- package/src/controller/cap-level-controller.ts +2 -1
- package/src/controller/cmcd-controller.ts +3 -25
- package/src/controller/content-steering-controller.ts +6 -8
- package/src/controller/eme-controller.ts +22 -9
- package/src/controller/error-controller.ts +8 -6
- package/src/controller/fps-controller.ts +3 -2
- package/src/controller/gap-controller.ts +16 -43
- package/src/controller/latency-controller.ts +11 -9
- package/src/controller/level-controller.ts +17 -5
- package/src/controller/stream-controller.ts +31 -24
- package/src/controller/subtitle-stream-controller.ts +14 -13
- package/src/controller/subtitle-track-controller.ts +3 -5
- package/src/controller/timeline-controller.ts +30 -23
- package/src/crypt/aes-crypto.ts +2 -21
- package/src/crypt/decrypter.ts +18 -32
- package/src/crypt/fast-aes-key.ts +5 -24
- package/src/demux/audio/adts.ts +4 -9
- package/src/demux/sample-aes.ts +0 -2
- package/src/demux/transmuxer-interface.ts +12 -4
- package/src/demux/transmuxer-worker.ts +4 -4
- package/src/demux/transmuxer.ts +3 -16
- package/src/demux/tsdemuxer.ts +17 -12
- package/src/events.ts +0 -7
- package/src/hls.ts +20 -33
- package/src/loader/fragment-loader.ts +2 -9
- package/src/loader/key-loader.ts +0 -2
- package/src/loader/level-key.ts +9 -10
- package/src/remux/mp4-remuxer.ts +4 -20
- package/src/task-loop.ts +2 -5
- package/src/types/demuxer.ts +0 -1
- package/src/types/events.ts +0 -4
- package/src/utils/codecs.ts +4 -33
- package/src/utils/logger.ts +24 -53
- package/src/crypt/decrypter-aes-mode.ts +0 -4
- package/src/utils/encryption-methods-util.ts +0 -21
package/dist/hls.light.mjs
CHANGED
@@ -256,7 +256,6 @@ let Events = /*#__PURE__*/function (Events) {
|
|
256
256
|
Events["MEDIA_ATTACHED"] = "hlsMediaAttached";
|
257
257
|
Events["MEDIA_DETACHING"] = "hlsMediaDetaching";
|
258
258
|
Events["MEDIA_DETACHED"] = "hlsMediaDetached";
|
259
|
-
Events["MEDIA_ENDED"] = "hlsMediaEnded";
|
260
259
|
Events["BUFFER_RESET"] = "hlsBufferReset";
|
261
260
|
Events["BUFFER_CODECS"] = "hlsBufferCodecs";
|
262
261
|
Events["BUFFER_CREATED"] = "hlsBufferCreated";
|
@@ -370,23 +369,6 @@ let ErrorDetails = /*#__PURE__*/function (ErrorDetails) {
|
|
370
369
|
return ErrorDetails;
|
371
370
|
}({});
|
372
371
|
|
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
|
-
}
|
390
372
|
const noop = function noop() {};
|
391
373
|
const fakeLogger = {
|
392
374
|
trace: noop,
|
@@ -396,9 +378,7 @@ const fakeLogger = {
|
|
396
378
|
info: noop,
|
397
379
|
error: noop
|
398
380
|
};
|
399
|
-
|
400
|
-
return _extends({}, fakeLogger);
|
401
|
-
}
|
381
|
+
let exportedLogger = fakeLogger;
|
402
382
|
|
403
383
|
// let lastCallTime;
|
404
384
|
// function formatMsgWithTimeInfo(type, msg) {
|
@@ -409,36 +389,35 @@ function createLogger() {
|
|
409
389
|
// return msg;
|
410
390
|
// }
|
411
391
|
|
412
|
-
function consolePrintFn(type
|
392
|
+
function consolePrintFn(type) {
|
413
393
|
const func = self.console[type];
|
414
|
-
|
394
|
+
if (func) {
|
395
|
+
return func.bind(self.console, `[${type}] >`);
|
396
|
+
}
|
397
|
+
return noop;
|
415
398
|
}
|
416
|
-
function
|
417
|
-
|
399
|
+
function exportLoggerFunctions(debugConfig, ...functions) {
|
400
|
+
functions.forEach(function (type) {
|
401
|
+
exportedLogger[type] = debugConfig[type] ? debugConfig[type].bind(debugConfig) : consolePrintFn(type);
|
402
|
+
});
|
418
403
|
}
|
419
|
-
|
420
|
-
function enableLogs(debugConfig, context, id) {
|
404
|
+
function enableLogs(debugConfig, id) {
|
421
405
|
// check that console is available
|
422
|
-
const newLogger = createLogger();
|
423
406
|
if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
|
424
|
-
|
407
|
+
exportLoggerFunctions(debugConfig,
|
425
408
|
// Remove out from list here to hard-disable a log-level
|
426
409
|
// 'trace',
|
427
|
-
'debug', 'log', 'info', 'warn', 'error'
|
428
|
-
keys.forEach(key => {
|
429
|
-
newLogger[key] = getLoggerFn(key, debugConfig, id);
|
430
|
-
});
|
410
|
+
'debug', 'log', 'info', 'warn', 'error');
|
431
411
|
// Some browsers don't allow to use bind on console object anyway
|
432
412
|
// fallback to default if needed
|
433
413
|
try {
|
434
|
-
|
414
|
+
exportedLogger.log(`Debug logs enabled for "${id}" in hls.js version ${"1.5.3"}`);
|
435
415
|
} catch (e) {
|
436
|
-
|
437
|
-
return createLogger();
|
416
|
+
exportedLogger = fakeLogger;
|
438
417
|
}
|
418
|
+
} else {
|
419
|
+
exportedLogger = fakeLogger;
|
439
420
|
}
|
440
|
-
exportedLogger = newLogger;
|
441
|
-
return newLogger;
|
442
421
|
}
|
443
422
|
const logger = exportedLogger;
|
444
423
|
|
@@ -1012,26 +991,6 @@ class LevelDetails {
|
|
1012
991
|
}
|
1013
992
|
}
|
1014
993
|
|
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
|
-
|
1035
994
|
// This file is inserted as a shim for modules which we do not want to include into the distro.
|
1036
995
|
// This replacement is done in the "alias" plugin of the rollup config.
|
1037
996
|
var empty = undefined;
|
@@ -2460,12 +2419,12 @@ class LevelKey {
|
|
2460
2419
|
this.keyFormatVersions = formatversions;
|
2461
2420
|
this.iv = iv;
|
2462
2421
|
this.encrypted = method ? method !== 'NONE' : false;
|
2463
|
-
this.isCommonEncryption = this.encrypted &&
|
2422
|
+
this.isCommonEncryption = this.encrypted && method !== 'AES-128';
|
2464
2423
|
}
|
2465
2424
|
isSupported() {
|
2466
2425
|
// If it's Segment encryption or No encryption, just select that key system
|
2467
2426
|
if (this.method) {
|
2468
|
-
if (
|
2427
|
+
if (this.method === 'AES-128' || this.method === 'NONE') {
|
2469
2428
|
return true;
|
2470
2429
|
}
|
2471
2430
|
if (this.keyFormat === 'identity') {
|
@@ -2479,13 +2438,14 @@ class LevelKey {
|
|
2479
2438
|
if (!this.encrypted || !this.uri) {
|
2480
2439
|
return null;
|
2481
2440
|
}
|
2482
|
-
if (
|
2441
|
+
if (this.method === 'AES-128' && this.uri && !this.iv) {
|
2483
2442
|
if (typeof sn !== 'number') {
|
2484
2443
|
// We are fetching decryption data for a initialization segment
|
2485
|
-
// If the segment was encrypted with AES-128
|
2444
|
+
// If the segment was encrypted with AES-128
|
2486
2445
|
// It must have an IV defined. We cannot substitute the Segment Number in.
|
2487
|
-
|
2488
|
-
|
2446
|
+
if (this.method === 'AES-128' && !this.iv) {
|
2447
|
+
logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
|
2448
|
+
}
|
2489
2449
|
// Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
|
2490
2450
|
sn = 0;
|
2491
2451
|
}
|
@@ -2632,28 +2592,23 @@ function getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource =
|
|
2632
2592
|
if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) {
|
2633
2593
|
return CODEC_COMPATIBLE_NAMES[lowerCaseCodec];
|
2634
2594
|
}
|
2595
|
+
|
2596
|
+
// Idealy fLaC and Opus would be first (spec-compliant) but
|
2597
|
+
// some browsers will report that fLaC is supported then fail.
|
2598
|
+
// see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
|
2635
2599
|
const codecsToCheck = {
|
2636
|
-
// Idealy fLaC and Opus would be first (spec-compliant) but
|
2637
|
-
// some browsers will report that fLaC is supported then fail.
|
2638
|
-
// see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
|
2639
2600
|
flac: ['flac', 'fLaC', 'FLAC'],
|
2640
|
-
opus: ['opus', 'Opus']
|
2641
|
-
// Replace audio codec info if browser does not support mp4a.40.34,
|
2642
|
-
// and demuxer can fallback to 'audio/mpeg' or 'audio/mp4;codecs="mp3"'
|
2643
|
-
'mp4a.40.34': ['mp3']
|
2601
|
+
opus: ['opus', 'Opus']
|
2644
2602
|
}[lowerCaseCodec];
|
2645
2603
|
for (let i = 0; i < codecsToCheck.length; i++) {
|
2646
|
-
var _getMediaSource;
|
2647
2604
|
if (isCodecMediaSourceSupported(codecsToCheck[i], 'audio', preferManagedMediaSource)) {
|
2648
2605
|
CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
|
2649
2606
|
return codecsToCheck[i];
|
2650
|
-
} else if (codecsToCheck[i] === 'mp3' && (_getMediaSource = getMediaSource(preferManagedMediaSource)) != null && _getMediaSource.isTypeSupported('audio/mpeg')) {
|
2651
|
-
return '';
|
2652
2607
|
}
|
2653
2608
|
}
|
2654
2609
|
return lowerCaseCodec;
|
2655
2610
|
}
|
2656
|
-
const AUDIO_CODEC_REGEXP = /flac|opus
|
2611
|
+
const AUDIO_CODEC_REGEXP = /flac|opus/i;
|
2657
2612
|
function getCodecCompatibleName(codec, preferManagedMediaSource = true) {
|
2658
2613
|
return codec.replace(AUDIO_CODEC_REGEXP, m => getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource));
|
2659
2614
|
}
|
@@ -2676,16 +2631,6 @@ function convertAVC1ToAVCOTI(codec) {
|
|
2676
2631
|
}
|
2677
2632
|
return codec;
|
2678
2633
|
}
|
2679
|
-
function getM2TSSupportedAudioTypes(preferManagedMediaSource) {
|
2680
|
-
const MediaSource = getMediaSource(preferManagedMediaSource) || {
|
2681
|
-
isTypeSupported: () => false
|
2682
|
-
};
|
2683
|
-
return {
|
2684
|
-
mpeg: MediaSource.isTypeSupported('audio/mpeg'),
|
2685
|
-
mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
|
2686
|
-
ac3: false
|
2687
|
-
};
|
2688
|
-
}
|
2689
2634
|
|
2690
2635
|
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;
|
2691
2636
|
const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g;
|
@@ -4263,47 +4208,7 @@ class LatencyController {
|
|
4263
4208
|
this.currentTime = 0;
|
4264
4209
|
this.stallCount = 0;
|
4265
4210
|
this._latency = null;
|
4266
|
-
this.
|
4267
|
-
const {
|
4268
|
-
media,
|
4269
|
-
levelDetails
|
4270
|
-
} = this;
|
4271
|
-
if (!media || !levelDetails) {
|
4272
|
-
return;
|
4273
|
-
}
|
4274
|
-
this.currentTime = media.currentTime;
|
4275
|
-
const latency = this.computeLatency();
|
4276
|
-
if (latency === null) {
|
4277
|
-
return;
|
4278
|
-
}
|
4279
|
-
this._latency = latency;
|
4280
|
-
|
4281
|
-
// Adapt playbackRate to meet target latency in low-latency mode
|
4282
|
-
const {
|
4283
|
-
lowLatencyMode,
|
4284
|
-
maxLiveSyncPlaybackRate
|
4285
|
-
} = this.config;
|
4286
|
-
if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
|
4287
|
-
return;
|
4288
|
-
}
|
4289
|
-
const targetLatency = this.targetLatency;
|
4290
|
-
if (targetLatency === null) {
|
4291
|
-
return;
|
4292
|
-
}
|
4293
|
-
const distanceFromTarget = latency - targetLatency;
|
4294
|
-
// Only adjust playbackRate when within one target duration of targetLatency
|
4295
|
-
// and more than one second from under-buffering.
|
4296
|
-
// Playback further than one target duration from target can be considered DVR playback.
|
4297
|
-
const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
|
4298
|
-
const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
|
4299
|
-
if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
|
4300
|
-
const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
|
4301
|
-
const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
|
4302
|
-
media.playbackRate = Math.min(max, Math.max(1, rate));
|
4303
|
-
} else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
|
4304
|
-
media.playbackRate = 1;
|
4305
|
-
}
|
4306
|
-
};
|
4211
|
+
this.timeupdateHandler = () => this.timeupdate();
|
4307
4212
|
this.hls = hls;
|
4308
4213
|
this.config = hls.config;
|
4309
4214
|
this.registerListeners();
|
@@ -4395,7 +4300,7 @@ class LatencyController {
|
|
4395
4300
|
this.onMediaDetaching();
|
4396
4301
|
this.levelDetails = null;
|
4397
4302
|
// @ts-ignore
|
4398
|
-
this.hls = null;
|
4303
|
+
this.hls = this.timeupdateHandler = null;
|
4399
4304
|
}
|
4400
4305
|
registerListeners() {
|
4401
4306
|
this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
@@ -4413,11 +4318,11 @@ class LatencyController {
|
|
4413
4318
|
}
|
4414
4319
|
onMediaAttached(event, data) {
|
4415
4320
|
this.media = data.media;
|
4416
|
-
this.media.addEventListener('timeupdate', this.
|
4321
|
+
this.media.addEventListener('timeupdate', this.timeupdateHandler);
|
4417
4322
|
}
|
4418
4323
|
onMediaDetaching() {
|
4419
4324
|
if (this.media) {
|
4420
|
-
this.media.removeEventListener('timeupdate', this.
|
4325
|
+
this.media.removeEventListener('timeupdate', this.timeupdateHandler);
|
4421
4326
|
this.media = null;
|
4422
4327
|
}
|
4423
4328
|
}
|
@@ -4431,10 +4336,10 @@ class LatencyController {
|
|
4431
4336
|
}) {
|
4432
4337
|
this.levelDetails = details;
|
4433
4338
|
if (details.advanced) {
|
4434
|
-
this.
|
4339
|
+
this.timeupdate();
|
4435
4340
|
}
|
4436
4341
|
if (!details.live && this.media) {
|
4437
|
-
this.media.removeEventListener('timeupdate', this.
|
4342
|
+
this.media.removeEventListener('timeupdate', this.timeupdateHandler);
|
4438
4343
|
}
|
4439
4344
|
}
|
4440
4345
|
onError(event, data) {
|
@@ -4444,7 +4349,48 @@ class LatencyController {
|
|
4444
4349
|
}
|
4445
4350
|
this.stallCount++;
|
4446
4351
|
if ((_this$levelDetails = this.levelDetails) != null && _this$levelDetails.live) {
|
4447
|
-
|
4352
|
+
logger.warn('[playback-rate-controller]: Stall detected, adjusting target latency');
|
4353
|
+
}
|
4354
|
+
}
|
4355
|
+
timeupdate() {
|
4356
|
+
const {
|
4357
|
+
media,
|
4358
|
+
levelDetails
|
4359
|
+
} = this;
|
4360
|
+
if (!media || !levelDetails) {
|
4361
|
+
return;
|
4362
|
+
}
|
4363
|
+
this.currentTime = media.currentTime;
|
4364
|
+
const latency = this.computeLatency();
|
4365
|
+
if (latency === null) {
|
4366
|
+
return;
|
4367
|
+
}
|
4368
|
+
this._latency = latency;
|
4369
|
+
|
4370
|
+
// Adapt playbackRate to meet target latency in low-latency mode
|
4371
|
+
const {
|
4372
|
+
lowLatencyMode,
|
4373
|
+
maxLiveSyncPlaybackRate
|
4374
|
+
} = this.config;
|
4375
|
+
if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
|
4376
|
+
return;
|
4377
|
+
}
|
4378
|
+
const targetLatency = this.targetLatency;
|
4379
|
+
if (targetLatency === null) {
|
4380
|
+
return;
|
4381
|
+
}
|
4382
|
+
const distanceFromTarget = latency - targetLatency;
|
4383
|
+
// Only adjust playbackRate when within one target duration of targetLatency
|
4384
|
+
// and more than one second from under-buffering.
|
4385
|
+
// Playback further than one target duration from target can be considered DVR playback.
|
4386
|
+
const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
|
4387
|
+
const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
|
4388
|
+
if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
|
4389
|
+
const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
|
4390
|
+
const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
|
4391
|
+
media.playbackRate = Math.min(max, Math.max(1, rate));
|
4392
|
+
} else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
|
4393
|
+
media.playbackRate = 1;
|
4448
4394
|
}
|
4449
4395
|
}
|
4450
4396
|
estimateLiveEdge() {
|
@@ -5216,13 +5162,18 @@ var ErrorActionFlags = {
|
|
5216
5162
|
MoveAllAlternatesMatchingHDCP: 2,
|
5217
5163
|
SwitchToSDR: 4
|
5218
5164
|
}; // Reserved for future use
|
5219
|
-
class ErrorController
|
5165
|
+
class ErrorController {
|
5220
5166
|
constructor(hls) {
|
5221
|
-
super('error-controller', hls.logger);
|
5222
5167
|
this.hls = void 0;
|
5223
5168
|
this.playlistError = 0;
|
5224
5169
|
this.penalizedRenditions = {};
|
5170
|
+
this.log = void 0;
|
5171
|
+
this.warn = void 0;
|
5172
|
+
this.error = void 0;
|
5225
5173
|
this.hls = hls;
|
5174
|
+
this.log = logger.log.bind(logger, `[info]:`);
|
5175
|
+
this.warn = logger.warn.bind(logger, `[warning]:`);
|
5176
|
+
this.error = logger.error.bind(logger, `[error]:`);
|
5226
5177
|
this.registerListeners();
|
5227
5178
|
}
|
5228
5179
|
registerListeners() {
|
@@ -5574,13 +5525,16 @@ class ErrorController extends Logger {
|
|
5574
5525
|
}
|
5575
5526
|
}
|
5576
5527
|
|
5577
|
-
class BasePlaylistController
|
5528
|
+
class BasePlaylistController {
|
5578
5529
|
constructor(hls, logPrefix) {
|
5579
|
-
super(logPrefix, hls.logger);
|
5580
5530
|
this.hls = void 0;
|
5581
5531
|
this.timer = -1;
|
5582
5532
|
this.requestScheduled = -1;
|
5583
5533
|
this.canLoad = false;
|
5534
|
+
this.log = void 0;
|
5535
|
+
this.warn = void 0;
|
5536
|
+
this.log = logger.log.bind(logger, `${logPrefix}:`);
|
5537
|
+
this.warn = logger.warn.bind(logger, `${logPrefix}:`);
|
5584
5538
|
this.hls = hls;
|
5585
5539
|
}
|
5586
5540
|
destroy() {
|
@@ -5613,7 +5567,7 @@ class BasePlaylistController extends Logger {
|
|
5613
5567
|
try {
|
5614
5568
|
uri = new self.URL(attr.URI, previous.url).href;
|
5615
5569
|
} catch (error) {
|
5616
|
-
|
5570
|
+
logger.warn(`Could not construct new URL for Rendition Report: ${error}`);
|
5617
5571
|
uri = attr.URI || '';
|
5618
5572
|
}
|
5619
5573
|
// Use exact match. Otherwise, the last partial match, if any, will be used
|
@@ -6159,9 +6113,8 @@ function getCodecTiers(levels, audioTracksByGroup, minAutoLevel, maxAutoLevel) {
|
|
6159
6113
|
}, {});
|
6160
6114
|
}
|
6161
6115
|
|
6162
|
-
class AbrController
|
6116
|
+
class AbrController {
|
6163
6117
|
constructor(_hls) {
|
6164
|
-
super('abr', _hls.logger);
|
6165
6118
|
this.hls = void 0;
|
6166
6119
|
this.lastLevelLoadSec = 0;
|
6167
6120
|
this.lastLoadedFragLevel = -1;
|
@@ -6275,7 +6228,7 @@ class AbrController extends Logger {
|
|
6275
6228
|
this.resetEstimator(nextLoadLevelBitrate);
|
6276
6229
|
}
|
6277
6230
|
this.clearTimer();
|
6278
|
-
|
6231
|
+
logger.warn(`[abr] Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
|
6279
6232
|
Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s
|
6280
6233
|
Estimated load time for current fragment: ${fragLoadedDelay.toFixed(3)} s
|
6281
6234
|
Estimated load time for down switch fragment: ${fragLevelNextLoadedDelay.toFixed(3)} s
|
@@ -6295,7 +6248,7 @@ class AbrController extends Logger {
|
|
6295
6248
|
}
|
6296
6249
|
resetEstimator(abrEwmaDefaultEstimate) {
|
6297
6250
|
if (abrEwmaDefaultEstimate) {
|
6298
|
-
|
6251
|
+
logger.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
|
6299
6252
|
this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate;
|
6300
6253
|
}
|
6301
6254
|
this.firstSelection = -1;
|
@@ -6527,7 +6480,7 @@ class AbrController extends Logger {
|
|
6527
6480
|
}
|
6528
6481
|
const firstLevel = this.hls.firstLevel;
|
6529
6482
|
const clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel);
|
6530
|
-
|
6483
|
+
logger.warn(`[abr] Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
|
6531
6484
|
return clamped;
|
6532
6485
|
}
|
6533
6486
|
get forcedAutoLevel() {
|
@@ -6612,13 +6565,13 @@ class AbrController extends Logger {
|
|
6612
6565
|
// cap maxLoadingDelay and ensure it is not bigger 'than bitrate test' frag duration
|
6613
6566
|
const maxLoadingDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxLoadingDelay) : config.maxLoadingDelay;
|
6614
6567
|
maxStarvationDelay = maxLoadingDelay - bitrateTestDelay;
|
6615
|
-
|
6568
|
+
logger.info(`[abr] bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
|
6616
6569
|
// don't use conservative factor on bitrate test
|
6617
6570
|
bwFactor = bwUpFactor = 1;
|
6618
6571
|
}
|
6619
6572
|
}
|
6620
6573
|
const bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor);
|
6621
|
-
|
6574
|
+
logger.info(`[abr] ${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
|
6622
6575
|
if (bestLevel > -1) {
|
6623
6576
|
return bestLevel;
|
6624
6577
|
}
|
@@ -6680,7 +6633,7 @@ class AbrController extends Logger {
|
|
6680
6633
|
currentVideoRange = preferHDR ? videoRanges[videoRanges.length - 1] : videoRanges[0];
|
6681
6634
|
currentFrameRate = minFramerate;
|
6682
6635
|
currentBw = Math.max(currentBw, minBitrate);
|
6683
|
-
|
6636
|
+
logger.log(`[abr] picked start tier ${JSON.stringify(startTier)}`);
|
6684
6637
|
} else {
|
6685
6638
|
currentCodecSet = level == null ? void 0 : level.codecSet;
|
6686
6639
|
currentVideoRange = level == null ? void 0 : level.videoRange;
|
@@ -6689,7 +6642,7 @@ class AbrController extends Logger {
|
|
6689
6642
|
const ttfbEstimateSec = this.bwEstimator.getEstimateTTFB() / 1000;
|
6690
6643
|
const levelsSkipped = [];
|
6691
6644
|
for (let i = maxAutoLevel; i >= minAutoLevel; i--) {
|
6692
|
-
var _levelInfo$supportedR
|
6645
|
+
var _levelInfo$supportedR;
|
6693
6646
|
const levelInfo = levels[i];
|
6694
6647
|
const upSwitch = i > selectionBaseLevel;
|
6695
6648
|
if (!levelInfo) {
|
@@ -6698,7 +6651,7 @@ class AbrController extends Logger {
|
|
6698
6651
|
|
6699
6652
|
// skip candidates which change codec-family or video-range,
|
6700
6653
|
// and which decrease or increase frame-rate for up and down-switch respectfully
|
6701
|
-
if (currentCodecSet && levelInfo.codecSet !== currentCodecSet || currentVideoRange && levelInfo.videoRange !== currentVideoRange || upSwitch && currentFrameRate > levelInfo.frameRate || !upSwitch && currentFrameRate > 0 && currentFrameRate < levelInfo.frameRate ||
|
6654
|
+
if (currentCodecSet && levelInfo.codecSet !== currentCodecSet || currentVideoRange && levelInfo.videoRange !== currentVideoRange || upSwitch && currentFrameRate > levelInfo.frameRate || !upSwitch && currentFrameRate > 0 && currentFrameRate < levelInfo.frameRate || levelInfo.supportedResult && !((_levelInfo$supportedR = levelInfo.supportedResult.decodingInfoResults) != null && _levelInfo$supportedR[0].smooth)) {
|
6702
6655
|
levelsSkipped.push(i);
|
6703
6656
|
continue;
|
6704
6657
|
}
|
@@ -6733,9 +6686,9 @@ class AbrController extends Logger {
|
|
6733
6686
|
const forcedAutoLevel = this.forcedAutoLevel;
|
6734
6687
|
if (i !== loadLevel && (forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)) {
|
6735
6688
|
if (levelsSkipped.length) {
|
6736
|
-
|
6689
|
+
logger.trace(`[abr] Skipped level(s) ${levelsSkipped.join(',')} of ${maxAutoLevel} max with CODECS and VIDEO-RANGE:"${levels[levelsSkipped[0]].codecs}" ${levels[levelsSkipped[0]].videoRange}; not compatible with "${level.codecs}" ${currentVideoRange}`);
|
6737
6690
|
}
|
6738
|
-
|
6691
|
+
logger.info(`[abr] switch candidate:${selectionBaseLevel}->${i} adjustedbw(${Math.round(adjustedbw)})-bitrate=${Math.round(adjustedbw - bitrate)} ttfb:${ttfbEstimateSec.toFixed(1)} avgDuration:${avgDuration.toFixed(1)} maxFetchDuration:${maxFetchDuration.toFixed(1)} fetchDuration:${fetchDuration.toFixed(1)} firstSelection:${firstSelection} codecSet:${currentCodecSet} videoRange:${currentVideoRange} hls.loadLevel:${loadLevel}`);
|
6739
6692
|
}
|
6740
6693
|
if (firstSelection) {
|
6741
6694
|
this.firstSelection = i;
|
@@ -6967,9 +6920,8 @@ class BufferOperationQueue {
|
|
6967
6920
|
}
|
6968
6921
|
|
6969
6922
|
const VIDEO_CODEC_PROFILE_REPLACE = /(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\.[^.,]+)+/;
|
6970
|
-
class BufferController
|
6923
|
+
class BufferController {
|
6971
6924
|
constructor(hls) {
|
6972
|
-
super('buffer-controller', hls.logger);
|
6973
6925
|
// The level details used to determine duration, target-duration and live
|
6974
6926
|
this.details = null;
|
6975
6927
|
// cache the self generated object url to detect hijack of video tag
|
@@ -6999,6 +6951,9 @@ class BufferController extends Logger {
|
|
6999
6951
|
this.tracks = {};
|
7000
6952
|
this.pendingTracks = {};
|
7001
6953
|
this.sourceBuffer = void 0;
|
6954
|
+
this.log = void 0;
|
6955
|
+
this.warn = void 0;
|
6956
|
+
this.error = void 0;
|
7002
6957
|
this._onEndStreaming = event => {
|
7003
6958
|
if (!this.hls) {
|
7004
6959
|
return;
|
@@ -7044,11 +6999,15 @@ class BufferController extends Logger {
|
|
7044
6999
|
_objectUrl
|
7045
7000
|
} = this;
|
7046
7001
|
if (mediaSrc !== _objectUrl) {
|
7047
|
-
|
7002
|
+
logger.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
|
7048
7003
|
}
|
7049
7004
|
};
|
7050
7005
|
this.hls = hls;
|
7006
|
+
const logPrefix = '[buffer-controller]';
|
7051
7007
|
this.appendSource = hls.config.preferManagedMediaSource;
|
7008
|
+
this.log = logger.log.bind(logger, logPrefix);
|
7009
|
+
this.warn = logger.warn.bind(logger, logPrefix);
|
7010
|
+
this.error = logger.error.bind(logger, logPrefix);
|
7052
7011
|
this._initSourceBuffer();
|
7053
7012
|
this.registerListeners();
|
7054
7013
|
}
|
@@ -7061,12 +7020,6 @@ class BufferController extends Logger {
|
|
7061
7020
|
this.lastMpegAudioChunk = null;
|
7062
7021
|
// @ts-ignore
|
7063
7022
|
this.hls = null;
|
7064
|
-
// @ts-ignore
|
7065
|
-
this._onMediaSourceOpen = this._onMediaSourceClose = null;
|
7066
|
-
// @ts-ignore
|
7067
|
-
this._onMediaSourceEnded = null;
|
7068
|
-
// @ts-ignore
|
7069
|
-
this._onStartStreaming = this._onEndStreaming = null;
|
7070
7023
|
}
|
7071
7024
|
registerListeners() {
|
7072
7025
|
const {
|
@@ -8066,7 +8019,7 @@ class CapLevelController {
|
|
8066
8019
|
const hls = this.hls;
|
8067
8020
|
const maxLevel = this.getMaxLevel(levels.length - 1);
|
8068
8021
|
if (maxLevel !== this.autoLevelCapping) {
|
8069
|
-
|
8022
|
+
logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
|
8070
8023
|
}
|
8071
8024
|
hls.autoLevelCapping = maxLevel;
|
8072
8025
|
if (hls.autoLevelCapping > this.autoLevelCapping && this.streamController) {
|
@@ -8244,10 +8197,10 @@ class FPSController {
|
|
8244
8197
|
totalDroppedFrames: droppedFrames
|
8245
8198
|
});
|
8246
8199
|
if (droppedFPS > 0) {
|
8247
|
-
//
|
8200
|
+
// logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
|
8248
8201
|
if (currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
|
8249
8202
|
let currentLevel = hls.currentLevel;
|
8250
|
-
|
8203
|
+
logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
|
8251
8204
|
if (currentLevel > 0 && (hls.autoLevelCapping === -1 || hls.autoLevelCapping >= currentLevel)) {
|
8252
8205
|
currentLevel = currentLevel - 1;
|
8253
8206
|
hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, {
|
@@ -8280,10 +8233,10 @@ class FPSController {
|
|
8280
8233
|
}
|
8281
8234
|
|
8282
8235
|
const PATHWAY_PENALTY_DURATION_MS = 300000;
|
8283
|
-
class ContentSteeringController
|
8236
|
+
class ContentSteeringController {
|
8284
8237
|
constructor(hls) {
|
8285
|
-
super('content-steering', hls.logger);
|
8286
8238
|
this.hls = void 0;
|
8239
|
+
this.log = void 0;
|
8287
8240
|
this.loader = null;
|
8288
8241
|
this.uri = null;
|
8289
8242
|
this.pathwayId = '.';
|
@@ -8298,6 +8251,7 @@ class ContentSteeringController extends Logger {
|
|
8298
8251
|
this.subtitleTracks = null;
|
8299
8252
|
this.penalizedPathways = {};
|
8300
8253
|
this.hls = hls;
|
8254
|
+
this.log = logger.log.bind(logger, `[content-steering]:`);
|
8301
8255
|
this.registerListeners();
|
8302
8256
|
}
|
8303
8257
|
registerListeners() {
|
@@ -8421,7 +8375,7 @@ class ContentSteeringController extends Logger {
|
|
8421
8375
|
errorAction.resolved = this.pathwayId !== errorPathway;
|
8422
8376
|
}
|
8423
8377
|
if (!errorAction.resolved) {
|
8424
|
-
|
8378
|
+
logger.warn(`Could not resolve ${data.details} ("${data.error.message}") with content-steering for Pathway: ${errorPathway} levels: ${levels ? levels.length : levels} priorities: ${JSON.stringify(pathwayPriority)} penalized: ${JSON.stringify(this.penalizedPathways)}`);
|
8425
8379
|
}
|
8426
8380
|
}
|
8427
8381
|
}
|
@@ -8592,7 +8546,7 @@ class ContentSteeringController extends Logger {
|
|
8592
8546
|
onSuccess: (response, stats, context, networkDetails) => {
|
8593
8547
|
this.log(`Loaded steering manifest: "${url}"`);
|
8594
8548
|
const steeringData = response.data;
|
8595
|
-
if (
|
8549
|
+
if (steeringData.VERSION !== 1) {
|
8596
8550
|
this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
|
8597
8551
|
return;
|
8598
8552
|
}
|
@@ -9531,7 +9485,7 @@ function timelineConfig() {
|
|
9531
9485
|
/**
|
9532
9486
|
* @ignore
|
9533
9487
|
*/
|
9534
|
-
function mergeConfig(defaultConfig, userConfig
|
9488
|
+
function mergeConfig(defaultConfig, userConfig) {
|
9535
9489
|
if ((userConfig.liveSyncDurationCount || userConfig.liveMaxLatencyDurationCount) && (userConfig.liveSyncDuration || userConfig.liveMaxLatencyDuration)) {
|
9536
9490
|
throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration");
|
9537
9491
|
}
|
@@ -9601,7 +9555,7 @@ function deepCpy(obj) {
|
|
9601
9555
|
/**
|
9602
9556
|
* @ignore
|
9603
9557
|
*/
|
9604
|
-
function enableStreamingMode(config
|
9558
|
+
function enableStreamingMode(config) {
|
9605
9559
|
const currentLoader = config.loader;
|
9606
9560
|
if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
|
9607
9561
|
// If a developer has configured their own loader, respect that choice
|
@@ -9618,9 +9572,10 @@ function enableStreamingMode(config, logger) {
|
|
9618
9572
|
}
|
9619
9573
|
}
|
9620
9574
|
|
9575
|
+
let chromeOrFirefox;
|
9621
9576
|
class LevelController extends BasePlaylistController {
|
9622
9577
|
constructor(hls, contentSteeringController) {
|
9623
|
-
super(hls, 'level-controller');
|
9578
|
+
super(hls, '[level-controller]');
|
9624
9579
|
this._levels = [];
|
9625
9580
|
this._firstLevel = -1;
|
9626
9581
|
this._maxAutoLevel = -1;
|
@@ -9691,15 +9646,23 @@ class LevelController extends BasePlaylistController {
|
|
9691
9646
|
let videoCodecFound = false;
|
9692
9647
|
let audioCodecFound = false;
|
9693
9648
|
data.levels.forEach(levelParsed => {
|
9694
|
-
var _videoCodec;
|
9649
|
+
var _audioCodec, _videoCodec;
|
9695
9650
|
const attributes = levelParsed.attrs;
|
9651
|
+
|
9652
|
+
// erase audio codec info if browser does not support mp4a.40.34.
|
9653
|
+
// demuxer will autodetect codec and fallback to mpeg/audio
|
9696
9654
|
let {
|
9697
9655
|
audioCodec,
|
9698
9656
|
videoCodec
|
9699
9657
|
} = levelParsed;
|
9658
|
+
if (((_audioCodec = audioCodec) == null ? void 0 : _audioCodec.indexOf('mp4a.40.34')) !== -1) {
|
9659
|
+
chromeOrFirefox || (chromeOrFirefox = /chrome|firefox/i.test(navigator.userAgent));
|
9660
|
+
if (chromeOrFirefox) {
|
9661
|
+
levelParsed.audioCodec = audioCodec = undefined;
|
9662
|
+
}
|
9663
|
+
}
|
9700
9664
|
if (audioCodec) {
|
9701
|
-
|
9702
|
-
levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource) || undefined;
|
9665
|
+
levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource);
|
9703
9666
|
}
|
9704
9667
|
if (((_videoCodec = videoCodec) == null ? void 0 : _videoCodec.indexOf('avc1')) === 0) {
|
9705
9668
|
videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
|
@@ -10827,8 +10790,8 @@ function createLoaderContext(frag, part = null) {
|
|
10827
10790
|
var _frag$decryptdata;
|
10828
10791
|
let byteRangeStart = start;
|
10829
10792
|
let byteRangeEnd = end;
|
10830
|
-
if (frag.sn === 'initSegment' &&
|
10831
|
-
// MAP segment encrypted with method 'AES-128'
|
10793
|
+
if (frag.sn === 'initSegment' && ((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method) === 'AES-128') {
|
10794
|
+
// MAP segment encrypted with method 'AES-128', when served with HTTP Range,
|
10832
10795
|
// has the unencrypted size specified in the range.
|
10833
10796
|
// Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
|
10834
10797
|
const fragmentLen = end - start;
|
@@ -10861,9 +10824,6 @@ function createGapLoadError(frag, part) {
|
|
10861
10824
|
(part ? part : frag).stats.aborted = true;
|
10862
10825
|
return new LoadError(errorData);
|
10863
10826
|
}
|
10864
|
-
function isMethodFullSegmentAesCbc(method) {
|
10865
|
-
return method === 'AES-128' || method === 'AES-256';
|
10866
|
-
}
|
10867
10827
|
class LoadError extends Error {
|
10868
10828
|
constructor(data) {
|
10869
10829
|
super(data.error.message);
|
@@ -11009,8 +10969,6 @@ class KeyLoader {
|
|
11009
10969
|
}
|
11010
10970
|
return this.loadKeyEME(keyInfo, frag);
|
11011
10971
|
case 'AES-128':
|
11012
|
-
case 'AES-256':
|
11013
|
-
case 'AES-256-CTR':
|
11014
10972
|
return this.loadKeyHTTP(keyInfo, frag);
|
11015
10973
|
default:
|
11016
10974
|
return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`)));
|
@@ -11146,9 +11104,8 @@ class KeyLoader {
|
|
11146
11104
|
* we are limiting the task execution per call stack to exactly one, but scheduling/post-poning further
|
11147
11105
|
* task processing on the next main loop iteration (also known as "next tick" in the Node/JS runtime lingo).
|
11148
11106
|
*/
|
11149
|
-
class TaskLoop
|
11150
|
-
constructor(
|
11151
|
-
super(label, logger);
|
11107
|
+
class TaskLoop {
|
11108
|
+
constructor() {
|
11152
11109
|
this._boundTick = void 0;
|
11153
11110
|
this._tickTimer = null;
|
11154
11111
|
this._tickInterval = null;
|
@@ -11416,61 +11373,33 @@ function alignMediaPlaylistByPDT(details, refDetails) {
|
|
11416
11373
|
}
|
11417
11374
|
|
11418
11375
|
class AESCrypto {
|
11419
|
-
constructor(subtle, iv
|
11376
|
+
constructor(subtle, iv) {
|
11420
11377
|
this.subtle = void 0;
|
11421
11378
|
this.aesIV = void 0;
|
11422
|
-
this.aesMode = void 0;
|
11423
11379
|
this.subtle = subtle;
|
11424
11380
|
this.aesIV = iv;
|
11425
|
-
this.aesMode = aesMode;
|
11426
11381
|
}
|
11427
11382
|
decrypt(data, key) {
|
11428
|
-
|
11429
|
-
|
11430
|
-
|
11431
|
-
|
11432
|
-
iv: this.aesIV
|
11433
|
-
}, key, data);
|
11434
|
-
case DecrypterAesMode.ctr:
|
11435
|
-
return this.subtle.decrypt({
|
11436
|
-
name: 'AES-CTR',
|
11437
|
-
counter: this.aesIV,
|
11438
|
-
length: 64
|
11439
|
-
},
|
11440
|
-
//64 : NIST SP800-38A standard suggests that the counter should occupy half of the counter block
|
11441
|
-
key, data);
|
11442
|
-
default:
|
11443
|
-
throw new Error(`[AESCrypto] invalid aes mode ${this.aesMode}`);
|
11444
|
-
}
|
11383
|
+
return this.subtle.decrypt({
|
11384
|
+
name: 'AES-CBC',
|
11385
|
+
iv: this.aesIV
|
11386
|
+
}, key, data);
|
11445
11387
|
}
|
11446
11388
|
}
|
11447
11389
|
|
11448
11390
|
class FastAESKey {
|
11449
|
-
constructor(subtle, key
|
11391
|
+
constructor(subtle, key) {
|
11450
11392
|
this.subtle = void 0;
|
11451
11393
|
this.key = void 0;
|
11452
|
-
this.aesMode = void 0;
|
11453
11394
|
this.subtle = subtle;
|
11454
11395
|
this.key = key;
|
11455
|
-
this.aesMode = aesMode;
|
11456
11396
|
}
|
11457
11397
|
expandKey() {
|
11458
|
-
const subtleAlgoName = getSubtleAlgoName(this.aesMode);
|
11459
11398
|
return this.subtle.importKey('raw', this.key, {
|
11460
|
-
name:
|
11399
|
+
name: 'AES-CBC'
|
11461
11400
|
}, false, ['encrypt', 'decrypt']);
|
11462
11401
|
}
|
11463
11402
|
}
|
11464
|
-
function getSubtleAlgoName(aesMode) {
|
11465
|
-
switch (aesMode) {
|
11466
|
-
case DecrypterAesMode.cbc:
|
11467
|
-
return 'AES-CBC';
|
11468
|
-
case DecrypterAesMode.ctr:
|
11469
|
-
return 'AES-CTR';
|
11470
|
-
default:
|
11471
|
-
throw new Error(`[FastAESKey] invalid aes mode ${aesMode}`);
|
11472
|
-
}
|
11473
|
-
}
|
11474
11403
|
|
11475
11404
|
// PKCS7
|
11476
11405
|
function removePadding(array) {
|
@@ -11720,8 +11649,7 @@ class Decrypter {
|
|
11720
11649
|
this.currentIV = null;
|
11721
11650
|
this.currentResult = null;
|
11722
11651
|
this.useSoftware = void 0;
|
11723
|
-
this.
|
11724
|
-
this.enableSoftwareAES = config.enableSoftwareAES;
|
11652
|
+
this.useSoftware = config.enableSoftwareAES;
|
11725
11653
|
this.removePKCS7Padding = removePKCS7Padding;
|
11726
11654
|
// built in decryptor expects PKCS7 padding
|
11727
11655
|
if (removePKCS7Padding) {
|
@@ -11734,7 +11662,9 @@ class Decrypter {
|
|
11734
11662
|
/* no-op */
|
11735
11663
|
}
|
11736
11664
|
}
|
11737
|
-
|
11665
|
+
if (this.subtle === null) {
|
11666
|
+
this.useSoftware = true;
|
11667
|
+
}
|
11738
11668
|
}
|
11739
11669
|
destroy() {
|
11740
11670
|
this.subtle = null;
|
@@ -11772,10 +11702,10 @@ class Decrypter {
|
|
11772
11702
|
this.softwareDecrypter = null;
|
11773
11703
|
}
|
11774
11704
|
}
|
11775
|
-
decrypt(data, key, iv
|
11705
|
+
decrypt(data, key, iv) {
|
11776
11706
|
if (this.useSoftware) {
|
11777
11707
|
return new Promise((resolve, reject) => {
|
11778
|
-
this.softwareDecrypt(new Uint8Array(data), key, iv
|
11708
|
+
this.softwareDecrypt(new Uint8Array(data), key, iv);
|
11779
11709
|
const decryptResult = this.flush();
|
11780
11710
|
if (decryptResult) {
|
11781
11711
|
resolve(decryptResult.buffer);
|
@@ -11784,21 +11714,17 @@ class Decrypter {
|
|
11784
11714
|
}
|
11785
11715
|
});
|
11786
11716
|
}
|
11787
|
-
return this.webCryptoDecrypt(new Uint8Array(data), key, iv
|
11717
|
+
return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
|
11788
11718
|
}
|
11789
11719
|
|
11790
11720
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
11791
11721
|
// data is handled in the flush() call
|
11792
|
-
softwareDecrypt(data, key, iv
|
11722
|
+
softwareDecrypt(data, key, iv) {
|
11793
11723
|
const {
|
11794
11724
|
currentIV,
|
11795
11725
|
currentResult,
|
11796
11726
|
remainderData
|
11797
11727
|
} = this;
|
11798
|
-
if (aesMode !== DecrypterAesMode.cbc || key.byteLength !== 16) {
|
11799
|
-
logger.warn('SoftwareDecrypt: can only handle AES-128-CBC');
|
11800
|
-
return null;
|
11801
|
-
}
|
11802
11728
|
this.logOnce('JS AES decrypt');
|
11803
11729
|
// The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
|
11804
11730
|
// This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached
|
@@ -11831,11 +11757,11 @@ class Decrypter {
|
|
11831
11757
|
}
|
11832
11758
|
return result;
|
11833
11759
|
}
|
11834
|
-
webCryptoDecrypt(data, key, iv
|
11760
|
+
webCryptoDecrypt(data, key, iv) {
|
11835
11761
|
const subtle = this.subtle;
|
11836
11762
|
if (this.key !== key || !this.fastAesKey) {
|
11837
11763
|
this.key = key;
|
11838
|
-
this.fastAesKey = new FastAESKey(subtle, key
|
11764
|
+
this.fastAesKey = new FastAESKey(subtle, key);
|
11839
11765
|
}
|
11840
11766
|
return this.fastAesKey.expandKey().then(aesKey => {
|
11841
11767
|
// decrypt using web crypto
|
@@ -11843,25 +11769,22 @@ class Decrypter {
|
|
11843
11769
|
return Promise.reject(new Error('web crypto not initialized'));
|
11844
11770
|
}
|
11845
11771
|
this.logOnce('WebCrypto AES decrypt');
|
11846
|
-
const crypto = new AESCrypto(subtle, new Uint8Array(iv)
|
11772
|
+
const crypto = new AESCrypto(subtle, new Uint8Array(iv));
|
11847
11773
|
return crypto.decrypt(data.buffer, aesKey);
|
11848
11774
|
}).catch(err => {
|
11849
11775
|
logger.warn(`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`);
|
11850
|
-
return this.onWebCryptoError(data, key, iv
|
11776
|
+
return this.onWebCryptoError(data, key, iv);
|
11851
11777
|
});
|
11852
11778
|
}
|
11853
|
-
onWebCryptoError(data, key, iv
|
11854
|
-
|
11855
|
-
|
11856
|
-
|
11857
|
-
|
11858
|
-
|
11859
|
-
|
11860
|
-
if (decryptResult) {
|
11861
|
-
return decryptResult.buffer;
|
11862
|
-
}
|
11779
|
+
onWebCryptoError(data, key, iv) {
|
11780
|
+
this.useSoftware = true;
|
11781
|
+
this.logEnabled = true;
|
11782
|
+
this.softwareDecrypt(data, key, iv);
|
11783
|
+
const decryptResult = this.flush();
|
11784
|
+
if (decryptResult) {
|
11785
|
+
return decryptResult.buffer;
|
11863
11786
|
}
|
11864
|
-
throw new Error('WebCrypto
|
11787
|
+
throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data');
|
11865
11788
|
}
|
11866
11789
|
getValidChunk(data) {
|
11867
11790
|
let currentChunk = data;
|
@@ -11912,7 +11835,7 @@ const State = {
|
|
11912
11835
|
};
|
11913
11836
|
class BaseStreamController extends TaskLoop {
|
11914
11837
|
constructor(hls, fragmentTracker, keyLoader, logPrefix, playlistType) {
|
11915
|
-
super(
|
11838
|
+
super();
|
11916
11839
|
this.hls = void 0;
|
11917
11840
|
this.fragPrevious = null;
|
11918
11841
|
this.fragCurrent = null;
|
@@ -11937,88 +11860,22 @@ class BaseStreamController extends TaskLoop {
|
|
11937
11860
|
this.startFragRequested = false;
|
11938
11861
|
this.decrypter = void 0;
|
11939
11862
|
this.initPTS = [];
|
11940
|
-
this.
|
11941
|
-
|
11942
|
-
|
11943
|
-
|
11944
|
-
|
11945
|
-
mediaBuffer,
|
11946
|
-
state
|
11947
|
-
} = this;
|
11948
|
-
const currentTime = media ? media.currentTime : 0;
|
11949
|
-
const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
|
11950
|
-
this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
|
11951
|
-
if (this.state === State.ENDED) {
|
11952
|
-
this.resetLoadingState();
|
11953
|
-
} else if (fragCurrent) {
|
11954
|
-
// Seeking while frag load is in progress
|
11955
|
-
const tolerance = config.maxFragLookUpTolerance;
|
11956
|
-
const fragStartOffset = fragCurrent.start - tolerance;
|
11957
|
-
const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
|
11958
|
-
// if seeking out of buffered range or into new one
|
11959
|
-
if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
|
11960
|
-
const pastFragment = currentTime > fragEndOffset;
|
11961
|
-
// if the seek position is outside the current fragment range
|
11962
|
-
if (currentTime < fragStartOffset || pastFragment) {
|
11963
|
-
if (pastFragment && fragCurrent.loader) {
|
11964
|
-
this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
|
11965
|
-
fragCurrent.abortRequests();
|
11966
|
-
this.resetLoadingState();
|
11967
|
-
}
|
11968
|
-
this.fragPrevious = null;
|
11969
|
-
}
|
11970
|
-
}
|
11971
|
-
}
|
11972
|
-
if (media) {
|
11973
|
-
// Remove gap fragments
|
11974
|
-
this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
|
11975
|
-
this.lastCurrentTime = currentTime;
|
11976
|
-
}
|
11977
|
-
|
11978
|
-
// in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
|
11979
|
-
if (!this.loadedmetadata && !bufferInfo.len) {
|
11980
|
-
this.nextLoadPosition = this.startPosition = currentTime;
|
11981
|
-
}
|
11982
|
-
|
11983
|
-
// Async tick to speed up processing
|
11984
|
-
this.tickImmediate();
|
11985
|
-
};
|
11986
|
-
this.onMediaEnded = () => {
|
11987
|
-
// reset startPosition and lastCurrentTime to restart playback @ stream beginning
|
11988
|
-
this.startPosition = this.lastCurrentTime = 0;
|
11989
|
-
if (this.playlistType === PlaylistLevelType.MAIN) {
|
11990
|
-
this.hls.trigger(Events.MEDIA_ENDED, {
|
11991
|
-
stalled: false
|
11992
|
-
});
|
11993
|
-
}
|
11994
|
-
};
|
11863
|
+
this.onvseeking = null;
|
11864
|
+
this.onvended = null;
|
11865
|
+
this.logPrefix = '';
|
11866
|
+
this.log = void 0;
|
11867
|
+
this.warn = void 0;
|
11995
11868
|
this.playlistType = playlistType;
|
11869
|
+
this.logPrefix = logPrefix;
|
11870
|
+
this.log = logger.log.bind(logger, `${logPrefix}:`);
|
11871
|
+
this.warn = logger.warn.bind(logger, `${logPrefix}:`);
|
11996
11872
|
this.hls = hls;
|
11997
11873
|
this.fragmentLoader = new FragmentLoader(hls.config);
|
11998
11874
|
this.keyLoader = keyLoader;
|
11999
11875
|
this.fragmentTracker = fragmentTracker;
|
12000
11876
|
this.config = hls.config;
|
12001
11877
|
this.decrypter = new Decrypter(hls.config);
|
12002
|
-
}
|
12003
|
-
registerListeners() {
|
12004
|
-
const {
|
12005
|
-
hls
|
12006
|
-
} = this;
|
12007
|
-
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
12008
|
-
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
12009
|
-
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
12010
11878
|
hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
12011
|
-
hls.on(Events.ERROR, this.onError, this);
|
12012
|
-
}
|
12013
|
-
unregisterListeners() {
|
12014
|
-
const {
|
12015
|
-
hls
|
12016
|
-
} = this;
|
12017
|
-
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
12018
|
-
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
12019
|
-
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
12020
|
-
hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
12021
|
-
hls.off(Events.ERROR, this.onError, this);
|
12022
11879
|
}
|
12023
11880
|
doTick() {
|
12024
11881
|
this.onTickEnd();
|
@@ -12072,8 +11929,10 @@ class BaseStreamController extends TaskLoop {
|
|
12072
11929
|
}
|
12073
11930
|
onMediaAttached(event, data) {
|
12074
11931
|
const media = this.media = this.mediaBuffer = data.media;
|
12075
|
-
|
12076
|
-
|
11932
|
+
this.onvseeking = this.onMediaSeeking.bind(this);
|
11933
|
+
this.onvended = this.onMediaEnded.bind(this);
|
11934
|
+
media.addEventListener('seeking', this.onvseeking);
|
11935
|
+
media.addEventListener('ended', this.onvended);
|
12077
11936
|
const config = this.config;
|
12078
11937
|
if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
|
12079
11938
|
this.startLoad(config.startPosition);
|
@@ -12087,9 +11946,10 @@ class BaseStreamController extends TaskLoop {
|
|
12087
11946
|
}
|
12088
11947
|
|
12089
11948
|
// remove video listeners
|
12090
|
-
if (media) {
|
12091
|
-
media.removeEventListener('seeking', this.
|
12092
|
-
media.removeEventListener('ended', this.
|
11949
|
+
if (media && this.onvseeking && this.onvended) {
|
11950
|
+
media.removeEventListener('seeking', this.onvseeking);
|
11951
|
+
media.removeEventListener('ended', this.onvended);
|
11952
|
+
this.onvseeking = this.onvended = null;
|
12093
11953
|
}
|
12094
11954
|
if (this.keyLoader) {
|
12095
11955
|
this.keyLoader.detach();
|
@@ -12099,8 +11959,56 @@ class BaseStreamController extends TaskLoop {
|
|
12099
11959
|
this.fragmentTracker.removeAllFragments();
|
12100
11960
|
this.stopLoad();
|
12101
11961
|
}
|
12102
|
-
|
12103
|
-
|
11962
|
+
onMediaSeeking() {
|
11963
|
+
const {
|
11964
|
+
config,
|
11965
|
+
fragCurrent,
|
11966
|
+
media,
|
11967
|
+
mediaBuffer,
|
11968
|
+
state
|
11969
|
+
} = this;
|
11970
|
+
const currentTime = media ? media.currentTime : 0;
|
11971
|
+
const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
|
11972
|
+
this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
|
11973
|
+
if (this.state === State.ENDED) {
|
11974
|
+
this.resetLoadingState();
|
11975
|
+
} else if (fragCurrent) {
|
11976
|
+
// Seeking while frag load is in progress
|
11977
|
+
const tolerance = config.maxFragLookUpTolerance;
|
11978
|
+
const fragStartOffset = fragCurrent.start - tolerance;
|
11979
|
+
const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
|
11980
|
+
// if seeking out of buffered range or into new one
|
11981
|
+
if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
|
11982
|
+
const pastFragment = currentTime > fragEndOffset;
|
11983
|
+
// if the seek position is outside the current fragment range
|
11984
|
+
if (currentTime < fragStartOffset || pastFragment) {
|
11985
|
+
if (pastFragment && fragCurrent.loader) {
|
11986
|
+
this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
|
11987
|
+
fragCurrent.abortRequests();
|
11988
|
+
this.resetLoadingState();
|
11989
|
+
}
|
11990
|
+
this.fragPrevious = null;
|
11991
|
+
}
|
11992
|
+
}
|
11993
|
+
}
|
11994
|
+
if (media) {
|
11995
|
+
// Remove gap fragments
|
11996
|
+
this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
|
11997
|
+
this.lastCurrentTime = currentTime;
|
11998
|
+
}
|
11999
|
+
|
12000
|
+
// in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
|
12001
|
+
if (!this.loadedmetadata && !bufferInfo.len) {
|
12002
|
+
this.nextLoadPosition = this.startPosition = currentTime;
|
12003
|
+
}
|
12004
|
+
|
12005
|
+
// Async tick to speed up processing
|
12006
|
+
this.tickImmediate();
|
12007
|
+
}
|
12008
|
+
onMediaEnded() {
|
12009
|
+
// reset startPosition and lastCurrentTime to restart playback @ stream beginning
|
12010
|
+
this.startPosition = this.lastCurrentTime = 0;
|
12011
|
+
}
|
12104
12012
|
onManifestLoaded(event, data) {
|
12105
12013
|
this.startTimeOffset = data.startTimeOffset;
|
12106
12014
|
this.initPTS = [];
|
@@ -12110,7 +12018,7 @@ class BaseStreamController extends TaskLoop {
|
|
12110
12018
|
this.stopLoad();
|
12111
12019
|
super.onHandlerDestroying();
|
12112
12020
|
// @ts-ignore
|
12113
|
-
this.hls =
|
12021
|
+
this.hls = null;
|
12114
12022
|
}
|
12115
12023
|
onHandlerDestroyed() {
|
12116
12024
|
this.state = State.STOPPED;
|
@@ -12241,10 +12149,10 @@ class BaseStreamController extends TaskLoop {
|
|
12241
12149
|
const decryptData = frag.decryptdata;
|
12242
12150
|
|
12243
12151
|
// check to see if the payload needs to be decrypted
|
12244
|
-
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv &&
|
12152
|
+
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
|
12245
12153
|
const startTime = self.performance.now();
|
12246
12154
|
// decrypt init segment data
|
12247
|
-
return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer
|
12155
|
+
return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
|
12248
12156
|
hls.trigger(Events.ERROR, {
|
12249
12157
|
type: ErrorTypes.MEDIA_ERROR,
|
12250
12158
|
details: ErrorDetails.FRAG_DECRYPT_ERROR,
|
@@ -12356,7 +12264,7 @@ class BaseStreamController extends TaskLoop {
|
|
12356
12264
|
}
|
12357
12265
|
let keyLoadingPromise = null;
|
12358
12266
|
if (frag.encrypted && !((_frag$decryptdata = frag.decryptdata) != null && _frag$decryptdata.key)) {
|
12359
|
-
this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.
|
12267
|
+
this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.logPrefix === '[stream-controller]' ? 'level' : 'track'} ${frag.level}`);
|
12360
12268
|
this.state = State.KEY_LOADING;
|
12361
12269
|
this.fragCurrent = frag;
|
12362
12270
|
keyLoadingPromise = this.keyLoader.load(frag).then(keyLoadedData => {
|
@@ -12387,7 +12295,7 @@ class BaseStreamController extends TaskLoop {
|
|
12387
12295
|
const partIndex = this.getNextPart(partList, frag, targetBufferTime);
|
12388
12296
|
if (partIndex > -1) {
|
12389
12297
|
const part = partList[partIndex];
|
12390
|
-
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.
|
12298
|
+
this.log(`Loading part sn: ${frag.sn} p: ${part.index} cc: ${frag.cc} of playlist [${details.startSN}-${details.endSN}] parts [0-${partIndex}-${partList.length - 1}] ${this.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
|
12391
12299
|
this.nextLoadPosition = part.start + part.duration;
|
12392
12300
|
this.state = State.FRAG_LOADING;
|
12393
12301
|
let _result;
|
@@ -12416,7 +12324,7 @@ class BaseStreamController extends TaskLoop {
|
|
12416
12324
|
}
|
12417
12325
|
}
|
12418
12326
|
}
|
12419
|
-
this.log(`Loading fragment ${frag.sn} cc: ${frag.cc} ${details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''}${this.
|
12327
|
+
this.log(`Loading fragment ${frag.sn} cc: ${frag.cc} ${details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''}${this.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
|
12420
12328
|
// Don't update nextLoadPosition for fragments which are not buffered
|
12421
12329
|
if (isFiniteNumber(frag.sn) && !this.bitrateTest) {
|
12422
12330
|
this.nextLoadPosition = frag.start + frag.duration;
|
@@ -13001,7 +12909,7 @@ class BaseStreamController extends TaskLoop {
|
|
13001
12909
|
errorAction.resolved = true;
|
13002
12910
|
}
|
13003
12911
|
} else {
|
13004
|
-
|
12912
|
+
logger.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
|
13005
12913
|
return;
|
13006
12914
|
}
|
13007
12915
|
} else if ((errorAction == null ? void 0 : errorAction.action) === NetworkErrorAction.SendAlternateToPenaltyBox) {
|
@@ -13396,7 +13304,6 @@ const initPTSFn = (timestamp, timeOffset, initPTS) => {
|
|
13396
13304
|
*/
|
13397
13305
|
function getAudioConfig(observer, data, offset, audioCodec) {
|
13398
13306
|
let adtsObjectType;
|
13399
|
-
let originalAdtsObjectType;
|
13400
13307
|
let adtsExtensionSamplingIndex;
|
13401
13308
|
let adtsChannelConfig;
|
13402
13309
|
let config;
|
@@ -13404,7 +13311,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
13404
13311
|
const manifestCodec = audioCodec;
|
13405
13312
|
const adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
|
13406
13313
|
// byte 2
|
13407
|
-
adtsObjectType =
|
13314
|
+
adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
|
13408
13315
|
const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
|
13409
13316
|
if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
|
13410
13317
|
const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
|
@@ -13421,8 +13328,8 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
13421
13328
|
// byte 3
|
13422
13329
|
adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6;
|
13423
13330
|
logger.log(`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`);
|
13424
|
-
//
|
13425
|
-
if (/firefox
|
13331
|
+
// firefox: freq less than 24kHz = AAC SBR (HE-AAC)
|
13332
|
+
if (/firefox/i.test(userAgent)) {
|
13426
13333
|
if (adtsSamplingIndex >= 6) {
|
13427
13334
|
adtsObjectType = 5;
|
13428
13335
|
config = new Array(4);
|
@@ -13516,7 +13423,6 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
13516
13423
|
samplerate: adtsSamplingRates[adtsSamplingIndex],
|
13517
13424
|
channelCount: adtsChannelConfig,
|
13518
13425
|
codec: 'mp4a.40.' + adtsObjectType,
|
13519
|
-
parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
|
13520
13426
|
manifestCodec
|
13521
13427
|
};
|
13522
13428
|
}
|
@@ -13571,8 +13477,7 @@ function initTrackConfig(track, observer, data, offset, audioCodec) {
|
|
13571
13477
|
track.channelCount = config.channelCount;
|
13572
13478
|
track.codec = config.codec;
|
13573
13479
|
track.manifestCodec = config.manifestCodec;
|
13574
|
-
track.
|
13575
|
-
logger.log(`parsed codec:${track.parsedCodec}, codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
|
13480
|
+
logger.log(`parsed codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
|
13576
13481
|
}
|
13577
13482
|
}
|
13578
13483
|
function getFrameDuration(samplerate) {
|
@@ -14635,7 +14540,7 @@ class SampleAesDecrypter {
|
|
14635
14540
|
});
|
14636
14541
|
}
|
14637
14542
|
decryptBuffer(encryptedData) {
|
14638
|
-
return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer
|
14543
|
+
return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer);
|
14639
14544
|
}
|
14640
14545
|
|
14641
14546
|
// AAC - encrypt all full 16 bytes blocks starting from offset 16
|
@@ -16552,24 +16457,12 @@ class MP4Remuxer {
|
|
16552
16457
|
inputSamples[0].dts = firstDTS;
|
16553
16458
|
inputSamples[0].pts = firstPTS;
|
16554
16459
|
} else {
|
16555
|
-
let isPTSOrderRetained = true;
|
16556
16460
|
for (let i = 0; i < inputSamples.length; i++) {
|
16557
|
-
if (inputSamples[i].dts > firstPTS
|
16461
|
+
if (inputSamples[i].dts > firstPTS) {
|
16558
16462
|
break;
|
16559
16463
|
}
|
16560
|
-
const prevPTS = inputSamples[i].pts;
|
16561
16464
|
inputSamples[i].dts -= delta;
|
16562
16465
|
inputSamples[i].pts -= delta;
|
16563
|
-
|
16564
|
-
// check to see if this sample's PTS order has changed
|
16565
|
-
// relative to the next one
|
16566
|
-
if (i < inputSamples.length - 1) {
|
16567
|
-
const nextSamplePTS = inputSamples[i + 1].pts;
|
16568
|
-
const currentSamplePTS = inputSamples[i].pts;
|
16569
|
-
const currentOrder = nextSamplePTS <= currentSamplePTS;
|
16570
|
-
const prevOrder = nextSamplePTS <= prevPTS;
|
16571
|
-
isPTSOrderRetained = currentOrder == prevOrder;
|
16572
|
-
}
|
16573
16466
|
}
|
16574
16467
|
}
|
16575
16468
|
logger.log(`Video: Initial PTS/DTS adjusted: ${toMsFromMpegTsClock(firstPTS, true)}/${toMsFromMpegTsClock(firstDTS, true)}, delta: ${toMsFromMpegTsClock(delta, true)} ms`);
|
@@ -16850,7 +16743,7 @@ class MP4Remuxer {
|
|
16850
16743
|
logger.warn(`[mp4-remuxer]: Injecting ${missing} audio frame @ ${(nextPts / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`);
|
16851
16744
|
for (let j = 0; j < missing; j++) {
|
16852
16745
|
const newStamp = Math.max(nextPts, 0);
|
16853
|
-
let fillFrame = AAC.getSilentFrame(track.
|
16746
|
+
let fillFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
|
16854
16747
|
if (!fillFrame) {
|
16855
16748
|
logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.');
|
16856
16749
|
fillFrame = sample.unit.subarray();
|
@@ -16978,7 +16871,7 @@ class MP4Remuxer {
|
|
16978
16871
|
// samples count of this segment's duration
|
16979
16872
|
const nbSamples = Math.ceil((endDTS - startDTS) / frameDuration);
|
16980
16873
|
// silent frame
|
16981
|
-
const silentFrame = AAC.getSilentFrame(track.
|
16874
|
+
const silentFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
|
16982
16875
|
logger.warn('[mp4-remuxer]: remux empty Audio');
|
16983
16876
|
// Can't remux if we can't generate a silent frame...
|
16984
16877
|
if (!silentFrame) {
|
@@ -17369,15 +17262,13 @@ class Transmuxer {
|
|
17369
17262
|
initSegmentData
|
17370
17263
|
} = transmuxConfig;
|
17371
17264
|
const keyData = getEncryptionType(uintData, decryptdata);
|
17372
|
-
if (keyData &&
|
17265
|
+
if (keyData && keyData.method === 'AES-128') {
|
17373
17266
|
const decrypter = this.getDecrypter();
|
17374
|
-
const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
|
17375
|
-
|
17376
17267
|
// Software decryption is synchronous; webCrypto is not
|
17377
17268
|
if (decrypter.isSync()) {
|
17378
17269
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
17379
17270
|
// data is handled in the flush() call
|
17380
|
-
let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer
|
17271
|
+
let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer);
|
17381
17272
|
// For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
|
17382
17273
|
const loadingParts = chunkMeta.part > -1;
|
17383
17274
|
if (loadingParts) {
|
@@ -17389,7 +17280,7 @@ class Transmuxer {
|
|
17389
17280
|
}
|
17390
17281
|
uintData = new Uint8Array(decryptedData);
|
17391
17282
|
} else {
|
17392
|
-
this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer
|
17283
|
+
this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer).then(decryptedData => {
|
17393
17284
|
// Calling push here is important; if flush() is called while this is still resolving, this ensures that
|
17394
17285
|
// the decrypted data has been transmuxed
|
17395
17286
|
const result = this.push(decryptedData, null, chunkMeta);
|
@@ -18043,7 +17934,14 @@ class TransmuxerInterface {
|
|
18043
17934
|
this.observer = new EventEmitter();
|
18044
17935
|
this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
|
18045
17936
|
this.observer.on(Events.ERROR, forwardMessage);
|
18046
|
-
const
|
17937
|
+
const MediaSource = getMediaSource(config.preferManagedMediaSource) || {
|
17938
|
+
isTypeSupported: () => false
|
17939
|
+
};
|
17940
|
+
const m2tsTypeSupported = {
|
17941
|
+
mpeg: MediaSource.isTypeSupported('audio/mpeg'),
|
17942
|
+
mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
|
17943
|
+
ac3: false
|
17944
|
+
};
|
18047
17945
|
|
18048
17946
|
// navigator.vendor is not always available in Web Worker
|
18049
17947
|
// refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
|
@@ -18307,9 +18205,8 @@ const STALL_MINIMUM_DURATION_MS = 250;
|
|
18307
18205
|
const MAX_START_GAP_JUMP = 2.0;
|
18308
18206
|
const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
|
18309
18207
|
const SKIP_BUFFER_RANGE_START = 0.05;
|
18310
|
-
class GapController
|
18208
|
+
class GapController {
|
18311
18209
|
constructor(config, media, fragmentTracker, hls) {
|
18312
|
-
super('gap-controller', hls.logger);
|
18313
18210
|
this.config = void 0;
|
18314
18211
|
this.media = null;
|
18315
18212
|
this.fragmentTracker = void 0;
|
@@ -18319,7 +18216,6 @@ class GapController extends Logger {
|
|
18319
18216
|
this.stalled = null;
|
18320
18217
|
this.moved = false;
|
18321
18218
|
this.seeking = false;
|
18322
|
-
this.ended = 0;
|
18323
18219
|
this.config = config;
|
18324
18220
|
this.media = media;
|
18325
18221
|
this.fragmentTracker = fragmentTracker;
|
@@ -18337,7 +18233,7 @@ class GapController extends Logger {
|
|
18337
18233
|
*
|
18338
18234
|
* @param lastCurrentTime - Previously read playhead position
|
18339
18235
|
*/
|
18340
|
-
poll(lastCurrentTime, activeFrag
|
18236
|
+
poll(lastCurrentTime, activeFrag) {
|
18341
18237
|
const {
|
18342
18238
|
config,
|
18343
18239
|
media,
|
@@ -18356,7 +18252,6 @@ class GapController extends Logger {
|
|
18356
18252
|
|
18357
18253
|
// The playhead is moving, no-op
|
18358
18254
|
if (currentTime !== lastCurrentTime) {
|
18359
|
-
this.ended = 0;
|
18360
18255
|
this.moved = true;
|
18361
18256
|
if (!seeking) {
|
18362
18257
|
this.nudgeRetry = 0;
|
@@ -18365,7 +18260,7 @@ class GapController extends Logger {
|
|
18365
18260
|
// The playhead is now moving, but was previously stalled
|
18366
18261
|
if (this.stallReported) {
|
18367
18262
|
const _stalledDuration = self.performance.now() - stalled;
|
18368
|
-
|
18263
|
+
logger.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
|
18369
18264
|
this.stallReported = false;
|
18370
18265
|
}
|
18371
18266
|
this.stalled = null;
|
@@ -18401,6 +18296,7 @@ class GapController extends Logger {
|
|
18401
18296
|
// Skip start gaps if we haven't played, but the last poll detected the start of a stall
|
18402
18297
|
// The addition poll gives the browser a chance to jump the gap for us
|
18403
18298
|
if (!this.moved && this.stalled !== null) {
|
18299
|
+
var _level$details;
|
18404
18300
|
// There is no playable buffer (seeked, waiting for buffer)
|
18405
18301
|
const isBuffered = bufferInfo.len > 0;
|
18406
18302
|
if (!isBuffered && !nextStart) {
|
@@ -18412,8 +18308,9 @@ class GapController extends Logger {
|
|
18412
18308
|
// When joining a live stream with audio tracks, account for live playlist window sliding by allowing
|
18413
18309
|
// a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
|
18414
18310
|
// that begins over 1 target duration after the video start position.
|
18415
|
-
const
|
18416
|
-
const
|
18311
|
+
const level = this.hls.levels ? this.hls.levels[this.hls.currentLevel] : null;
|
18312
|
+
const isLive = level == null ? void 0 : (_level$details = level.details) == null ? void 0 : _level$details.live;
|
18313
|
+
const maxStartGapJump = isLive ? level.details.targetduration * 2 : MAX_START_GAP_JUMP;
|
18417
18314
|
const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
|
18418
18315
|
if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
|
18419
18316
|
if (!media.paused) {
|
@@ -18431,17 +18328,6 @@ class GapController extends Logger {
|
|
18431
18328
|
}
|
18432
18329
|
const stalledDuration = tnow - stalled;
|
18433
18330
|
if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) {
|
18434
|
-
// Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
|
18435
|
-
if (state === State.ENDED && !(levelDetails && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? void 0 : levelDetails.edge) || 0)) < 1) {
|
18436
|
-
if (stalledDuration < 1000 || this.ended) {
|
18437
|
-
return;
|
18438
|
-
}
|
18439
|
-
this.ended = currentTime;
|
18440
|
-
this.hls.trigger(Events.MEDIA_ENDED, {
|
18441
|
-
stalled: true
|
18442
|
-
});
|
18443
|
-
return;
|
18444
|
-
}
|
18445
18331
|
// Report stalling after trying to fix
|
18446
18332
|
this._reportStall(bufferInfo);
|
18447
18333
|
if (!this.media) {
|
@@ -18485,7 +18371,7 @@ class GapController extends Logger {
|
|
18485
18371
|
// needs to cross some sort of threshold covering all source-buffers content
|
18486
18372
|
// to start playing properly.
|
18487
18373
|
if ((bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
|
18488
|
-
|
18374
|
+
logger.warn('Trying to nudge playhead over buffer-hole');
|
18489
18375
|
// Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
|
18490
18376
|
// We only try to jump the hole if it's under the configured size
|
18491
18377
|
// Reset stalled so to rearm watchdog timer
|
@@ -18509,7 +18395,7 @@ class GapController extends Logger {
|
|
18509
18395
|
// Report stalled error once
|
18510
18396
|
this.stallReported = true;
|
18511
18397
|
const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${JSON.stringify(bufferInfo)})`);
|
18512
|
-
|
18398
|
+
logger.warn(error.message);
|
18513
18399
|
hls.trigger(Events.ERROR, {
|
18514
18400
|
type: ErrorTypes.MEDIA_ERROR,
|
18515
18401
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
@@ -18577,7 +18463,7 @@ class GapController extends Logger {
|
|
18577
18463
|
}
|
18578
18464
|
}
|
18579
18465
|
const targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
|
18580
|
-
|
18466
|
+
logger.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
|
18581
18467
|
this.moved = true;
|
18582
18468
|
this.stalled = null;
|
18583
18469
|
media.currentTime = targetTime;
|
@@ -18618,7 +18504,7 @@ class GapController extends Logger {
|
|
18618
18504
|
const targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset;
|
18619
18505
|
// playback stalled in buffered area ... let's nudge currentTime to try to overcome this
|
18620
18506
|
const error = new Error(`Nudging 'currentTime' from ${currentTime} to ${targetTime}`);
|
18621
|
-
|
18507
|
+
logger.warn(error.message);
|
18622
18508
|
media.currentTime = targetTime;
|
18623
18509
|
hls.trigger(Events.ERROR, {
|
18624
18510
|
type: ErrorTypes.MEDIA_ERROR,
|
@@ -18628,7 +18514,7 @@ class GapController extends Logger {
|
|
18628
18514
|
});
|
18629
18515
|
} else {
|
18630
18516
|
const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`);
|
18631
|
-
|
18517
|
+
logger.error(error.message);
|
18632
18518
|
hls.trigger(Events.ERROR, {
|
18633
18519
|
type: ErrorTypes.MEDIA_ERROR,
|
18634
18520
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
@@ -18643,7 +18529,7 @@ const TICK_INTERVAL = 100; // how often to tick in ms
|
|
18643
18529
|
|
18644
18530
|
class StreamController extends BaseStreamController {
|
18645
18531
|
constructor(hls, fragmentTracker, keyLoader) {
|
18646
|
-
super(hls, fragmentTracker, keyLoader, 'stream-controller', PlaylistLevelType.MAIN);
|
18532
|
+
super(hls, fragmentTracker, keyLoader, '[stream-controller]', PlaylistLevelType.MAIN);
|
18647
18533
|
this.audioCodecSwap = false;
|
18648
18534
|
this.gapController = null;
|
18649
18535
|
this.level = -1;
|
@@ -18651,43 +18537,27 @@ class StreamController extends BaseStreamController {
|
|
18651
18537
|
this.altAudio = false;
|
18652
18538
|
this.audioOnly = false;
|
18653
18539
|
this.fragPlaying = null;
|
18540
|
+
this.onvplaying = null;
|
18541
|
+
this.onvseeked = null;
|
18654
18542
|
this.fragLastKbps = 0;
|
18655
18543
|
this.couldBacktrack = false;
|
18656
18544
|
this.backtrackFragment = null;
|
18657
18545
|
this.audioCodecSwitch = false;
|
18658
18546
|
this.videoBuffer = null;
|
18659
|
-
this.
|
18660
|
-
// tick to speed up FRAG_CHANGED triggering
|
18661
|
-
this.tick();
|
18662
|
-
};
|
18663
|
-
this.onMediaSeeked = () => {
|
18664
|
-
const media = this.media;
|
18665
|
-
const currentTime = media ? media.currentTime : null;
|
18666
|
-
if (isFiniteNumber(currentTime)) {
|
18667
|
-
this.log(`Media seeked to ${currentTime.toFixed(3)}`);
|
18668
|
-
}
|
18669
|
-
|
18670
|
-
// If seeked was issued before buffer was appended do not tick immediately
|
18671
|
-
const bufferInfo = this.getMainFwdBufferInfo();
|
18672
|
-
if (bufferInfo === null || bufferInfo.len === 0) {
|
18673
|
-
this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
|
18674
|
-
return;
|
18675
|
-
}
|
18676
|
-
|
18677
|
-
// tick to speed up FRAG_CHANGED triggering
|
18678
|
-
this.tick();
|
18679
|
-
};
|
18680
|
-
this.registerListeners();
|
18547
|
+
this._registerListeners();
|
18681
18548
|
}
|
18682
|
-
|
18683
|
-
super.registerListeners();
|
18549
|
+
_registerListeners() {
|
18684
18550
|
const {
|
18685
18551
|
hls
|
18686
18552
|
} = this;
|
18553
|
+
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
18554
|
+
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
18555
|
+
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
18687
18556
|
hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
18688
18557
|
hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
|
18689
18558
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
18690
18559
|
hls.on(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
|
18560
|
+
hls.on(Events.ERROR, this.onError, this);
|
18691
18561
|
hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
18692
18562
|
hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
18693
18563
|
hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -18695,14 +18565,17 @@ class StreamController extends BaseStreamController {
|
|
18695
18565
|
hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
|
18696
18566
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
18697
18567
|
}
|
18698
|
-
|
18699
|
-
super.unregisterListeners();
|
18568
|
+
_unregisterListeners() {
|
18700
18569
|
const {
|
18701
18570
|
hls
|
18702
18571
|
} = this;
|
18572
|
+
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
18573
|
+
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
18574
|
+
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
18703
18575
|
hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
18704
18576
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
18705
18577
|
hls.off(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
|
18578
|
+
hls.off(Events.ERROR, this.onError, this);
|
18706
18579
|
hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
18707
18580
|
hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
18708
18581
|
hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -18711,9 +18584,7 @@ class StreamController extends BaseStreamController {
|
|
18711
18584
|
hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
18712
18585
|
}
|
18713
18586
|
onHandlerDestroying() {
|
18714
|
-
|
18715
|
-
this.onMediaPlaying = this.onMediaSeeked = null;
|
18716
|
-
this.unregisterListeners();
|
18587
|
+
this._unregisterListeners();
|
18717
18588
|
super.onHandlerDestroying();
|
18718
18589
|
}
|
18719
18590
|
startLoad(startPosition) {
|
@@ -19040,17 +18911,20 @@ class StreamController extends BaseStreamController {
|
|
19040
18911
|
onMediaAttached(event, data) {
|
19041
18912
|
super.onMediaAttached(event, data);
|
19042
18913
|
const media = data.media;
|
19043
|
-
|
19044
|
-
|
18914
|
+
this.onvplaying = this.onMediaPlaying.bind(this);
|
18915
|
+
this.onvseeked = this.onMediaSeeked.bind(this);
|
18916
|
+
media.addEventListener('playing', this.onvplaying);
|
18917
|
+
media.addEventListener('seeked', this.onvseeked);
|
19045
18918
|
this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
|
19046
18919
|
}
|
19047
18920
|
onMediaDetaching() {
|
19048
18921
|
const {
|
19049
18922
|
media
|
19050
18923
|
} = this;
|
19051
|
-
if (media) {
|
19052
|
-
media.removeEventListener('playing', this.
|
19053
|
-
media.removeEventListener('seeked', this.
|
18924
|
+
if (media && this.onvplaying && this.onvseeked) {
|
18925
|
+
media.removeEventListener('playing', this.onvplaying);
|
18926
|
+
media.removeEventListener('seeked', this.onvseeked);
|
18927
|
+
this.onvplaying = this.onvseeked = null;
|
19054
18928
|
this.videoBuffer = null;
|
19055
18929
|
}
|
19056
18930
|
this.fragPlaying = null;
|
@@ -19060,6 +18934,27 @@ class StreamController extends BaseStreamController {
|
|
19060
18934
|
}
|
19061
18935
|
super.onMediaDetaching();
|
19062
18936
|
}
|
18937
|
+
onMediaPlaying() {
|
18938
|
+
// tick to speed up FRAG_CHANGED triggering
|
18939
|
+
this.tick();
|
18940
|
+
}
|
18941
|
+
onMediaSeeked() {
|
18942
|
+
const media = this.media;
|
18943
|
+
const currentTime = media ? media.currentTime : null;
|
18944
|
+
if (isFiniteNumber(currentTime)) {
|
18945
|
+
this.log(`Media seeked to ${currentTime.toFixed(3)}`);
|
18946
|
+
}
|
18947
|
+
|
18948
|
+
// If seeked was issued before buffer was appended do not tick immediately
|
18949
|
+
const bufferInfo = this.getMainFwdBufferInfo();
|
18950
|
+
if (bufferInfo === null || bufferInfo.len === 0) {
|
18951
|
+
this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
|
18952
|
+
return;
|
18953
|
+
}
|
18954
|
+
|
18955
|
+
// tick to speed up FRAG_CHANGED triggering
|
18956
|
+
this.tick();
|
18957
|
+
}
|
19063
18958
|
onManifestLoading() {
|
19064
18959
|
// reset buffer on manifest loading
|
19065
18960
|
this.log('Trigger BUFFER_RESET');
|
@@ -19351,10 +19246,8 @@ class StreamController extends BaseStreamController {
|
|
19351
19246
|
}
|
19352
19247
|
if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) {
|
19353
19248
|
// Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
|
19354
|
-
const
|
19355
|
-
|
19356
|
-
const levelDetails = this.getLevelDetails();
|
19357
|
-
gapController.poll(this.lastCurrentTime, activeFrag, levelDetails, state);
|
19249
|
+
const activeFrag = this.state !== State.IDLE ? this.fragCurrent : null;
|
19250
|
+
gapController.poll(this.lastCurrentTime, activeFrag);
|
19358
19251
|
}
|
19359
19252
|
this.lastCurrentTime = media.currentTime;
|
19360
19253
|
}
|
@@ -19792,7 +19685,7 @@ class Hls {
|
|
19792
19685
|
* Get the video-dev/hls.js package version.
|
19793
19686
|
*/
|
19794
19687
|
static get version() {
|
19795
|
-
return "1.5.
|
19688
|
+
return "1.5.3";
|
19796
19689
|
}
|
19797
19690
|
|
19798
19691
|
/**
|
@@ -19855,10 +19748,6 @@ class Hls {
|
|
19855
19748
|
* The configuration object provided on player instantiation.
|
19856
19749
|
*/
|
19857
19750
|
this.userConfig = void 0;
|
19858
|
-
/**
|
19859
|
-
* The logger functions used by this player instance, configured on player instantiation.
|
19860
|
-
*/
|
19861
|
-
this.logger = void 0;
|
19862
19751
|
this.coreComponents = void 0;
|
19863
19752
|
this.networkControllers = void 0;
|
19864
19753
|
this.started = false;
|
@@ -19878,11 +19767,11 @@ class Hls {
|
|
19878
19767
|
this._media = null;
|
19879
19768
|
this.url = null;
|
19880
19769
|
this.triggeringException = void 0;
|
19881
|
-
|
19882
|
-
const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig
|
19770
|
+
enableLogs(userConfig.debug || false, 'Hls instance');
|
19771
|
+
const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig);
|
19883
19772
|
this.userConfig = userConfig;
|
19884
19773
|
if (config.progressive) {
|
19885
|
-
enableStreamingMode(config
|
19774
|
+
enableStreamingMode(config);
|
19886
19775
|
}
|
19887
19776
|
|
19888
19777
|
// core controllers and network loaders
|
@@ -19981,7 +19870,7 @@ class Hls {
|
|
19981
19870
|
try {
|
19982
19871
|
return this.emit(event, event, eventObject);
|
19983
19872
|
} catch (error) {
|
19984
|
-
|
19873
|
+
logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
|
19985
19874
|
// Prevent recursion in error event handlers that throw #5497
|
19986
19875
|
if (!this.triggeringException) {
|
19987
19876
|
this.triggeringException = true;
|
@@ -20007,7 +19896,7 @@ class Hls {
|
|
20007
19896
|
* Dispose of the instance
|
20008
19897
|
*/
|
20009
19898
|
destroy() {
|
20010
|
-
|
19899
|
+
logger.log('destroy');
|
20011
19900
|
this.trigger(Events.DESTROYING, undefined);
|
20012
19901
|
this.detachMedia();
|
20013
19902
|
this.removeAllListeners();
|
@@ -20028,7 +19917,7 @@ class Hls {
|
|
20028
19917
|
* Attaches Hls.js to a media element
|
20029
19918
|
*/
|
20030
19919
|
attachMedia(media) {
|
20031
|
-
|
19920
|
+
logger.log('attachMedia');
|
20032
19921
|
this._media = media;
|
20033
19922
|
this.trigger(Events.MEDIA_ATTACHING, {
|
20034
19923
|
media: media
|
@@ -20039,7 +19928,7 @@ class Hls {
|
|
20039
19928
|
* Detach Hls.js from the media
|
20040
19929
|
*/
|
20041
19930
|
detachMedia() {
|
20042
|
-
|
19931
|
+
logger.log('detachMedia');
|
20043
19932
|
this.trigger(Events.MEDIA_DETACHING, undefined);
|
20044
19933
|
this._media = null;
|
20045
19934
|
}
|
@@ -20056,7 +19945,7 @@ class Hls {
|
|
20056
19945
|
});
|
20057
19946
|
this._autoLevelCapping = -1;
|
20058
19947
|
this._maxHdcpLevel = null;
|
20059
|
-
|
19948
|
+
logger.log(`loadSource:${loadingSource}`);
|
20060
19949
|
if (media && loadedSource && (loadedSource !== loadingSource || this.bufferController.hasSourceTypes())) {
|
20061
19950
|
this.detachMedia();
|
20062
19951
|
this.attachMedia(media);
|
@@ -20075,7 +19964,7 @@ class Hls {
|
|
20075
19964
|
* Defaults to -1 (None: starts from earliest point)
|
20076
19965
|
*/
|
20077
19966
|
startLoad(startPosition = -1) {
|
20078
|
-
|
19967
|
+
logger.log(`startLoad(${startPosition})`);
|
20079
19968
|
this.started = true;
|
20080
19969
|
this.networkControllers.forEach(controller => {
|
20081
19970
|
controller.startLoad(startPosition);
|
@@ -20086,7 +19975,7 @@ class Hls {
|
|
20086
19975
|
* Stop loading of any stream data.
|
20087
19976
|
*/
|
20088
19977
|
stopLoad() {
|
20089
|
-
|
19978
|
+
logger.log('stopLoad');
|
20090
19979
|
this.started = false;
|
20091
19980
|
this.networkControllers.forEach(controller => {
|
20092
19981
|
controller.stopLoad();
|
@@ -20122,7 +20011,7 @@ class Hls {
|
|
20122
20011
|
* Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
|
20123
20012
|
*/
|
20124
20013
|
swapAudioCodec() {
|
20125
|
-
|
20014
|
+
logger.log('swapAudioCodec');
|
20126
20015
|
this.streamController.swapAudioCodec();
|
20127
20016
|
}
|
20128
20017
|
|
@@ -20133,7 +20022,7 @@ class Hls {
|
|
20133
20022
|
* Automatic recovery of media-errors by this process is configurable.
|
20134
20023
|
*/
|
20135
20024
|
recoverMediaError() {
|
20136
|
-
|
20025
|
+
logger.log('recoverMediaError');
|
20137
20026
|
const media = this._media;
|
20138
20027
|
this.detachMedia();
|
20139
20028
|
if (media) {
|
@@ -20163,7 +20052,7 @@ class Hls {
|
|
20163
20052
|
* 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.
|
20164
20053
|
*/
|
20165
20054
|
set currentLevel(newLevel) {
|
20166
|
-
|
20055
|
+
logger.log(`set currentLevel:${newLevel}`);
|
20167
20056
|
this.levelController.manualLevel = newLevel;
|
20168
20057
|
this.streamController.immediateLevelSwitch();
|
20169
20058
|
}
|
@@ -20182,7 +20071,7 @@ class Hls {
|
|
20182
20071
|
* @param newLevel - Pass -1 for automatic level selection
|
20183
20072
|
*/
|
20184
20073
|
set nextLevel(newLevel) {
|
20185
|
-
|
20074
|
+
logger.log(`set nextLevel:${newLevel}`);
|
20186
20075
|
this.levelController.manualLevel = newLevel;
|
20187
20076
|
this.streamController.nextLevelSwitch();
|
20188
20077
|
}
|
@@ -20201,7 +20090,7 @@ class Hls {
|
|
20201
20090
|
* @param newLevel - Pass -1 for automatic level selection
|
20202
20091
|
*/
|
20203
20092
|
set loadLevel(newLevel) {
|
20204
|
-
|
20093
|
+
logger.log(`set loadLevel:${newLevel}`);
|
20205
20094
|
this.levelController.manualLevel = newLevel;
|
20206
20095
|
}
|
20207
20096
|
|
@@ -20232,7 +20121,7 @@ class Hls {
|
|
20232
20121
|
* Sets "first-level", see getter.
|
20233
20122
|
*/
|
20234
20123
|
set firstLevel(newLevel) {
|
20235
|
-
|
20124
|
+
logger.log(`set firstLevel:${newLevel}`);
|
20236
20125
|
this.levelController.firstLevel = newLevel;
|
20237
20126
|
}
|
20238
20127
|
|
@@ -20257,7 +20146,7 @@ class Hls {
|
|
20257
20146
|
* (determined from download of first segment)
|
20258
20147
|
*/
|
20259
20148
|
set startLevel(newLevel) {
|
20260
|
-
|
20149
|
+
logger.log(`set startLevel:${newLevel}`);
|
20261
20150
|
// if not in automatic start level detection, ensure startLevel is greater than minAutoLevel
|
20262
20151
|
if (newLevel !== -1) {
|
20263
20152
|
newLevel = Math.max(newLevel, this.minAutoLevel);
|
@@ -20332,7 +20221,7 @@ class Hls {
|
|
20332
20221
|
*/
|
20333
20222
|
set autoLevelCapping(newLevel) {
|
20334
20223
|
if (this._autoLevelCapping !== newLevel) {
|
20335
|
-
|
20224
|
+
logger.log(`set autoLevelCapping:${newLevel}`);
|
20336
20225
|
this._autoLevelCapping = newLevel;
|
20337
20226
|
this.levelController.checkMaxAutoUpdated();
|
20338
20227
|
}
|