hls.js 1.5.5-0.canary.9995 → 1.5.5
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 +0 -1
- package/dist/hls-demo.js +0 -10
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +1134 -2043
- package/dist/hls.js.d.ts +50 -65
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +852 -1141
- 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 +686 -974
- 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 +847 -1741
- package/dist/hls.mjs.map +1 -1
- package/dist/hls.worker.js +1 -1
- package/dist/hls.worker.js.map +1 -1
- package/package.json +20 -20
- package/src/config.ts +2 -3
- package/src/controller/abr-controller.ts +20 -21
- package/src/controller/audio-stream-controller.ts +16 -15
- package/src/controller/audio-track-controller.ts +1 -1
- package/src/controller/base-playlist-controller.ts +8 -20
- package/src/controller/base-stream-controller.ts +33 -149
- package/src/controller/buffer-controller.ts +11 -11
- package/src/controller/cap-level-controller.ts +2 -1
- package/src/controller/cmcd-controller.ts +6 -27
- 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 +18 -12
- package/src/controller/stream-controller.ts +32 -25
- 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 +37 -71
- package/src/demux/video/avc-video-parser.ts +119 -208
- package/src/demux/video/base-video-parser.ts +2 -134
- package/src/demux/video/exp-golomb.ts +208 -0
- package/src/events.ts +0 -7
- package/src/hls.ts +34 -42
- 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/loader/playlist-loader.ts +5 -4
- package/src/remux/mp4-generator.ts +1 -196
- package/src/remux/mp4-remuxer.ts +7 -23
- package/src/task-loop.ts +2 -5
- package/src/types/component-api.ts +0 -2
- package/src/types/demuxer.ts +0 -3
- package/src/types/events.ts +0 -4
- package/src/utils/codecs.ts +4 -33
- package/src/utils/logger.ts +24 -54
- package/src/crypt/decrypter-aes-mode.ts +0 -4
- package/src/demux/video/hevc-video-parser.ts +0 -746
- 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,6 +369,58 @@ let ErrorDetails = /*#__PURE__*/function (ErrorDetails) {
|
|
370
369
|
return ErrorDetails;
|
371
370
|
}({});
|
372
371
|
|
372
|
+
const noop = function noop() {};
|
373
|
+
const fakeLogger = {
|
374
|
+
trace: noop,
|
375
|
+
debug: noop,
|
376
|
+
log: noop,
|
377
|
+
warn: noop,
|
378
|
+
info: noop,
|
379
|
+
error: noop
|
380
|
+
};
|
381
|
+
let exportedLogger = fakeLogger;
|
382
|
+
|
383
|
+
// let lastCallTime;
|
384
|
+
// function formatMsgWithTimeInfo(type, msg) {
|
385
|
+
// const now = Date.now();
|
386
|
+
// const diff = lastCallTime ? '+' + (now - lastCallTime) : '0';
|
387
|
+
// lastCallTime = now;
|
388
|
+
// msg = (new Date(now)).toISOString() + ' | [' + type + '] > ' + msg + ' ( ' + diff + ' ms )';
|
389
|
+
// return msg;
|
390
|
+
// }
|
391
|
+
|
392
|
+
function consolePrintFn(type) {
|
393
|
+
const func = self.console[type];
|
394
|
+
if (func) {
|
395
|
+
return func.bind(self.console, `[${type}] >`);
|
396
|
+
}
|
397
|
+
return noop;
|
398
|
+
}
|
399
|
+
function exportLoggerFunctions(debugConfig, ...functions) {
|
400
|
+
functions.forEach(function (type) {
|
401
|
+
exportedLogger[type] = debugConfig[type] ? debugConfig[type].bind(debugConfig) : consolePrintFn(type);
|
402
|
+
});
|
403
|
+
}
|
404
|
+
function enableLogs(debugConfig, id) {
|
405
|
+
// check that console is available
|
406
|
+
if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
|
407
|
+
exportLoggerFunctions(debugConfig,
|
408
|
+
// Remove out from list here to hard-disable a log-level
|
409
|
+
// 'trace',
|
410
|
+
'debug', 'log', 'info', 'warn', 'error');
|
411
|
+
// Some browsers don't allow to use bind on console object anyway
|
412
|
+
// fallback to default if needed
|
413
|
+
try {
|
414
|
+
exportedLogger.log(`Debug logs enabled for "${id}" in hls.js version ${"1.5.5"}`);
|
415
|
+
} catch (e) {
|
416
|
+
exportedLogger = fakeLogger;
|
417
|
+
}
|
418
|
+
} else {
|
419
|
+
exportedLogger = fakeLogger;
|
420
|
+
}
|
421
|
+
}
|
422
|
+
const logger = exportedLogger;
|
423
|
+
|
373
424
|
const DECIMAL_RESOLUTION_REGEX = /^(\d+)x(\d+)$/;
|
374
425
|
const ATTR_LIST_REGEX = /(.+?)=(".*?"|.*?)(?:,|$)/g;
|
375
426
|
|
@@ -451,79 +502,6 @@ class AttrList {
|
|
451
502
|
}
|
452
503
|
}
|
453
504
|
|
454
|
-
class Logger {
|
455
|
-
constructor(label, logger) {
|
456
|
-
this.trace = void 0;
|
457
|
-
this.debug = void 0;
|
458
|
-
this.log = void 0;
|
459
|
-
this.warn = void 0;
|
460
|
-
this.info = void 0;
|
461
|
-
this.error = void 0;
|
462
|
-
const lb = `[${label}]:`;
|
463
|
-
this.trace = noop;
|
464
|
-
this.debug = logger.debug.bind(null, lb);
|
465
|
-
this.log = logger.log.bind(null, lb);
|
466
|
-
this.warn = logger.warn.bind(null, lb);
|
467
|
-
this.info = logger.info.bind(null, lb);
|
468
|
-
this.error = logger.error.bind(null, lb);
|
469
|
-
}
|
470
|
-
}
|
471
|
-
const noop = function noop() {};
|
472
|
-
const fakeLogger = {
|
473
|
-
trace: noop,
|
474
|
-
debug: noop,
|
475
|
-
log: noop,
|
476
|
-
warn: noop,
|
477
|
-
info: noop,
|
478
|
-
error: noop
|
479
|
-
};
|
480
|
-
function createLogger() {
|
481
|
-
return _extends({}, fakeLogger);
|
482
|
-
}
|
483
|
-
|
484
|
-
// let lastCallTime;
|
485
|
-
// function formatMsgWithTimeInfo(type, msg) {
|
486
|
-
// const now = Date.now();
|
487
|
-
// const diff = lastCallTime ? '+' + (now - lastCallTime) : '0';
|
488
|
-
// lastCallTime = now;
|
489
|
-
// msg = (new Date(now)).toISOString() + ' | [' + type + '] > ' + msg + ' ( ' + diff + ' ms )';
|
490
|
-
// return msg;
|
491
|
-
// }
|
492
|
-
|
493
|
-
function consolePrintFn(type, id) {
|
494
|
-
const func = self.console[type];
|
495
|
-
return func ? func.bind(self.console, `${id ? '[' + id + '] ' : ''}[${type}] >`) : noop;
|
496
|
-
}
|
497
|
-
function getLoggerFn(key, debugConfig, id) {
|
498
|
-
return debugConfig[key] ? debugConfig[key].bind(debugConfig) : consolePrintFn(key, id);
|
499
|
-
}
|
500
|
-
const exportedLogger = createLogger();
|
501
|
-
function enableLogs(debugConfig, context, id) {
|
502
|
-
// check that console is available
|
503
|
-
const newLogger = createLogger();
|
504
|
-
if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
|
505
|
-
const keys = [
|
506
|
-
// Remove out from list here to hard-disable a log-level
|
507
|
-
// 'trace',
|
508
|
-
'debug', 'log', 'info', 'warn', 'error'];
|
509
|
-
keys.forEach(key => {
|
510
|
-
newLogger[key] = getLoggerFn(key, debugConfig, id);
|
511
|
-
});
|
512
|
-
// Some browsers don't allow to use bind on console object anyway
|
513
|
-
// fallback to default if needed
|
514
|
-
try {
|
515
|
-
newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.5.5-0.canary.9995"}`);
|
516
|
-
} catch (e) {
|
517
|
-
/* log fn threw an exception. All logger methods are no-ops. */
|
518
|
-
return createLogger();
|
519
|
-
}
|
520
|
-
}
|
521
|
-
// global exported logger uses the log methods from last call to `enableLogs`
|
522
|
-
_extends(exportedLogger, newLogger);
|
523
|
-
return newLogger;
|
524
|
-
}
|
525
|
-
const logger = exportedLogger;
|
526
|
-
|
527
505
|
// Avoid exporting const enum so that these values can be inlined
|
528
506
|
|
529
507
|
function isDateRangeCueAttribute(attrName) {
|
@@ -1013,30 +991,10 @@ class LevelDetails {
|
|
1013
991
|
}
|
1014
992
|
}
|
1015
993
|
|
1016
|
-
var DecrypterAesMode = {
|
1017
|
-
cbc: 0,
|
1018
|
-
ctr: 1
|
1019
|
-
};
|
1020
|
-
|
1021
|
-
function isFullSegmentEncryption(method) {
|
1022
|
-
return method === 'AES-128' || method === 'AES-256' || method === 'AES-256-CTR';
|
1023
|
-
}
|
1024
|
-
function getAesModeFromFullSegmentMethod(method) {
|
1025
|
-
switch (method) {
|
1026
|
-
case 'AES-128':
|
1027
|
-
case 'AES-256':
|
1028
|
-
return DecrypterAesMode.cbc;
|
1029
|
-
case 'AES-256-CTR':
|
1030
|
-
return DecrypterAesMode.ctr;
|
1031
|
-
default:
|
1032
|
-
throw new Error(`invalid full segment method ${method}`);
|
1033
|
-
}
|
1034
|
-
}
|
1035
|
-
|
1036
994
|
// This file is inserted as a shim for modules which we do not want to include into the distro.
|
1037
995
|
// This replacement is done in the "alias" plugin of the rollup config.
|
1038
996
|
var empty = undefined;
|
1039
|
-
var
|
997
|
+
var Cues = /*@__PURE__*/getDefaultExportFromCjs(empty);
|
1040
998
|
|
1041
999
|
function sliceUint8(array, start, end) {
|
1042
1000
|
// @ts-expect-error This polyfills IE11 usage of Uint8Array slice.
|
@@ -2473,12 +2431,12 @@ class LevelKey {
|
|
2473
2431
|
this.keyFormatVersions = formatversions;
|
2474
2432
|
this.iv = iv;
|
2475
2433
|
this.encrypted = method ? method !== 'NONE' : false;
|
2476
|
-
this.isCommonEncryption = this.encrypted &&
|
2434
|
+
this.isCommonEncryption = this.encrypted && method !== 'AES-128';
|
2477
2435
|
}
|
2478
2436
|
isSupported() {
|
2479
2437
|
// If it's Segment encryption or No encryption, just select that key system
|
2480
2438
|
if (this.method) {
|
2481
|
-
if (
|
2439
|
+
if (this.method === 'AES-128' || this.method === 'NONE') {
|
2482
2440
|
return true;
|
2483
2441
|
}
|
2484
2442
|
if (this.keyFormat === 'identity') {
|
@@ -2492,13 +2450,14 @@ class LevelKey {
|
|
2492
2450
|
if (!this.encrypted || !this.uri) {
|
2493
2451
|
return null;
|
2494
2452
|
}
|
2495
|
-
if (
|
2453
|
+
if (this.method === 'AES-128' && this.uri && !this.iv) {
|
2496
2454
|
if (typeof sn !== 'number') {
|
2497
2455
|
// We are fetching decryption data for a initialization segment
|
2498
|
-
// If the segment was encrypted with AES-128
|
2456
|
+
// If the segment was encrypted with AES-128
|
2499
2457
|
// It must have an IV defined. We cannot substitute the Segment Number in.
|
2500
|
-
|
2501
|
-
|
2458
|
+
if (this.method === 'AES-128' && !this.iv) {
|
2459
|
+
logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
|
2460
|
+
}
|
2502
2461
|
// Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
|
2503
2462
|
sn = 0;
|
2504
2463
|
}
|
@@ -2645,28 +2604,23 @@ function getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource =
|
|
2645
2604
|
if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) {
|
2646
2605
|
return CODEC_COMPATIBLE_NAMES[lowerCaseCodec];
|
2647
2606
|
}
|
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
|
2648
2611
|
const codecsToCheck = {
|
2649
|
-
// Idealy fLaC and Opus would be first (spec-compliant) but
|
2650
|
-
// some browsers will report that fLaC is supported then fail.
|
2651
|
-
// see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
|
2652
2612
|
flac: ['flac', 'fLaC', 'FLAC'],
|
2653
|
-
opus: ['opus', 'Opus']
|
2654
|
-
// Replace audio codec info if browser does not support mp4a.40.34,
|
2655
|
-
// and demuxer can fallback to 'audio/mpeg' or 'audio/mp4;codecs="mp3"'
|
2656
|
-
'mp4a.40.34': ['mp3']
|
2613
|
+
opus: ['opus', 'Opus']
|
2657
2614
|
}[lowerCaseCodec];
|
2658
2615
|
for (let i = 0; i < codecsToCheck.length; i++) {
|
2659
|
-
var _getMediaSource;
|
2660
2616
|
if (isCodecMediaSourceSupported(codecsToCheck[i], 'audio', preferManagedMediaSource)) {
|
2661
2617
|
CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
|
2662
2618
|
return codecsToCheck[i];
|
2663
|
-
} else if (codecsToCheck[i] === 'mp3' && (_getMediaSource = getMediaSource(preferManagedMediaSource)) != null && _getMediaSource.isTypeSupported('audio/mpeg')) {
|
2664
|
-
return '';
|
2665
2619
|
}
|
2666
2620
|
}
|
2667
2621
|
return lowerCaseCodec;
|
2668
2622
|
}
|
2669
|
-
const AUDIO_CODEC_REGEXP = /flac|opus
|
2623
|
+
const AUDIO_CODEC_REGEXP = /flac|opus/i;
|
2670
2624
|
function getCodecCompatibleName(codec, preferManagedMediaSource = true) {
|
2671
2625
|
return codec.replace(AUDIO_CODEC_REGEXP, m => getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource));
|
2672
2626
|
}
|
@@ -2689,16 +2643,6 @@ function convertAVC1ToAVCOTI(codec) {
|
|
2689
2643
|
}
|
2690
2644
|
return codec;
|
2691
2645
|
}
|
2692
|
-
function getM2TSSupportedAudioTypes(preferManagedMediaSource) {
|
2693
|
-
const MediaSource = getMediaSource(preferManagedMediaSource) || {
|
2694
|
-
isTypeSupported: () => false
|
2695
|
-
};
|
2696
|
-
return {
|
2697
|
-
mpeg: MediaSource.isTypeSupported('audio/mpeg'),
|
2698
|
-
mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
|
2699
|
-
ac3: false
|
2700
|
-
};
|
2701
|
-
}
|
2702
2646
|
|
2703
2647
|
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;
|
2704
2648
|
const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g;
|
@@ -3499,10 +3443,10 @@ class PlaylistLoader {
|
|
3499
3443
|
const loaderContext = loader.context;
|
3500
3444
|
if (loaderContext && loaderContext.url === context.url && loaderContext.level === context.level) {
|
3501
3445
|
// same URL can't overlap
|
3502
|
-
|
3446
|
+
logger.trace('[playlist-loader]: playlist request ongoing');
|
3503
3447
|
return;
|
3504
3448
|
}
|
3505
|
-
|
3449
|
+
logger.log(`[playlist-loader]: aborting previous loader for type: ${context.type}`);
|
3506
3450
|
loader.abort();
|
3507
3451
|
}
|
3508
3452
|
|
@@ -3612,7 +3556,7 @@ class PlaylistLoader {
|
|
3612
3556
|
// alt audio rendition in which quality levels (main)
|
3613
3557
|
// contains both audio+video. but with mixed audio track not signaled
|
3614
3558
|
if (!embeddedAudioFound && levels[0].audioCodec && !levels[0].attrs.AUDIO) {
|
3615
|
-
|
3559
|
+
logger.log('[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one');
|
3616
3560
|
audioTracks.unshift({
|
3617
3561
|
type: 'main',
|
3618
3562
|
name: 'main',
|
@@ -3711,7 +3655,7 @@ class PlaylistLoader {
|
|
3711
3655
|
message += ` id: ${context.id} group-id: "${context.groupId}"`;
|
3712
3656
|
}
|
3713
3657
|
const error = new Error(message);
|
3714
|
-
|
3658
|
+
logger.warn(`[playlist-loader]: ${message}`);
|
3715
3659
|
let details = ErrorDetails.UNKNOWN;
|
3716
3660
|
let fatal = false;
|
3717
3661
|
const loader = this.getInternalLoader(context);
|
@@ -4276,47 +4220,7 @@ class LatencyController {
|
|
4276
4220
|
this.currentTime = 0;
|
4277
4221
|
this.stallCount = 0;
|
4278
4222
|
this._latency = null;
|
4279
|
-
this.
|
4280
|
-
const {
|
4281
|
-
media,
|
4282
|
-
levelDetails
|
4283
|
-
} = this;
|
4284
|
-
if (!media || !levelDetails) {
|
4285
|
-
return;
|
4286
|
-
}
|
4287
|
-
this.currentTime = media.currentTime;
|
4288
|
-
const latency = this.computeLatency();
|
4289
|
-
if (latency === null) {
|
4290
|
-
return;
|
4291
|
-
}
|
4292
|
-
this._latency = latency;
|
4293
|
-
|
4294
|
-
// Adapt playbackRate to meet target latency in low-latency mode
|
4295
|
-
const {
|
4296
|
-
lowLatencyMode,
|
4297
|
-
maxLiveSyncPlaybackRate
|
4298
|
-
} = this.config;
|
4299
|
-
if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
|
4300
|
-
return;
|
4301
|
-
}
|
4302
|
-
const targetLatency = this.targetLatency;
|
4303
|
-
if (targetLatency === null) {
|
4304
|
-
return;
|
4305
|
-
}
|
4306
|
-
const distanceFromTarget = latency - targetLatency;
|
4307
|
-
// Only adjust playbackRate when within one target duration of targetLatency
|
4308
|
-
// and more than one second from under-buffering.
|
4309
|
-
// Playback further than one target duration from target can be considered DVR playback.
|
4310
|
-
const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
|
4311
|
-
const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
|
4312
|
-
if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
|
4313
|
-
const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
|
4314
|
-
const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
|
4315
|
-
media.playbackRate = Math.min(max, Math.max(1, rate));
|
4316
|
-
} else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
|
4317
|
-
media.playbackRate = 1;
|
4318
|
-
}
|
4319
|
-
};
|
4223
|
+
this.timeupdateHandler = () => this.timeupdate();
|
4320
4224
|
this.hls = hls;
|
4321
4225
|
this.config = hls.config;
|
4322
4226
|
this.registerListeners();
|
@@ -4408,7 +4312,7 @@ class LatencyController {
|
|
4408
4312
|
this.onMediaDetaching();
|
4409
4313
|
this.levelDetails = null;
|
4410
4314
|
// @ts-ignore
|
4411
|
-
this.hls = null;
|
4315
|
+
this.hls = this.timeupdateHandler = null;
|
4412
4316
|
}
|
4413
4317
|
registerListeners() {
|
4414
4318
|
this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
@@ -4426,11 +4330,11 @@ class LatencyController {
|
|
4426
4330
|
}
|
4427
4331
|
onMediaAttached(event, data) {
|
4428
4332
|
this.media = data.media;
|
4429
|
-
this.media.addEventListener('timeupdate', this.
|
4333
|
+
this.media.addEventListener('timeupdate', this.timeupdateHandler);
|
4430
4334
|
}
|
4431
4335
|
onMediaDetaching() {
|
4432
4336
|
if (this.media) {
|
4433
|
-
this.media.removeEventListener('timeupdate', this.
|
4337
|
+
this.media.removeEventListener('timeupdate', this.timeupdateHandler);
|
4434
4338
|
this.media = null;
|
4435
4339
|
}
|
4436
4340
|
}
|
@@ -4444,10 +4348,10 @@ class LatencyController {
|
|
4444
4348
|
}) {
|
4445
4349
|
this.levelDetails = details;
|
4446
4350
|
if (details.advanced) {
|
4447
|
-
this.
|
4351
|
+
this.timeupdate();
|
4448
4352
|
}
|
4449
4353
|
if (!details.live && this.media) {
|
4450
|
-
this.media.removeEventListener('timeupdate', this.
|
4354
|
+
this.media.removeEventListener('timeupdate', this.timeupdateHandler);
|
4451
4355
|
}
|
4452
4356
|
}
|
4453
4357
|
onError(event, data) {
|
@@ -4457,7 +4361,48 @@ class LatencyController {
|
|
4457
4361
|
}
|
4458
4362
|
this.stallCount++;
|
4459
4363
|
if ((_this$levelDetails = this.levelDetails) != null && _this$levelDetails.live) {
|
4460
|
-
|
4364
|
+
logger.warn('[playback-rate-controller]: Stall detected, adjusting target latency');
|
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;
|
4461
4406
|
}
|
4462
4407
|
}
|
4463
4408
|
estimateLiveEdge() {
|
@@ -5229,13 +5174,18 @@ var ErrorActionFlags = {
|
|
5229
5174
|
MoveAllAlternatesMatchingHDCP: 2,
|
5230
5175
|
SwitchToSDR: 4
|
5231
5176
|
}; // Reserved for future use
|
5232
|
-
class ErrorController
|
5177
|
+
class ErrorController {
|
5233
5178
|
constructor(hls) {
|
5234
|
-
super('error-controller', hls.logger);
|
5235
5179
|
this.hls = void 0;
|
5236
5180
|
this.playlistError = 0;
|
5237
5181
|
this.penalizedRenditions = {};
|
5182
|
+
this.log = void 0;
|
5183
|
+
this.warn = void 0;
|
5184
|
+
this.error = void 0;
|
5238
5185
|
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]:`);
|
5239
5189
|
this.registerListeners();
|
5240
5190
|
}
|
5241
5191
|
registerListeners() {
|
@@ -5587,13 +5537,16 @@ class ErrorController extends Logger {
|
|
5587
5537
|
}
|
5588
5538
|
}
|
5589
5539
|
|
5590
|
-
class BasePlaylistController
|
5540
|
+
class BasePlaylistController {
|
5591
5541
|
constructor(hls, logPrefix) {
|
5592
|
-
super(logPrefix, hls.logger);
|
5593
5542
|
this.hls = void 0;
|
5594
5543
|
this.timer = -1;
|
5595
5544
|
this.requestScheduled = -1;
|
5596
5545
|
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}:`);
|
5597
5550
|
this.hls = hls;
|
5598
5551
|
}
|
5599
5552
|
destroy() {
|
@@ -5626,7 +5579,7 @@ class BasePlaylistController extends Logger {
|
|
5626
5579
|
try {
|
5627
5580
|
uri = new self.URL(attr.URI, previous.url).href;
|
5628
5581
|
} catch (error) {
|
5629
|
-
|
5582
|
+
logger.warn(`Could not construct new URL for Rendition Report: ${error}`);
|
5630
5583
|
uri = attr.URI || '';
|
5631
5584
|
}
|
5632
5585
|
// Use exact match. Otherwise, the last partial match, if any, will be used
|
@@ -5713,12 +5666,7 @@ class BasePlaylistController extends Logger {
|
|
5713
5666
|
const cdnAge = lastAdvanced + details.ageHeader;
|
5714
5667
|
let currentGoal = Math.min(cdnAge - details.partTarget, details.targetduration * 1.5);
|
5715
5668
|
if (currentGoal > 0) {
|
5716
|
-
if (
|
5717
|
-
// Omit segment and part directives when the last response was more than 3 target durations ago,
|
5718
|
-
this.log(`Playlist last advanced ${lastAdvanced.toFixed(2)}s ago. Omitting segment and part directives.`);
|
5719
|
-
msn = undefined;
|
5720
|
-
part = undefined;
|
5721
|
-
} else if (previousDetails != null && previousDetails.tuneInGoal && cdnAge - details.partTarget > previousDetails.tuneInGoal) {
|
5669
|
+
if (previousDetails && currentGoal > previousDetails.tuneInGoal) {
|
5722
5670
|
// If we attempted to get the next or latest playlist update, but currentGoal increased,
|
5723
5671
|
// then we either can't catchup, or the "age" header cannot be trusted.
|
5724
5672
|
this.warn(`CDN Tune-in goal increased from: ${previousDetails.tuneInGoal} to: ${currentGoal} with playlist age: ${details.age}`);
|
@@ -6177,9 +6125,8 @@ function getCodecTiers(levels, audioTracksByGroup, minAutoLevel, maxAutoLevel) {
|
|
6177
6125
|
}, {});
|
6178
6126
|
}
|
6179
6127
|
|
6180
|
-
class AbrController
|
6128
|
+
class AbrController {
|
6181
6129
|
constructor(_hls) {
|
6182
|
-
super('abr', _hls.logger);
|
6183
6130
|
this.hls = void 0;
|
6184
6131
|
this.lastLevelLoadSec = 0;
|
6185
6132
|
this.lastLoadedFragLevel = -1;
|
@@ -6293,7 +6240,7 @@ class AbrController extends Logger {
|
|
6293
6240
|
this.resetEstimator(nextLoadLevelBitrate);
|
6294
6241
|
}
|
6295
6242
|
this.clearTimer();
|
6296
|
-
|
6243
|
+
logger.warn(`[abr] Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
|
6297
6244
|
Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s
|
6298
6245
|
Estimated load time for current fragment: ${fragLoadedDelay.toFixed(3)} s
|
6299
6246
|
Estimated load time for down switch fragment: ${fragLevelNextLoadedDelay.toFixed(3)} s
|
@@ -6313,7 +6260,7 @@ class AbrController extends Logger {
|
|
6313
6260
|
}
|
6314
6261
|
resetEstimator(abrEwmaDefaultEstimate) {
|
6315
6262
|
if (abrEwmaDefaultEstimate) {
|
6316
|
-
|
6263
|
+
logger.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
|
6317
6264
|
this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate;
|
6318
6265
|
}
|
6319
6266
|
this.firstSelection = -1;
|
@@ -6545,7 +6492,7 @@ class AbrController extends Logger {
|
|
6545
6492
|
}
|
6546
6493
|
const firstLevel = this.hls.firstLevel;
|
6547
6494
|
const clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel);
|
6548
|
-
|
6495
|
+
logger.warn(`[abr] Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
|
6549
6496
|
return clamped;
|
6550
6497
|
}
|
6551
6498
|
get forcedAutoLevel() {
|
@@ -6630,13 +6577,13 @@ class AbrController extends Logger {
|
|
6630
6577
|
// cap maxLoadingDelay and ensure it is not bigger 'than bitrate test' frag duration
|
6631
6578
|
const maxLoadingDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxLoadingDelay) : config.maxLoadingDelay;
|
6632
6579
|
maxStarvationDelay = maxLoadingDelay - bitrateTestDelay;
|
6633
|
-
|
6580
|
+
logger.info(`[abr] bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
|
6634
6581
|
// don't use conservative factor on bitrate test
|
6635
6582
|
bwFactor = bwUpFactor = 1;
|
6636
6583
|
}
|
6637
6584
|
}
|
6638
6585
|
const bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor);
|
6639
|
-
|
6586
|
+
logger.info(`[abr] ${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
|
6640
6587
|
if (bestLevel > -1) {
|
6641
6588
|
return bestLevel;
|
6642
6589
|
}
|
@@ -6698,7 +6645,7 @@ class AbrController extends Logger {
|
|
6698
6645
|
currentVideoRange = preferHDR ? videoRanges[videoRanges.length - 1] : videoRanges[0];
|
6699
6646
|
currentFrameRate = minFramerate;
|
6700
6647
|
currentBw = Math.max(currentBw, minBitrate);
|
6701
|
-
|
6648
|
+
logger.log(`[abr] picked start tier ${JSON.stringify(startTier)}`);
|
6702
6649
|
} else {
|
6703
6650
|
currentCodecSet = level == null ? void 0 : level.codecSet;
|
6704
6651
|
currentVideoRange = level == null ? void 0 : level.videoRange;
|
@@ -6751,9 +6698,9 @@ class AbrController extends Logger {
|
|
6751
6698
|
const forcedAutoLevel = this.forcedAutoLevel;
|
6752
6699
|
if (i !== loadLevel && (forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)) {
|
6753
6700
|
if (levelsSkipped.length) {
|
6754
|
-
|
6701
|
+
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}`);
|
6755
6702
|
}
|
6756
|
-
|
6703
|
+
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}`);
|
6757
6704
|
}
|
6758
6705
|
if (firstSelection) {
|
6759
6706
|
this.firstSelection = i;
|
@@ -6989,9 +6936,8 @@ class BufferOperationQueue {
|
|
6989
6936
|
}
|
6990
6937
|
|
6991
6938
|
const VIDEO_CODEC_PROFILE_REPLACE = /(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\.[^.,]+)+/;
|
6992
|
-
class BufferController
|
6939
|
+
class BufferController {
|
6993
6940
|
constructor(hls) {
|
6994
|
-
super('buffer-controller', hls.logger);
|
6995
6941
|
// The level details used to determine duration, target-duration and live
|
6996
6942
|
this.details = null;
|
6997
6943
|
// cache the self generated object url to detect hijack of video tag
|
@@ -7021,6 +6967,9 @@ class BufferController extends Logger {
|
|
7021
6967
|
this.tracks = {};
|
7022
6968
|
this.pendingTracks = {};
|
7023
6969
|
this.sourceBuffer = void 0;
|
6970
|
+
this.log = void 0;
|
6971
|
+
this.warn = void 0;
|
6972
|
+
this.error = void 0;
|
7024
6973
|
this._onEndStreaming = event => {
|
7025
6974
|
if (!this.hls) {
|
7026
6975
|
return;
|
@@ -7066,11 +7015,15 @@ class BufferController extends Logger {
|
|
7066
7015
|
_objectUrl
|
7067
7016
|
} = this;
|
7068
7017
|
if (mediaSrc !== _objectUrl) {
|
7069
|
-
|
7018
|
+
logger.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
|
7070
7019
|
}
|
7071
7020
|
};
|
7072
7021
|
this.hls = hls;
|
7022
|
+
const logPrefix = '[buffer-controller]';
|
7073
7023
|
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);
|
7074
7027
|
this._initSourceBuffer();
|
7075
7028
|
this.registerListeners();
|
7076
7029
|
}
|
@@ -7083,12 +7036,6 @@ class BufferController extends Logger {
|
|
7083
7036
|
this.lastMpegAudioChunk = null;
|
7084
7037
|
// @ts-ignore
|
7085
7038
|
this.hls = null;
|
7086
|
-
// @ts-ignore
|
7087
|
-
this._onMediaSourceOpen = this._onMediaSourceClose = null;
|
7088
|
-
// @ts-ignore
|
7089
|
-
this._onMediaSourceEnded = null;
|
7090
|
-
// @ts-ignore
|
7091
|
-
this._onStartStreaming = this._onEndStreaming = null;
|
7092
7039
|
}
|
7093
7040
|
registerListeners() {
|
7094
7041
|
const {
|
@@ -7251,7 +7198,6 @@ class BufferController extends Logger {
|
|
7251
7198
|
this.resetBuffer(type);
|
7252
7199
|
});
|
7253
7200
|
this._initSourceBuffer();
|
7254
|
-
this.hls.resumeBuffering();
|
7255
7201
|
}
|
7256
7202
|
resetBuffer(type) {
|
7257
7203
|
const sb = this.sourceBuffer[type];
|
@@ -8089,7 +8035,7 @@ class CapLevelController {
|
|
8089
8035
|
const hls = this.hls;
|
8090
8036
|
const maxLevel = this.getMaxLevel(levels.length - 1);
|
8091
8037
|
if (maxLevel !== this.autoLevelCapping) {
|
8092
|
-
|
8038
|
+
logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
|
8093
8039
|
}
|
8094
8040
|
hls.autoLevelCapping = maxLevel;
|
8095
8041
|
if (hls.autoLevelCapping > this.autoLevelCapping && this.streamController) {
|
@@ -8267,10 +8213,10 @@ class FPSController {
|
|
8267
8213
|
totalDroppedFrames: droppedFrames
|
8268
8214
|
});
|
8269
8215
|
if (droppedFPS > 0) {
|
8270
|
-
//
|
8216
|
+
// logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
|
8271
8217
|
if (currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
|
8272
8218
|
let currentLevel = hls.currentLevel;
|
8273
|
-
|
8219
|
+
logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
|
8274
8220
|
if (currentLevel > 0 && (hls.autoLevelCapping === -1 || hls.autoLevelCapping >= currentLevel)) {
|
8275
8221
|
currentLevel = currentLevel - 1;
|
8276
8222
|
hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, {
|
@@ -8303,10 +8249,10 @@ class FPSController {
|
|
8303
8249
|
}
|
8304
8250
|
|
8305
8251
|
const PATHWAY_PENALTY_DURATION_MS = 300000;
|
8306
|
-
class ContentSteeringController
|
8252
|
+
class ContentSteeringController {
|
8307
8253
|
constructor(hls) {
|
8308
|
-
super('content-steering', hls.logger);
|
8309
8254
|
this.hls = void 0;
|
8255
|
+
this.log = void 0;
|
8310
8256
|
this.loader = null;
|
8311
8257
|
this.uri = null;
|
8312
8258
|
this.pathwayId = '.';
|
@@ -8321,6 +8267,7 @@ class ContentSteeringController extends Logger {
|
|
8321
8267
|
this.subtitleTracks = null;
|
8322
8268
|
this.penalizedPathways = {};
|
8323
8269
|
this.hls = hls;
|
8270
|
+
this.log = logger.log.bind(logger, `[content-steering]:`);
|
8324
8271
|
this.registerListeners();
|
8325
8272
|
}
|
8326
8273
|
registerListeners() {
|
@@ -8444,7 +8391,7 @@ class ContentSteeringController extends Logger {
|
|
8444
8391
|
errorAction.resolved = this.pathwayId !== errorPathway;
|
8445
8392
|
}
|
8446
8393
|
if (!errorAction.resolved) {
|
8447
|
-
|
8394
|
+
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)}`);
|
8448
8395
|
}
|
8449
8396
|
}
|
8450
8397
|
}
|
@@ -8615,7 +8562,7 @@ class ContentSteeringController extends Logger {
|
|
8615
8562
|
onSuccess: (response, stats, context, networkDetails) => {
|
8616
8563
|
this.log(`Loaded steering manifest: "${url}"`);
|
8617
8564
|
const steeringData = response.data;
|
8618
|
-
if (
|
8565
|
+
if (steeringData.VERSION !== 1) {
|
8619
8566
|
this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
|
8620
8567
|
return;
|
8621
8568
|
}
|
@@ -9523,7 +9470,7 @@ const hlsDefaultConfig = _objectSpread2(_objectSpread2({
|
|
9523
9470
|
});
|
9524
9471
|
function timelineConfig() {
|
9525
9472
|
return {
|
9526
|
-
cueHandler:
|
9473
|
+
cueHandler: Cues,
|
9527
9474
|
// used by timeline-controller
|
9528
9475
|
enableWebVTT: false,
|
9529
9476
|
// used by timeline-controller
|
@@ -9554,7 +9501,7 @@ function timelineConfig() {
|
|
9554
9501
|
/**
|
9555
9502
|
* @ignore
|
9556
9503
|
*/
|
9557
|
-
function mergeConfig(defaultConfig, userConfig
|
9504
|
+
function mergeConfig(defaultConfig, userConfig) {
|
9558
9505
|
if ((userConfig.liveSyncDurationCount || userConfig.liveMaxLatencyDurationCount) && (userConfig.liveSyncDuration || userConfig.liveMaxLatencyDuration)) {
|
9559
9506
|
throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration");
|
9560
9507
|
}
|
@@ -9624,7 +9571,7 @@ function deepCpy(obj) {
|
|
9624
9571
|
/**
|
9625
9572
|
* @ignore
|
9626
9573
|
*/
|
9627
|
-
function enableStreamingMode(config
|
9574
|
+
function enableStreamingMode(config) {
|
9628
9575
|
const currentLoader = config.loader;
|
9629
9576
|
if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
|
9630
9577
|
// If a developer has configured their own loader, respect that choice
|
@@ -9641,9 +9588,10 @@ function enableStreamingMode(config, logger) {
|
|
9641
9588
|
}
|
9642
9589
|
}
|
9643
9590
|
|
9591
|
+
let chromeOrFirefox;
|
9644
9592
|
class LevelController extends BasePlaylistController {
|
9645
9593
|
constructor(hls, contentSteeringController) {
|
9646
|
-
super(hls, 'level-controller');
|
9594
|
+
super(hls, '[level-controller]');
|
9647
9595
|
this._levels = [];
|
9648
9596
|
this._firstLevel = -1;
|
9649
9597
|
this._maxAutoLevel = -1;
|
@@ -9714,15 +9662,23 @@ class LevelController extends BasePlaylistController {
|
|
9714
9662
|
let videoCodecFound = false;
|
9715
9663
|
let audioCodecFound = false;
|
9716
9664
|
data.levels.forEach(levelParsed => {
|
9717
|
-
var _videoCodec;
|
9665
|
+
var _audioCodec, _videoCodec;
|
9718
9666
|
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
|
9719
9670
|
let {
|
9720
9671
|
audioCodec,
|
9721
9672
|
videoCodec
|
9722
9673
|
} = 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
|
+
}
|
9723
9680
|
if (audioCodec) {
|
9724
|
-
|
9725
|
-
levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource) || undefined;
|
9681
|
+
levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource);
|
9726
9682
|
}
|
9727
9683
|
if (((_videoCodec = videoCodec) == null ? void 0 : _videoCodec.indexOf('avc1')) === 0) {
|
9728
9684
|
videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
|
@@ -10064,12 +10020,7 @@ class LevelController extends BasePlaylistController {
|
|
10064
10020
|
if (curLevel.fragmentError === 0) {
|
10065
10021
|
curLevel.loadError = 0;
|
10066
10022
|
}
|
10067
|
-
|
10068
|
-
let previousDetails = curLevel.details;
|
10069
|
-
if (previousDetails === data.details && previousDetails.advanced) {
|
10070
|
-
previousDetails = undefined;
|
10071
|
-
}
|
10072
|
-
this.playlistLoaded(level, data, previousDetails);
|
10023
|
+
this.playlistLoaded(level, data, curLevel.details);
|
10073
10024
|
} else if ((_data$deliveryDirecti2 = data.deliveryDirectives) != null && _data$deliveryDirecti2.skip) {
|
10074
10025
|
// received a delta playlist update that cannot be merged
|
10075
10026
|
details.deltaUpdateFailed = true;
|
@@ -10855,8 +10806,8 @@ function createLoaderContext(frag, part = null) {
|
|
10855
10806
|
var _frag$decryptdata;
|
10856
10807
|
let byteRangeStart = start;
|
10857
10808
|
let byteRangeEnd = end;
|
10858
|
-
if (frag.sn === 'initSegment' &&
|
10859
|
-
// MAP segment encrypted with method 'AES-128'
|
10809
|
+
if (frag.sn === 'initSegment' && ((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method) === 'AES-128') {
|
10810
|
+
// MAP segment encrypted with method 'AES-128', when served with HTTP Range,
|
10860
10811
|
// has the unencrypted size specified in the range.
|
10861
10812
|
// Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
|
10862
10813
|
const fragmentLen = end - start;
|
@@ -10889,9 +10840,6 @@ function createGapLoadError(frag, part) {
|
|
10889
10840
|
(part ? part : frag).stats.aborted = true;
|
10890
10841
|
return new LoadError(errorData);
|
10891
10842
|
}
|
10892
|
-
function isMethodFullSegmentAesCbc(method) {
|
10893
|
-
return method === 'AES-128' || method === 'AES-256';
|
10894
|
-
}
|
10895
10843
|
class LoadError extends Error {
|
10896
10844
|
constructor(data) {
|
10897
10845
|
super(data.error.message);
|
@@ -11037,8 +10985,6 @@ class KeyLoader {
|
|
11037
10985
|
}
|
11038
10986
|
return this.loadKeyEME(keyInfo, frag);
|
11039
10987
|
case 'AES-128':
|
11040
|
-
case 'AES-256':
|
11041
|
-
case 'AES-256-CTR':
|
11042
10988
|
return this.loadKeyHTTP(keyInfo, frag);
|
11043
10989
|
default:
|
11044
10990
|
return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`)));
|
@@ -11174,9 +11120,8 @@ class KeyLoader {
|
|
11174
11120
|
* we are limiting the task execution per call stack to exactly one, but scheduling/post-poning further
|
11175
11121
|
* task processing on the next main loop iteration (also known as "next tick" in the Node/JS runtime lingo).
|
11176
11122
|
*/
|
11177
|
-
class TaskLoop
|
11178
|
-
constructor(
|
11179
|
-
super(label, logger);
|
11123
|
+
class TaskLoop {
|
11124
|
+
constructor() {
|
11180
11125
|
this._boundTick = void 0;
|
11181
11126
|
this._tickTimer = null;
|
11182
11127
|
this._tickInterval = null;
|
@@ -11444,61 +11389,33 @@ function alignMediaPlaylistByPDT(details, refDetails) {
|
|
11444
11389
|
}
|
11445
11390
|
|
11446
11391
|
class AESCrypto {
|
11447
|
-
constructor(subtle, iv
|
11392
|
+
constructor(subtle, iv) {
|
11448
11393
|
this.subtle = void 0;
|
11449
11394
|
this.aesIV = void 0;
|
11450
|
-
this.aesMode = void 0;
|
11451
11395
|
this.subtle = subtle;
|
11452
11396
|
this.aesIV = iv;
|
11453
|
-
this.aesMode = aesMode;
|
11454
11397
|
}
|
11455
11398
|
decrypt(data, key) {
|
11456
|
-
|
11457
|
-
|
11458
|
-
|
11459
|
-
|
11460
|
-
iv: this.aesIV
|
11461
|
-
}, key, data);
|
11462
|
-
case DecrypterAesMode.ctr:
|
11463
|
-
return this.subtle.decrypt({
|
11464
|
-
name: 'AES-CTR',
|
11465
|
-
counter: this.aesIV,
|
11466
|
-
length: 64
|
11467
|
-
},
|
11468
|
-
//64 : NIST SP800-38A standard suggests that the counter should occupy half of the counter block
|
11469
|
-
key, data);
|
11470
|
-
default:
|
11471
|
-
throw new Error(`[AESCrypto] invalid aes mode ${this.aesMode}`);
|
11472
|
-
}
|
11399
|
+
return this.subtle.decrypt({
|
11400
|
+
name: 'AES-CBC',
|
11401
|
+
iv: this.aesIV
|
11402
|
+
}, key, data);
|
11473
11403
|
}
|
11474
11404
|
}
|
11475
11405
|
|
11476
11406
|
class FastAESKey {
|
11477
|
-
constructor(subtle, key
|
11407
|
+
constructor(subtle, key) {
|
11478
11408
|
this.subtle = void 0;
|
11479
11409
|
this.key = void 0;
|
11480
|
-
this.aesMode = void 0;
|
11481
11410
|
this.subtle = subtle;
|
11482
11411
|
this.key = key;
|
11483
|
-
this.aesMode = aesMode;
|
11484
11412
|
}
|
11485
11413
|
expandKey() {
|
11486
|
-
const subtleAlgoName = getSubtleAlgoName(this.aesMode);
|
11487
11414
|
return this.subtle.importKey('raw', this.key, {
|
11488
|
-
name:
|
11415
|
+
name: 'AES-CBC'
|
11489
11416
|
}, false, ['encrypt', 'decrypt']);
|
11490
11417
|
}
|
11491
11418
|
}
|
11492
|
-
function getSubtleAlgoName(aesMode) {
|
11493
|
-
switch (aesMode) {
|
11494
|
-
case DecrypterAesMode.cbc:
|
11495
|
-
return 'AES-CBC';
|
11496
|
-
case DecrypterAesMode.ctr:
|
11497
|
-
return 'AES-CTR';
|
11498
|
-
default:
|
11499
|
-
throw new Error(`[FastAESKey] invalid aes mode ${aesMode}`);
|
11500
|
-
}
|
11501
|
-
}
|
11502
11419
|
|
11503
11420
|
// PKCS7
|
11504
11421
|
function removePadding(array) {
|
@@ -11748,8 +11665,7 @@ class Decrypter {
|
|
11748
11665
|
this.currentIV = null;
|
11749
11666
|
this.currentResult = null;
|
11750
11667
|
this.useSoftware = void 0;
|
11751
|
-
this.
|
11752
|
-
this.enableSoftwareAES = config.enableSoftwareAES;
|
11668
|
+
this.useSoftware = config.enableSoftwareAES;
|
11753
11669
|
this.removePKCS7Padding = removePKCS7Padding;
|
11754
11670
|
// built in decryptor expects PKCS7 padding
|
11755
11671
|
if (removePKCS7Padding) {
|
@@ -11762,7 +11678,9 @@ class Decrypter {
|
|
11762
11678
|
/* no-op */
|
11763
11679
|
}
|
11764
11680
|
}
|
11765
|
-
|
11681
|
+
if (this.subtle === null) {
|
11682
|
+
this.useSoftware = true;
|
11683
|
+
}
|
11766
11684
|
}
|
11767
11685
|
destroy() {
|
11768
11686
|
this.subtle = null;
|
@@ -11800,10 +11718,10 @@ class Decrypter {
|
|
11800
11718
|
this.softwareDecrypter = null;
|
11801
11719
|
}
|
11802
11720
|
}
|
11803
|
-
decrypt(data, key, iv
|
11721
|
+
decrypt(data, key, iv) {
|
11804
11722
|
if (this.useSoftware) {
|
11805
11723
|
return new Promise((resolve, reject) => {
|
11806
|
-
this.softwareDecrypt(new Uint8Array(data), key, iv
|
11724
|
+
this.softwareDecrypt(new Uint8Array(data), key, iv);
|
11807
11725
|
const decryptResult = this.flush();
|
11808
11726
|
if (decryptResult) {
|
11809
11727
|
resolve(decryptResult.buffer);
|
@@ -11812,21 +11730,17 @@ class Decrypter {
|
|
11812
11730
|
}
|
11813
11731
|
});
|
11814
11732
|
}
|
11815
|
-
return this.webCryptoDecrypt(new Uint8Array(data), key, iv
|
11733
|
+
return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
|
11816
11734
|
}
|
11817
11735
|
|
11818
11736
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
11819
11737
|
// data is handled in the flush() call
|
11820
|
-
softwareDecrypt(data, key, iv
|
11738
|
+
softwareDecrypt(data, key, iv) {
|
11821
11739
|
const {
|
11822
11740
|
currentIV,
|
11823
11741
|
currentResult,
|
11824
11742
|
remainderData
|
11825
11743
|
} = this;
|
11826
|
-
if (aesMode !== DecrypterAesMode.cbc || key.byteLength !== 16) {
|
11827
|
-
logger.warn('SoftwareDecrypt: can only handle AES-128-CBC');
|
11828
|
-
return null;
|
11829
|
-
}
|
11830
11744
|
this.logOnce('JS AES decrypt');
|
11831
11745
|
// The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
|
11832
11746
|
// This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached
|
@@ -11859,11 +11773,11 @@ class Decrypter {
|
|
11859
11773
|
}
|
11860
11774
|
return result;
|
11861
11775
|
}
|
11862
|
-
webCryptoDecrypt(data, key, iv
|
11776
|
+
webCryptoDecrypt(data, key, iv) {
|
11863
11777
|
const subtle = this.subtle;
|
11864
11778
|
if (this.key !== key || !this.fastAesKey) {
|
11865
11779
|
this.key = key;
|
11866
|
-
this.fastAesKey = new FastAESKey(subtle, key
|
11780
|
+
this.fastAesKey = new FastAESKey(subtle, key);
|
11867
11781
|
}
|
11868
11782
|
return this.fastAesKey.expandKey().then(aesKey => {
|
11869
11783
|
// decrypt using web crypto
|
@@ -11871,25 +11785,22 @@ class Decrypter {
|
|
11871
11785
|
return Promise.reject(new Error('web crypto not initialized'));
|
11872
11786
|
}
|
11873
11787
|
this.logOnce('WebCrypto AES decrypt');
|
11874
|
-
const crypto = new AESCrypto(subtle, new Uint8Array(iv)
|
11788
|
+
const crypto = new AESCrypto(subtle, new Uint8Array(iv));
|
11875
11789
|
return crypto.decrypt(data.buffer, aesKey);
|
11876
11790
|
}).catch(err => {
|
11877
11791
|
logger.warn(`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`);
|
11878
|
-
return this.onWebCryptoError(data, key, iv
|
11792
|
+
return this.onWebCryptoError(data, key, iv);
|
11879
11793
|
});
|
11880
11794
|
}
|
11881
|
-
onWebCryptoError(data, key, iv
|
11882
|
-
|
11883
|
-
|
11884
|
-
|
11885
|
-
|
11886
|
-
|
11887
|
-
|
11888
|
-
if (decryptResult) {
|
11889
|
-
return decryptResult.buffer;
|
11890
|
-
}
|
11795
|
+
onWebCryptoError(data, key, iv) {
|
11796
|
+
this.useSoftware = true;
|
11797
|
+
this.logEnabled = true;
|
11798
|
+
this.softwareDecrypt(data, key, iv);
|
11799
|
+
const decryptResult = this.flush();
|
11800
|
+
if (decryptResult) {
|
11801
|
+
return decryptResult.buffer;
|
11891
11802
|
}
|
11892
|
-
throw new Error('WebCrypto
|
11803
|
+
throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data');
|
11893
11804
|
}
|
11894
11805
|
getValidChunk(data) {
|
11895
11806
|
let currentChunk = data;
|
@@ -11940,7 +11851,7 @@ const State = {
|
|
11940
11851
|
};
|
11941
11852
|
class BaseStreamController extends TaskLoop {
|
11942
11853
|
constructor(hls, fragmentTracker, keyLoader, logPrefix, playlistType) {
|
11943
|
-
super(
|
11854
|
+
super();
|
11944
11855
|
this.hls = void 0;
|
11945
11856
|
this.fragPrevious = null;
|
11946
11857
|
this.fragCurrent = null;
|
@@ -11965,98 +11876,22 @@ class BaseStreamController extends TaskLoop {
|
|
11965
11876
|
this.startFragRequested = false;
|
11966
11877
|
this.decrypter = void 0;
|
11967
11878
|
this.initPTS = [];
|
11968
|
-
this.
|
11969
|
-
this.
|
11970
|
-
this.
|
11971
|
-
|
11972
|
-
|
11973
|
-
fragCurrent,
|
11974
|
-
media,
|
11975
|
-
mediaBuffer,
|
11976
|
-
state
|
11977
|
-
} = this;
|
11978
|
-
const currentTime = media ? media.currentTime : 0;
|
11979
|
-
const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
|
11980
|
-
this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
|
11981
|
-
if (this.state === State.ENDED) {
|
11982
|
-
this.resetLoadingState();
|
11983
|
-
} else if (fragCurrent) {
|
11984
|
-
// Seeking while frag load is in progress
|
11985
|
-
const tolerance = config.maxFragLookUpTolerance;
|
11986
|
-
const fragStartOffset = fragCurrent.start - tolerance;
|
11987
|
-
const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
|
11988
|
-
// if seeking out of buffered range or into new one
|
11989
|
-
if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
|
11990
|
-
const pastFragment = currentTime > fragEndOffset;
|
11991
|
-
// if the seek position is outside the current fragment range
|
11992
|
-
if (currentTime < fragStartOffset || pastFragment) {
|
11993
|
-
if (pastFragment && fragCurrent.loader) {
|
11994
|
-
this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
|
11995
|
-
fragCurrent.abortRequests();
|
11996
|
-
this.resetLoadingState();
|
11997
|
-
}
|
11998
|
-
this.fragPrevious = null;
|
11999
|
-
}
|
12000
|
-
}
|
12001
|
-
}
|
12002
|
-
if (media) {
|
12003
|
-
// Remove gap fragments
|
12004
|
-
this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
|
12005
|
-
this.lastCurrentTime = currentTime;
|
12006
|
-
if (!this.loadingParts) {
|
12007
|
-
const bufferEnd = Math.max(bufferInfo.end, currentTime);
|
12008
|
-
const shouldLoadParts = this.shouldLoadParts(this.getLevelDetails(), bufferEnd);
|
12009
|
-
if (shouldLoadParts) {
|
12010
|
-
this.log(`LL-Part loading ON after seeking to ${currentTime.toFixed(2)} with buffer @${bufferEnd.toFixed(2)}`);
|
12011
|
-
this.loadingParts = shouldLoadParts;
|
12012
|
-
}
|
12013
|
-
}
|
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
|
-
this.onMediaEnded = () => {
|
12025
|
-
// reset startPosition and lastCurrentTime to restart playback @ stream beginning
|
12026
|
-
this.startPosition = this.lastCurrentTime = 0;
|
12027
|
-
if (this.playlistType === PlaylistLevelType.MAIN) {
|
12028
|
-
this.hls.trigger(Events.MEDIA_ENDED, {
|
12029
|
-
stalled: false
|
12030
|
-
});
|
12031
|
-
}
|
12032
|
-
};
|
11879
|
+
this.onvseeking = null;
|
11880
|
+
this.onvended = null;
|
11881
|
+
this.logPrefix = '';
|
11882
|
+
this.log = void 0;
|
11883
|
+
this.warn = void 0;
|
12033
11884
|
this.playlistType = playlistType;
|
11885
|
+
this.logPrefix = logPrefix;
|
11886
|
+
this.log = logger.log.bind(logger, `${logPrefix}:`);
|
11887
|
+
this.warn = logger.warn.bind(logger, `${logPrefix}:`);
|
12034
11888
|
this.hls = hls;
|
12035
11889
|
this.fragmentLoader = new FragmentLoader(hls.config);
|
12036
11890
|
this.keyLoader = keyLoader;
|
12037
11891
|
this.fragmentTracker = fragmentTracker;
|
12038
11892
|
this.config = hls.config;
|
12039
11893
|
this.decrypter = new Decrypter(hls.config);
|
12040
|
-
}
|
12041
|
-
registerListeners() {
|
12042
|
-
const {
|
12043
|
-
hls
|
12044
|
-
} = this;
|
12045
|
-
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
12046
|
-
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
12047
|
-
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
12048
11894
|
hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
12049
|
-
hls.on(Events.ERROR, this.onError, this);
|
12050
|
-
}
|
12051
|
-
unregisterListeners() {
|
12052
|
-
const {
|
12053
|
-
hls
|
12054
|
-
} = this;
|
12055
|
-
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
12056
|
-
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
12057
|
-
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
12058
|
-
hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
12059
|
-
hls.off(Events.ERROR, this.onError, this);
|
12060
11895
|
}
|
12061
11896
|
doTick() {
|
12062
11897
|
this.onTickEnd();
|
@@ -12080,12 +11915,6 @@ class BaseStreamController extends TaskLoop {
|
|
12080
11915
|
this.clearNextTick();
|
12081
11916
|
this.state = State.STOPPED;
|
12082
11917
|
}
|
12083
|
-
pauseBuffering() {
|
12084
|
-
this.buffering = false;
|
12085
|
-
}
|
12086
|
-
resumeBuffering() {
|
12087
|
-
this.buffering = true;
|
12088
|
-
}
|
12089
11918
|
_streamEnded(bufferInfo, levelDetails) {
|
12090
11919
|
// If playlist is live, there is another buffered range after the current range, nothing buffered, media is detached,
|
12091
11920
|
// of nothing loading/loaded return false
|
@@ -12116,8 +11945,10 @@ class BaseStreamController extends TaskLoop {
|
|
12116
11945
|
}
|
12117
11946
|
onMediaAttached(event, data) {
|
12118
11947
|
const media = this.media = this.mediaBuffer = data.media;
|
12119
|
-
|
12120
|
-
|
11948
|
+
this.onvseeking = this.onMediaSeeking.bind(this);
|
11949
|
+
this.onvended = this.onMediaEnded.bind(this);
|
11950
|
+
media.addEventListener('seeking', this.onvseeking);
|
11951
|
+
media.addEventListener('ended', this.onvended);
|
12121
11952
|
const config = this.config;
|
12122
11953
|
if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
|
12123
11954
|
this.startLoad(config.startPosition);
|
@@ -12131,9 +11962,10 @@ class BaseStreamController extends TaskLoop {
|
|
12131
11962
|
}
|
12132
11963
|
|
12133
11964
|
// remove video listeners
|
12134
|
-
if (media) {
|
12135
|
-
media.removeEventListener('seeking', this.
|
12136
|
-
media.removeEventListener('ended', this.
|
11965
|
+
if (media && this.onvseeking && this.onvended) {
|
11966
|
+
media.removeEventListener('seeking', this.onvseeking);
|
11967
|
+
media.removeEventListener('ended', this.onvended);
|
11968
|
+
this.onvseeking = this.onvended = null;
|
12137
11969
|
}
|
12138
11970
|
if (this.keyLoader) {
|
12139
11971
|
this.keyLoader.detach();
|
@@ -12143,8 +11975,56 @@ class BaseStreamController extends TaskLoop {
|
|
12143
11975
|
this.fragmentTracker.removeAllFragments();
|
12144
11976
|
this.stopLoad();
|
12145
11977
|
}
|
12146
|
-
|
12147
|
-
|
11978
|
+
onMediaSeeking() {
|
11979
|
+
const {
|
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
|
+
}
|
12148
12028
|
onManifestLoaded(event, data) {
|
12149
12029
|
this.startTimeOffset = data.startTimeOffset;
|
12150
12030
|
this.initPTS = [];
|
@@ -12154,7 +12034,7 @@ class BaseStreamController extends TaskLoop {
|
|
12154
12034
|
this.stopLoad();
|
12155
12035
|
super.onHandlerDestroying();
|
12156
12036
|
// @ts-ignore
|
12157
|
-
this.hls =
|
12037
|
+
this.hls = null;
|
12158
12038
|
}
|
12159
12039
|
onHandlerDestroyed() {
|
12160
12040
|
this.state = State.STOPPED;
|
@@ -12285,10 +12165,10 @@ class BaseStreamController extends TaskLoop {
|
|
12285
12165
|
const decryptData = frag.decryptdata;
|
12286
12166
|
|
12287
12167
|
// check to see if the payload needs to be decrypted
|
12288
|
-
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv &&
|
12168
|
+
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
|
12289
12169
|
const startTime = self.performance.now();
|
12290
12170
|
// decrypt init segment data
|
12291
|
-
return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer
|
12171
|
+
return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
|
12292
12172
|
hls.trigger(Events.ERROR, {
|
12293
12173
|
type: ErrorTypes.MEDIA_ERROR,
|
12294
12174
|
details: ErrorDetails.FRAG_DECRYPT_ERROR,
|
@@ -12400,7 +12280,7 @@ class BaseStreamController extends TaskLoop {
|
|
12400
12280
|
}
|
12401
12281
|
let keyLoadingPromise = null;
|
12402
12282
|
if (frag.encrypted && !((_frag$decryptdata = frag.decryptdata) != null && _frag$decryptdata.key)) {
|
12403
|
-
this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.
|
12283
|
+
this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.logPrefix === '[stream-controller]' ? 'level' : 'track'} ${frag.level}`);
|
12404
12284
|
this.state = State.KEY_LOADING;
|
12405
12285
|
this.fragCurrent = frag;
|
12406
12286
|
keyLoadingPromise = this.keyLoader.load(frag).then(keyLoadedData => {
|
@@ -12421,16 +12301,8 @@ class BaseStreamController extends TaskLoop {
|
|
12421
12301
|
} else if (!frag.encrypted && details.encryptedFragments.length) {
|
12422
12302
|
this.keyLoader.loadClear(frag, details.encryptedFragments);
|
12423
12303
|
}
|
12424
|
-
const fragPrevious = this.fragPrevious;
|
12425
|
-
if (frag.sn !== 'initSegment' && (!fragPrevious || frag.sn !== fragPrevious.sn)) {
|
12426
|
-
const shouldLoadParts = this.shouldLoadParts(level.details, frag.end);
|
12427
|
-
if (shouldLoadParts !== this.loadingParts) {
|
12428
|
-
this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} loading sn ${fragPrevious == null ? void 0 : fragPrevious.sn}->${frag.sn}`);
|
12429
|
-
this.loadingParts = shouldLoadParts;
|
12430
|
-
}
|
12431
|
-
}
|
12432
12304
|
targetBufferTime = Math.max(frag.start, targetBufferTime || 0);
|
12433
|
-
if (this.
|
12305
|
+
if (this.config.lowLatencyMode && frag.sn !== 'initSegment') {
|
12434
12306
|
const partList = details.partList;
|
12435
12307
|
if (partList && progressCallback) {
|
12436
12308
|
if (targetBufferTime > frag.end && details.fragmentHint) {
|
@@ -12439,7 +12311,7 @@ class BaseStreamController extends TaskLoop {
|
|
12439
12311
|
const partIndex = this.getNextPart(partList, frag, targetBufferTime);
|
12440
12312
|
if (partIndex > -1) {
|
12441
12313
|
const part = partList[partIndex];
|
12442
|
-
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.
|
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.logPrefix === '[stream-controller]' ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
|
12443
12315
|
this.nextLoadPosition = part.start + part.duration;
|
12444
12316
|
this.state = State.FRAG_LOADING;
|
12445
12317
|
let _result;
|
@@ -12468,14 +12340,7 @@ class BaseStreamController extends TaskLoop {
|
|
12468
12340
|
}
|
12469
12341
|
}
|
12470
12342
|
}
|
12471
|
-
|
12472
|
-
this.log(`LL-Part loading OFF after next part miss @${targetBufferTime.toFixed(2)}`);
|
12473
|
-
this.loadingParts = false;
|
12474
|
-
} else if (!frag.url) {
|
12475
|
-
// Selected fragment hint for part but not loading parts
|
12476
|
-
return Promise.resolve(null);
|
12477
|
-
}
|
12478
|
-
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))}`);
|
12343
|
+
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))}`);
|
12479
12344
|
// Don't update nextLoadPosition for fragments which are not buffered
|
12480
12345
|
if (isFiniteNumber(frag.sn) && !this.bitrateTest) {
|
12481
12346
|
this.nextLoadPosition = frag.start + frag.duration;
|
@@ -12573,36 +12438,8 @@ class BaseStreamController extends TaskLoop {
|
|
12573
12438
|
if (part) {
|
12574
12439
|
part.stats.parsing.end = now;
|
12575
12440
|
}
|
12576
|
-
// See if part loading should be disabled/enabled based on buffer and playback position.
|
12577
|
-
if (frag.sn !== 'initSegment') {
|
12578
|
-
const levelDetails = this.getLevelDetails();
|
12579
|
-
const loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
|
12580
|
-
const shouldLoadParts = loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
|
12581
|
-
if (shouldLoadParts !== this.loadingParts) {
|
12582
|
-
this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} after parsing segment ending @${frag.end.toFixed(2)}`);
|
12583
|
-
this.loadingParts = shouldLoadParts;
|
12584
|
-
}
|
12585
|
-
}
|
12586
12441
|
this.updateLevelTiming(frag, part, level, chunkMeta.partial);
|
12587
12442
|
}
|
12588
|
-
shouldLoadParts(details, bufferEnd) {
|
12589
|
-
if (this.config.lowLatencyMode) {
|
12590
|
-
if (!details) {
|
12591
|
-
return this.loadingParts;
|
12592
|
-
}
|
12593
|
-
if (details != null && details.partList) {
|
12594
|
-
var _details$fragmentHint;
|
12595
|
-
// Buffer must be ahead of first part + duration of parts after last segment
|
12596
|
-
// and playback must be at or past segment adjacent to part list
|
12597
|
-
const firstPart = details.partList[0];
|
12598
|
-
const safePartStart = firstPart.end + (((_details$fragmentHint = details.fragmentHint) == null ? void 0 : _details$fragmentHint.duration) || 0);
|
12599
|
-
if (bufferEnd >= safePartStart && this.lastCurrentTime > firstPart.start - firstPart.fragment.duration) {
|
12600
|
-
return true;
|
12601
|
-
}
|
12602
|
-
}
|
12603
|
-
}
|
12604
|
-
return false;
|
12605
|
-
}
|
12606
12443
|
getCurrentContext(chunkMeta) {
|
12607
12444
|
const {
|
12608
12445
|
levels,
|
@@ -12751,8 +12588,7 @@ class BaseStreamController extends TaskLoop {
|
|
12751
12588
|
config
|
12752
12589
|
} = this;
|
12753
12590
|
const start = fragments[0].start;
|
12754
|
-
|
12755
|
-
let frag = null;
|
12591
|
+
let frag;
|
12756
12592
|
if (levelDetails.live) {
|
12757
12593
|
const initialLiveManifestSize = config.initialLiveManifestSize;
|
12758
12594
|
if (fragLen < initialLiveManifestSize) {
|
@@ -12764,10 +12600,6 @@ class BaseStreamController extends TaskLoop {
|
|
12764
12600
|
// Do not load using live logic if the starting frag is requested - we want to use getFragmentAtPosition() so that
|
12765
12601
|
// we get the fragment matching that start time
|
12766
12602
|
if (!levelDetails.PTSKnown && !this.startFragRequested && this.startPosition === -1 || pos < start) {
|
12767
|
-
if (canLoadParts && !this.loadingParts) {
|
12768
|
-
this.log(`LL-Part loading ON for initial live fragment`);
|
12769
|
-
this.loadingParts = true;
|
12770
|
-
}
|
12771
12603
|
frag = this.getInitialLiveFragment(levelDetails, fragments);
|
12772
12604
|
this.startPosition = this.nextLoadPosition = frag ? this.hls.liveSyncPosition || frag.start : pos;
|
12773
12605
|
}
|
@@ -12778,7 +12610,7 @@ class BaseStreamController extends TaskLoop {
|
|
12778
12610
|
|
12779
12611
|
// If we haven't run into any special cases already, just load the fragment most closely matching the requested position
|
12780
12612
|
if (!frag) {
|
12781
|
-
const end =
|
12613
|
+
const end = config.lowLatencyMode ? levelDetails.partEnd : levelDetails.fragmentEnd;
|
12782
12614
|
frag = this.getFragmentAtPosition(pos, end, levelDetails);
|
12783
12615
|
}
|
12784
12616
|
return this.mapToInitFragWhenRequired(frag);
|
@@ -12900,7 +12732,7 @@ class BaseStreamController extends TaskLoop {
|
|
12900
12732
|
} = levelDetails;
|
12901
12733
|
const tolerance = config.maxFragLookUpTolerance;
|
12902
12734
|
const partList = levelDetails.partList;
|
12903
|
-
const loadingParts = !!(
|
12735
|
+
const loadingParts = !!(config.lowLatencyMode && partList != null && partList.length && fragmentHint);
|
12904
12736
|
if (loadingParts && fragmentHint && !this.bitrateTest) {
|
12905
12737
|
// Include incomplete fragment with parts at end
|
12906
12738
|
fragments = fragments.concat(fragmentHint);
|
@@ -13093,7 +12925,7 @@ class BaseStreamController extends TaskLoop {
|
|
13093
12925
|
errorAction.resolved = true;
|
13094
12926
|
}
|
13095
12927
|
} else {
|
13096
|
-
|
12928
|
+
logger.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
|
13097
12929
|
return;
|
13098
12930
|
}
|
13099
12931
|
} else if ((errorAction == null ? void 0 : errorAction.action) === NetworkErrorAction.SendAlternateToPenaltyBox) {
|
@@ -13488,7 +13320,6 @@ const initPTSFn = (timestamp, timeOffset, initPTS) => {
|
|
13488
13320
|
*/
|
13489
13321
|
function getAudioConfig(observer, data, offset, audioCodec) {
|
13490
13322
|
let adtsObjectType;
|
13491
|
-
let originalAdtsObjectType;
|
13492
13323
|
let adtsExtensionSamplingIndex;
|
13493
13324
|
let adtsChannelConfig;
|
13494
13325
|
let config;
|
@@ -13496,7 +13327,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
13496
13327
|
const manifestCodec = audioCodec;
|
13497
13328
|
const adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
|
13498
13329
|
// byte 2
|
13499
|
-
adtsObjectType =
|
13330
|
+
adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
|
13500
13331
|
const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
|
13501
13332
|
if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
|
13502
13333
|
const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
|
@@ -13513,8 +13344,8 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
13513
13344
|
// byte 3
|
13514
13345
|
adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6;
|
13515
13346
|
logger.log(`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`);
|
13516
|
-
//
|
13517
|
-
if (/firefox
|
13347
|
+
// firefox: freq less than 24kHz = AAC SBR (HE-AAC)
|
13348
|
+
if (/firefox/i.test(userAgent)) {
|
13518
13349
|
if (adtsSamplingIndex >= 6) {
|
13519
13350
|
adtsObjectType = 5;
|
13520
13351
|
config = new Array(4);
|
@@ -13608,7 +13439,6 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
13608
13439
|
samplerate: adtsSamplingRates[adtsSamplingIndex],
|
13609
13440
|
channelCount: adtsChannelConfig,
|
13610
13441
|
codec: 'mp4a.40.' + adtsObjectType,
|
13611
|
-
parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
|
13612
13442
|
manifestCodec
|
13613
13443
|
};
|
13614
13444
|
}
|
@@ -13663,8 +13493,7 @@ function initTrackConfig(track, observer, data, offset, audioCodec) {
|
|
13663
13493
|
track.channelCount = config.channelCount;
|
13664
13494
|
track.codec = config.codec;
|
13665
13495
|
track.manifestCodec = config.manifestCodec;
|
13666
|
-
track.
|
13667
|
-
logger.log(`parsed codec:${track.parsedCodec}, codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
|
13496
|
+
logger.log(`parsed codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
|
13668
13497
|
}
|
13669
13498
|
}
|
13670
13499
|
function getFrameDuration(samplerate) {
|
@@ -14142,110 +13971,6 @@ class BaseVideoParser {
|
|
14142
13971
|
logger.log(VideoSample.pts + '/' + VideoSample.dts + ':' + VideoSample.debug);
|
14143
13972
|
}
|
14144
13973
|
}
|
14145
|
-
parseNALu(track, array) {
|
14146
|
-
const len = array.byteLength;
|
14147
|
-
let state = track.naluState || 0;
|
14148
|
-
const lastState = state;
|
14149
|
-
const units = [];
|
14150
|
-
let i = 0;
|
14151
|
-
let value;
|
14152
|
-
let overflow;
|
14153
|
-
let unitType;
|
14154
|
-
let lastUnitStart = -1;
|
14155
|
-
let lastUnitType = 0;
|
14156
|
-
// logger.log('PES:' + Hex.hexDump(array));
|
14157
|
-
|
14158
|
-
if (state === -1) {
|
14159
|
-
// special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
|
14160
|
-
lastUnitStart = 0;
|
14161
|
-
// NALu type is value read from offset 0
|
14162
|
-
lastUnitType = this.getNALuType(array, 0);
|
14163
|
-
state = 0;
|
14164
|
-
i = 1;
|
14165
|
-
}
|
14166
|
-
while (i < len) {
|
14167
|
-
value = array[i++];
|
14168
|
-
// optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
|
14169
|
-
if (!state) {
|
14170
|
-
state = value ? 0 : 1;
|
14171
|
-
continue;
|
14172
|
-
}
|
14173
|
-
if (state === 1) {
|
14174
|
-
state = value ? 0 : 2;
|
14175
|
-
continue;
|
14176
|
-
}
|
14177
|
-
// here we have state either equal to 2 or 3
|
14178
|
-
if (!value) {
|
14179
|
-
state = 3;
|
14180
|
-
} else if (value === 1) {
|
14181
|
-
overflow = i - state - 1;
|
14182
|
-
if (lastUnitStart >= 0) {
|
14183
|
-
const unit = {
|
14184
|
-
data: array.subarray(lastUnitStart, overflow),
|
14185
|
-
type: lastUnitType
|
14186
|
-
};
|
14187
|
-
// logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
|
14188
|
-
units.push(unit);
|
14189
|
-
} else {
|
14190
|
-
// lastUnitStart is undefined => this is the first start code found in this PES packet
|
14191
|
-
// first check if start code delimiter is overlapping between 2 PES packets,
|
14192
|
-
// ie it started in last packet (lastState not zero)
|
14193
|
-
// and ended at the beginning of this PES packet (i <= 4 - lastState)
|
14194
|
-
const lastUnit = this.getLastNalUnit(track.samples);
|
14195
|
-
if (lastUnit) {
|
14196
|
-
if (lastState && i <= 4 - lastState) {
|
14197
|
-
// start delimiter overlapping between PES packets
|
14198
|
-
// strip start delimiter bytes from the end of last NAL unit
|
14199
|
-
// check if lastUnit had a state different from zero
|
14200
|
-
if (lastUnit.state) {
|
14201
|
-
// strip last bytes
|
14202
|
-
lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
|
14203
|
-
}
|
14204
|
-
}
|
14205
|
-
// If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
|
14206
|
-
|
14207
|
-
if (overflow > 0) {
|
14208
|
-
// logger.log('first NALU found with overflow:' + overflow);
|
14209
|
-
lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
|
14210
|
-
lastUnit.state = 0;
|
14211
|
-
}
|
14212
|
-
}
|
14213
|
-
}
|
14214
|
-
// check if we can read unit type
|
14215
|
-
if (i < len) {
|
14216
|
-
unitType = this.getNALuType(array, i);
|
14217
|
-
// logger.log('find NALU @ offset:' + i + ',type:' + unitType);
|
14218
|
-
lastUnitStart = i;
|
14219
|
-
lastUnitType = unitType;
|
14220
|
-
state = 0;
|
14221
|
-
} else {
|
14222
|
-
// not enough byte to read unit type. let's read it on next PES parsing
|
14223
|
-
state = -1;
|
14224
|
-
}
|
14225
|
-
} else {
|
14226
|
-
state = 0;
|
14227
|
-
}
|
14228
|
-
}
|
14229
|
-
if (lastUnitStart >= 0 && state >= 0) {
|
14230
|
-
const unit = {
|
14231
|
-
data: array.subarray(lastUnitStart, len),
|
14232
|
-
type: lastUnitType,
|
14233
|
-
state: state
|
14234
|
-
};
|
14235
|
-
units.push(unit);
|
14236
|
-
// logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
|
14237
|
-
}
|
14238
|
-
// no NALu found
|
14239
|
-
if (units.length === 0) {
|
14240
|
-
// append pes.data to previous NAL unit
|
14241
|
-
const lastUnit = this.getLastNalUnit(track.samples);
|
14242
|
-
if (lastUnit) {
|
14243
|
-
lastUnit.data = appendUint8Array(lastUnit.data, array);
|
14244
|
-
}
|
14245
|
-
}
|
14246
|
-
track.naluState = state;
|
14247
|
-
return units;
|
14248
|
-
}
|
14249
13974
|
}
|
14250
13975
|
|
14251
13976
|
/**
|
@@ -14388,11 +14113,194 @@ class ExpGolomb {
|
|
14388
14113
|
readUInt() {
|
14389
14114
|
return this.readBits(32);
|
14390
14115
|
}
|
14116
|
+
|
14117
|
+
/**
|
14118
|
+
* Advance the ExpGolomb decoder past a scaling list. The scaling
|
14119
|
+
* list is optionally transmitted as part of a sequence parameter
|
14120
|
+
* set and is not relevant to transmuxing.
|
14121
|
+
* @param count the number of entries in this scaling list
|
14122
|
+
* @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
|
14123
|
+
*/
|
14124
|
+
skipScalingList(count) {
|
14125
|
+
let lastScale = 8;
|
14126
|
+
let nextScale = 8;
|
14127
|
+
let deltaScale;
|
14128
|
+
for (let j = 0; j < count; j++) {
|
14129
|
+
if (nextScale !== 0) {
|
14130
|
+
deltaScale = this.readEG();
|
14131
|
+
nextScale = (lastScale + deltaScale + 256) % 256;
|
14132
|
+
}
|
14133
|
+
lastScale = nextScale === 0 ? lastScale : nextScale;
|
14134
|
+
}
|
14135
|
+
}
|
14136
|
+
|
14137
|
+
/**
|
14138
|
+
* Read a sequence parameter set and return some interesting video
|
14139
|
+
* properties. A sequence parameter set is the H264 metadata that
|
14140
|
+
* describes the properties of upcoming video frames.
|
14141
|
+
* @returns an object with configuration parsed from the
|
14142
|
+
* sequence parameter set, including the dimensions of the
|
14143
|
+
* associated video frames.
|
14144
|
+
*/
|
14145
|
+
readSPS() {
|
14146
|
+
let frameCropLeftOffset = 0;
|
14147
|
+
let frameCropRightOffset = 0;
|
14148
|
+
let frameCropTopOffset = 0;
|
14149
|
+
let frameCropBottomOffset = 0;
|
14150
|
+
let numRefFramesInPicOrderCntCycle;
|
14151
|
+
let scalingListCount;
|
14152
|
+
let i;
|
14153
|
+
const readUByte = this.readUByte.bind(this);
|
14154
|
+
const readBits = this.readBits.bind(this);
|
14155
|
+
const readUEG = this.readUEG.bind(this);
|
14156
|
+
const readBoolean = this.readBoolean.bind(this);
|
14157
|
+
const skipBits = this.skipBits.bind(this);
|
14158
|
+
const skipEG = this.skipEG.bind(this);
|
14159
|
+
const skipUEG = this.skipUEG.bind(this);
|
14160
|
+
const skipScalingList = this.skipScalingList.bind(this);
|
14161
|
+
readUByte();
|
14162
|
+
const profileIdc = readUByte(); // profile_idc
|
14163
|
+
readBits(5); // profileCompat constraint_set[0-4]_flag, u(5)
|
14164
|
+
skipBits(3); // reserved_zero_3bits u(3),
|
14165
|
+
readUByte(); // level_idc u(8)
|
14166
|
+
skipUEG(); // seq_parameter_set_id
|
14167
|
+
// some profiles have more optional data we don't need
|
14168
|
+
if (profileIdc === 100 || profileIdc === 110 || profileIdc === 122 || profileIdc === 244 || profileIdc === 44 || profileIdc === 83 || profileIdc === 86 || profileIdc === 118 || profileIdc === 128) {
|
14169
|
+
const chromaFormatIdc = readUEG();
|
14170
|
+
if (chromaFormatIdc === 3) {
|
14171
|
+
skipBits(1);
|
14172
|
+
} // separate_colour_plane_flag
|
14173
|
+
|
14174
|
+
skipUEG(); // bit_depth_luma_minus8
|
14175
|
+
skipUEG(); // bit_depth_chroma_minus8
|
14176
|
+
skipBits(1); // qpprime_y_zero_transform_bypass_flag
|
14177
|
+
if (readBoolean()) {
|
14178
|
+
// seq_scaling_matrix_present_flag
|
14179
|
+
scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
|
14180
|
+
for (i = 0; i < scalingListCount; i++) {
|
14181
|
+
if (readBoolean()) {
|
14182
|
+
// seq_scaling_list_present_flag[ i ]
|
14183
|
+
if (i < 6) {
|
14184
|
+
skipScalingList(16);
|
14185
|
+
} else {
|
14186
|
+
skipScalingList(64);
|
14187
|
+
}
|
14188
|
+
}
|
14189
|
+
}
|
14190
|
+
}
|
14191
|
+
}
|
14192
|
+
skipUEG(); // log2_max_frame_num_minus4
|
14193
|
+
const picOrderCntType = readUEG();
|
14194
|
+
if (picOrderCntType === 0) {
|
14195
|
+
readUEG(); // log2_max_pic_order_cnt_lsb_minus4
|
14196
|
+
} else if (picOrderCntType === 1) {
|
14197
|
+
skipBits(1); // delta_pic_order_always_zero_flag
|
14198
|
+
skipEG(); // offset_for_non_ref_pic
|
14199
|
+
skipEG(); // offset_for_top_to_bottom_field
|
14200
|
+
numRefFramesInPicOrderCntCycle = readUEG();
|
14201
|
+
for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
|
14202
|
+
skipEG();
|
14203
|
+
} // offset_for_ref_frame[ i ]
|
14204
|
+
}
|
14205
|
+
skipUEG(); // max_num_ref_frames
|
14206
|
+
skipBits(1); // gaps_in_frame_num_value_allowed_flag
|
14207
|
+
const picWidthInMbsMinus1 = readUEG();
|
14208
|
+
const picHeightInMapUnitsMinus1 = readUEG();
|
14209
|
+
const frameMbsOnlyFlag = readBits(1);
|
14210
|
+
if (frameMbsOnlyFlag === 0) {
|
14211
|
+
skipBits(1);
|
14212
|
+
} // mb_adaptive_frame_field_flag
|
14213
|
+
|
14214
|
+
skipBits(1); // direct_8x8_inference_flag
|
14215
|
+
if (readBoolean()) {
|
14216
|
+
// frame_cropping_flag
|
14217
|
+
frameCropLeftOffset = readUEG();
|
14218
|
+
frameCropRightOffset = readUEG();
|
14219
|
+
frameCropTopOffset = readUEG();
|
14220
|
+
frameCropBottomOffset = readUEG();
|
14221
|
+
}
|
14222
|
+
let pixelRatio = [1, 1];
|
14223
|
+
if (readBoolean()) {
|
14224
|
+
// vui_parameters_present_flag
|
14225
|
+
if (readBoolean()) {
|
14226
|
+
// aspect_ratio_info_present_flag
|
14227
|
+
const aspectRatioIdc = readUByte();
|
14228
|
+
switch (aspectRatioIdc) {
|
14229
|
+
case 1:
|
14230
|
+
pixelRatio = [1, 1];
|
14231
|
+
break;
|
14232
|
+
case 2:
|
14233
|
+
pixelRatio = [12, 11];
|
14234
|
+
break;
|
14235
|
+
case 3:
|
14236
|
+
pixelRatio = [10, 11];
|
14237
|
+
break;
|
14238
|
+
case 4:
|
14239
|
+
pixelRatio = [16, 11];
|
14240
|
+
break;
|
14241
|
+
case 5:
|
14242
|
+
pixelRatio = [40, 33];
|
14243
|
+
break;
|
14244
|
+
case 6:
|
14245
|
+
pixelRatio = [24, 11];
|
14246
|
+
break;
|
14247
|
+
case 7:
|
14248
|
+
pixelRatio = [20, 11];
|
14249
|
+
break;
|
14250
|
+
case 8:
|
14251
|
+
pixelRatio = [32, 11];
|
14252
|
+
break;
|
14253
|
+
case 9:
|
14254
|
+
pixelRatio = [80, 33];
|
14255
|
+
break;
|
14256
|
+
case 10:
|
14257
|
+
pixelRatio = [18, 11];
|
14258
|
+
break;
|
14259
|
+
case 11:
|
14260
|
+
pixelRatio = [15, 11];
|
14261
|
+
break;
|
14262
|
+
case 12:
|
14263
|
+
pixelRatio = [64, 33];
|
14264
|
+
break;
|
14265
|
+
case 13:
|
14266
|
+
pixelRatio = [160, 99];
|
14267
|
+
break;
|
14268
|
+
case 14:
|
14269
|
+
pixelRatio = [4, 3];
|
14270
|
+
break;
|
14271
|
+
case 15:
|
14272
|
+
pixelRatio = [3, 2];
|
14273
|
+
break;
|
14274
|
+
case 16:
|
14275
|
+
pixelRatio = [2, 1];
|
14276
|
+
break;
|
14277
|
+
case 255:
|
14278
|
+
{
|
14279
|
+
pixelRatio = [readUByte() << 8 | readUByte(), readUByte() << 8 | readUByte()];
|
14280
|
+
break;
|
14281
|
+
}
|
14282
|
+
}
|
14283
|
+
}
|
14284
|
+
}
|
14285
|
+
return {
|
14286
|
+
width: Math.ceil((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2),
|
14287
|
+
height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - (frameMbsOnlyFlag ? 2 : 4) * (frameCropTopOffset + frameCropBottomOffset),
|
14288
|
+
pixelRatio: pixelRatio
|
14289
|
+
};
|
14290
|
+
}
|
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
|
+
}
|
14391
14299
|
}
|
14392
14300
|
|
14393
14301
|
class AvcVideoParser extends BaseVideoParser {
|
14394
|
-
|
14395
|
-
const units = this.
|
14302
|
+
parseAVCPES(track, textTrack, pes, last, duration) {
|
14303
|
+
const units = this.parseAVCNALu(track, pes.data);
|
14396
14304
|
let VideoSample = this.VideoSample;
|
14397
14305
|
let push;
|
14398
14306
|
let spsfound = false;
|
@@ -14417,7 +14325,7 @@ class AvcVideoParser extends BaseVideoParser {
|
|
14417
14325
|
// only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
|
14418
14326
|
if (spsfound && data.length > 4) {
|
14419
14327
|
// retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
|
14420
|
-
const sliceType =
|
14328
|
+
const sliceType = new ExpGolomb(data).readSliceType();
|
14421
14329
|
// 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
|
14422
14330
|
// SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
|
14423
14331
|
// An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
|
@@ -14471,7 +14379,8 @@ class AvcVideoParser extends BaseVideoParser {
|
|
14471
14379
|
push = true;
|
14472
14380
|
spsfound = true;
|
14473
14381
|
const sps = unit.data;
|
14474
|
-
const
|
14382
|
+
const expGolombDecoder = new ExpGolomb(sps);
|
14383
|
+
const config = expGolombDecoder.readSPS();
|
14475
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]) {
|
14476
14385
|
track.width = config.width;
|
14477
14386
|
track.height = config.height;
|
@@ -14527,192 +14436,109 @@ class AvcVideoParser extends BaseVideoParser {
|
|
14527
14436
|
this.VideoSample = null;
|
14528
14437
|
}
|
14529
14438
|
}
|
14530
|
-
|
14531
|
-
|
14532
|
-
|
14533
|
-
|
14534
|
-
const
|
14535
|
-
|
14536
|
-
|
14537
|
-
|
14538
|
-
|
14539
|
-
|
14540
|
-
|
14541
|
-
|
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));
|
14542
14451
|
|
14543
|
-
|
14544
|
-
|
14545
|
-
|
14546
|
-
|
14547
|
-
|
14548
|
-
|
14549
|
-
|
14550
|
-
let lastScale = 8;
|
14551
|
-
let nextScale = 8;
|
14552
|
-
let deltaScale;
|
14553
|
-
for (let j = 0; j < count; j++) {
|
14554
|
-
if (nextScale !== 0) {
|
14555
|
-
deltaScale = reader.readEG();
|
14556
|
-
nextScale = (lastScale + deltaScale + 256) % 256;
|
14557
|
-
}
|
14558
|
-
lastScale = nextScale === 0 ? lastScale : nextScale;
|
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;
|
14559
14459
|
}
|
14560
|
-
|
14561
|
-
|
14562
|
-
|
14563
|
-
|
14564
|
-
|
14565
|
-
|
14566
|
-
|
14567
|
-
|
14568
|
-
|
14569
|
-
|
14570
|
-
|
14571
|
-
|
14572
|
-
|
14573
|
-
|
14574
|
-
|
14575
|
-
|
14576
|
-
|
14577
|
-
|
14578
|
-
|
14579
|
-
|
14580
|
-
|
14581
|
-
|
14582
|
-
|
14583
|
-
|
14584
|
-
|
14585
|
-
|
14586
|
-
|
14587
|
-
|
14588
|
-
|
14589
|
-
|
14590
|
-
|
14591
|
-
|
14592
|
-
|
14593
|
-
|
14594
|
-
|
14595
|
-
|
14596
|
-
|
14597
|
-
|
14598
|
-
|
14599
|
-
|
14600
|
-
|
14601
|
-
|
14602
|
-
|
14603
|
-
|
14604
|
-
|
14605
|
-
scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
|
14606
|
-
for (i = 0; i < scalingListCount; i++) {
|
14607
|
-
if (readBoolean()) {
|
14608
|
-
// seq_scaling_list_present_flag[ i ]
|
14609
|
-
if (i < 6) {
|
14610
|
-
skipScalingList(16, eg);
|
14611
|
-
} else {
|
14612
|
-
skipScalingList(64, eg);
|
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;
|
14613
14505
|
}
|
14614
14506
|
}
|
14615
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;
|
14616
14521
|
}
|
14617
14522
|
}
|
14618
|
-
|
14619
|
-
|
14620
|
-
|
14621
|
-
|
14622
|
-
|
14623
|
-
|
14624
|
-
|
14625
|
-
|
14626
|
-
numRefFramesInPicOrderCntCycle = readUEG();
|
14627
|
-
for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
|
14628
|
-
skipEG();
|
14629
|
-
} // offset_for_ref_frame[ i ]
|
14630
|
-
}
|
14631
|
-
skipUEG(); // max_num_ref_frames
|
14632
|
-
skipBits(1); // gaps_in_frame_num_value_allowed_flag
|
14633
|
-
const picWidthInMbsMinus1 = readUEG();
|
14634
|
-
const picHeightInMapUnitsMinus1 = readUEG();
|
14635
|
-
const frameMbsOnlyFlag = readBits(1);
|
14636
|
-
if (frameMbsOnlyFlag === 0) {
|
14637
|
-
skipBits(1);
|
14638
|
-
} // mb_adaptive_frame_field_flag
|
14639
|
-
|
14640
|
-
skipBits(1); // direct_8x8_inference_flag
|
14641
|
-
if (readBoolean()) {
|
14642
|
-
// frame_cropping_flag
|
14643
|
-
frameCropLeftOffset = readUEG();
|
14644
|
-
frameCropRightOffset = readUEG();
|
14645
|
-
frameCropTopOffset = readUEG();
|
14646
|
-
frameCropBottomOffset = readUEG();
|
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);
|
14647
14531
|
}
|
14648
|
-
|
14649
|
-
if (
|
14650
|
-
//
|
14651
|
-
|
14652
|
-
|
14653
|
-
|
14654
|
-
switch (aspectRatioIdc) {
|
14655
|
-
case 1:
|
14656
|
-
pixelRatio = [1, 1];
|
14657
|
-
break;
|
14658
|
-
case 2:
|
14659
|
-
pixelRatio = [12, 11];
|
14660
|
-
break;
|
14661
|
-
case 3:
|
14662
|
-
pixelRatio = [10, 11];
|
14663
|
-
break;
|
14664
|
-
case 4:
|
14665
|
-
pixelRatio = [16, 11];
|
14666
|
-
break;
|
14667
|
-
case 5:
|
14668
|
-
pixelRatio = [40, 33];
|
14669
|
-
break;
|
14670
|
-
case 6:
|
14671
|
-
pixelRatio = [24, 11];
|
14672
|
-
break;
|
14673
|
-
case 7:
|
14674
|
-
pixelRatio = [20, 11];
|
14675
|
-
break;
|
14676
|
-
case 8:
|
14677
|
-
pixelRatio = [32, 11];
|
14678
|
-
break;
|
14679
|
-
case 9:
|
14680
|
-
pixelRatio = [80, 33];
|
14681
|
-
break;
|
14682
|
-
case 10:
|
14683
|
-
pixelRatio = [18, 11];
|
14684
|
-
break;
|
14685
|
-
case 11:
|
14686
|
-
pixelRatio = [15, 11];
|
14687
|
-
break;
|
14688
|
-
case 12:
|
14689
|
-
pixelRatio = [64, 33];
|
14690
|
-
break;
|
14691
|
-
case 13:
|
14692
|
-
pixelRatio = [160, 99];
|
14693
|
-
break;
|
14694
|
-
case 14:
|
14695
|
-
pixelRatio = [4, 3];
|
14696
|
-
break;
|
14697
|
-
case 15:
|
14698
|
-
pixelRatio = [3, 2];
|
14699
|
-
break;
|
14700
|
-
case 16:
|
14701
|
-
pixelRatio = [2, 1];
|
14702
|
-
break;
|
14703
|
-
case 255:
|
14704
|
-
{
|
14705
|
-
pixelRatio = [readUByte() << 8 | readUByte(), readUByte() << 8 | readUByte()];
|
14706
|
-
break;
|
14707
|
-
}
|
14708
|
-
}
|
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);
|
14709
14538
|
}
|
14710
14539
|
}
|
14711
|
-
|
14712
|
-
|
14713
|
-
height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - (frameMbsOnlyFlag ? 2 : 4) * (frameCropTopOffset + frameCropBottomOffset),
|
14714
|
-
pixelRatio: pixelRatio
|
14715
|
-
};
|
14540
|
+
track.naluState = state;
|
14541
|
+
return units;
|
14716
14542
|
}
|
14717
14543
|
}
|
14718
14544
|
|
@@ -14730,7 +14556,7 @@ class SampleAesDecrypter {
|
|
14730
14556
|
});
|
14731
14557
|
}
|
14732
14558
|
decryptBuffer(encryptedData) {
|
14733
|
-
return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer
|
14559
|
+
return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer);
|
14734
14560
|
}
|
14735
14561
|
|
14736
14562
|
// AAC - encrypt all full 16 bytes blocks starting from offset 16
|
@@ -14844,7 +14670,7 @@ class TSDemuxer {
|
|
14844
14670
|
this.observer = observer;
|
14845
14671
|
this.config = config;
|
14846
14672
|
this.typeSupported = typeSupported;
|
14847
|
-
this.videoParser =
|
14673
|
+
this.videoParser = new AvcVideoParser();
|
14848
14674
|
}
|
14849
14675
|
static probe(data) {
|
14850
14676
|
const syncOffset = TSDemuxer.syncOffset(data);
|
@@ -15009,16 +14835,7 @@ class TSDemuxer {
|
|
15009
14835
|
case videoPid:
|
15010
14836
|
if (stt) {
|
15011
14837
|
if (videoData && (pes = parsePES(videoData))) {
|
15012
|
-
|
15013
|
-
switch (videoTrack.segmentCodec) {
|
15014
|
-
case 'avc':
|
15015
|
-
this.videoParser = new AvcVideoParser();
|
15016
|
-
break;
|
15017
|
-
}
|
15018
|
-
}
|
15019
|
-
if (this.videoParser !== null) {
|
15020
|
-
this.videoParser.parsePES(videoTrack, textTrack, pes, false, this._duration);
|
15021
|
-
}
|
14838
|
+
this.videoParser.parseAVCPES(videoTrack, textTrack, pes, false, this._duration);
|
15022
14839
|
}
|
15023
14840
|
videoData = {
|
15024
14841
|
data: [],
|
@@ -15180,17 +14997,8 @@ class TSDemuxer {
|
|
15180
14997
|
// try to parse last PES packets
|
15181
14998
|
let pes;
|
15182
14999
|
if (videoData && (pes = parsePES(videoData))) {
|
15183
|
-
|
15184
|
-
|
15185
|
-
case 'avc':
|
15186
|
-
this.videoParser = new AvcVideoParser();
|
15187
|
-
break;
|
15188
|
-
}
|
15189
|
-
}
|
15190
|
-
if (this.videoParser !== null) {
|
15191
|
-
this.videoParser.parsePES(videoTrack, textTrack, pes, true, this._duration);
|
15192
|
-
videoTrack.pesData = null;
|
15193
|
-
}
|
15000
|
+
this.videoParser.parseAVCPES(videoTrack, textTrack, pes, true, this._duration);
|
15001
|
+
videoTrack.pesData = null;
|
15194
15002
|
} else {
|
15195
15003
|
// either avcData null or PES truncated, keep it for next frag parsing
|
15196
15004
|
videoTrack.pesData = videoData;
|
@@ -15493,10 +15301,7 @@ function parsePMT(data, offset, typeSupported, isSampleAes) {
|
|
15493
15301
|
logger.warn('Unsupported EC-3 in M2TS found');
|
15494
15302
|
break;
|
15495
15303
|
case 0x24:
|
15496
|
-
|
15497
|
-
{
|
15498
|
-
logger.warn('Unsupported HEVC in M2TS found');
|
15499
|
-
}
|
15304
|
+
logger.warn('Unsupported HEVC in M2TS found');
|
15500
15305
|
break;
|
15501
15306
|
}
|
15502
15307
|
// move to the next table entry
|
@@ -15719,8 +15524,6 @@ class MP4 {
|
|
15719
15524
|
avc1: [],
|
15720
15525
|
// codingname
|
15721
15526
|
avcC: [],
|
15722
|
-
hvc1: [],
|
15723
|
-
hvcC: [],
|
15724
15527
|
btrt: [],
|
15725
15528
|
dinf: [],
|
15726
15529
|
dref: [],
|
@@ -16145,10 +15948,8 @@ class MP4 {
|
|
16145
15948
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.ac3(track));
|
16146
15949
|
}
|
16147
15950
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track));
|
16148
|
-
} else if (track.segmentCodec === 'avc') {
|
16149
|
-
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
|
16150
15951
|
} else {
|
16151
|
-
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.
|
15952
|
+
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
|
16152
15953
|
}
|
16153
15954
|
}
|
16154
15955
|
static tkhd(track) {
|
@@ -16286,84 +16087,6 @@ class MP4 {
|
|
16286
16087
|
const result = appendUint8Array(MP4.FTYP, movie);
|
16287
16088
|
return result;
|
16288
16089
|
}
|
16289
|
-
static hvc1(track) {
|
16290
|
-
const ps = track.params;
|
16291
|
-
const units = [track.vps, track.sps, track.pps];
|
16292
|
-
const NALuLengthSize = 4;
|
16293
|
-
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]);
|
16294
|
-
|
16295
|
-
// compute hvcC size in bytes
|
16296
|
-
let length = config.length;
|
16297
|
-
for (let i = 0; i < units.length; i += 1) {
|
16298
|
-
length += 3;
|
16299
|
-
for (let j = 0; j < units[i].length; j += 1) {
|
16300
|
-
length += 2 + units[i][j].length;
|
16301
|
-
}
|
16302
|
-
}
|
16303
|
-
const hvcC = new Uint8Array(length);
|
16304
|
-
hvcC.set(config, 0);
|
16305
|
-
length = config.length;
|
16306
|
-
// append parameter set units: one vps, one or more sps and pps
|
16307
|
-
const iMax = units.length - 1;
|
16308
|
-
for (let i = 0; i < units.length; i += 1) {
|
16309
|
-
hvcC.set(new Uint8Array([32 + i | (i === iMax ? 128 : 0), 0x00, units[i].length]), length);
|
16310
|
-
length += 3;
|
16311
|
-
for (let j = 0; j < units[i].length; j += 1) {
|
16312
|
-
hvcC.set(new Uint8Array([units[i][j].length >> 8, units[i][j].length & 255]), length);
|
16313
|
-
length += 2;
|
16314
|
-
hvcC.set(units[i][j], length);
|
16315
|
-
length += units[i][j].length;
|
16316
|
-
}
|
16317
|
-
}
|
16318
|
-
const hvcc = MP4.box(MP4.types.hvcC, hvcC);
|
16319
|
-
const width = track.width;
|
16320
|
-
const height = track.height;
|
16321
|
-
const hSpacing = track.pixelRatio[0];
|
16322
|
-
const vSpacing = track.pixelRatio[1];
|
16323
|
-
return MP4.box(MP4.types.hvc1, new Uint8Array([0x00, 0x00, 0x00,
|
16324
|
-
// reserved
|
16325
|
-
0x00, 0x00, 0x00,
|
16326
|
-
// reserved
|
16327
|
-
0x00, 0x01,
|
16328
|
-
// data_reference_index
|
16329
|
-
0x00, 0x00,
|
16330
|
-
// pre_defined
|
16331
|
-
0x00, 0x00,
|
16332
|
-
// reserved
|
16333
|
-
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
16334
|
-
// pre_defined
|
16335
|
-
width >> 8 & 0xff, width & 0xff,
|
16336
|
-
// width
|
16337
|
-
height >> 8 & 0xff, height & 0xff,
|
16338
|
-
// height
|
16339
|
-
0x00, 0x48, 0x00, 0x00,
|
16340
|
-
// horizresolution
|
16341
|
-
0x00, 0x48, 0x00, 0x00,
|
16342
|
-
// vertresolution
|
16343
|
-
0x00, 0x00, 0x00, 0x00,
|
16344
|
-
// reserved
|
16345
|
-
0x00, 0x01,
|
16346
|
-
// frame_count
|
16347
|
-
0x12, 0x64, 0x61, 0x69, 0x6c,
|
16348
|
-
// dailymotion/hls.js
|
16349
|
-
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,
|
16350
|
-
// compressorname
|
16351
|
-
0x00, 0x18,
|
16352
|
-
// depth = 24
|
16353
|
-
0x11, 0x11]),
|
16354
|
-
// pre_defined = -1
|
16355
|
-
hvcc, MP4.box(MP4.types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80,
|
16356
|
-
// bufferSizeDB
|
16357
|
-
0x00, 0x2d, 0xc6, 0xc0,
|
16358
|
-
// maxBitrate
|
16359
|
-
0x00, 0x2d, 0xc6, 0xc0])),
|
16360
|
-
// avgBitrate
|
16361
|
-
MP4.box(MP4.types.pasp, new Uint8Array([hSpacing >> 24,
|
16362
|
-
// hSpacing
|
16363
|
-
hSpacing >> 16 & 0xff, hSpacing >> 8 & 0xff, hSpacing & 0xff, vSpacing >> 24,
|
16364
|
-
// vSpacing
|
16365
|
-
vSpacing >> 16 & 0xff, vSpacing >> 8 & 0xff, vSpacing & 0xff])));
|
16366
|
-
}
|
16367
16090
|
}
|
16368
16091
|
MP4.types = void 0;
|
16369
16092
|
MP4.HDLR_TYPES = void 0;
|
@@ -16739,9 +16462,9 @@ class MP4Remuxer {
|
|
16739
16462
|
const foundOverlap = delta < -1;
|
16740
16463
|
if (foundHole || foundOverlap) {
|
16741
16464
|
if (foundHole) {
|
16742
|
-
logger.warn(
|
16465
|
+
logger.warn(`AVC: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
|
16743
16466
|
} else {
|
16744
|
-
logger.warn(
|
16467
|
+
logger.warn(`AVC: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
|
16745
16468
|
}
|
16746
16469
|
if (!foundOverlap || nextAvcDts >= inputSamples[0].pts || chromeVersion) {
|
16747
16470
|
firstDTS = nextAvcDts;
|
@@ -16750,24 +16473,12 @@ class MP4Remuxer {
|
|
16750
16473
|
inputSamples[0].dts = firstDTS;
|
16751
16474
|
inputSamples[0].pts = firstPTS;
|
16752
16475
|
} else {
|
16753
|
-
let isPTSOrderRetained = true;
|
16754
16476
|
for (let i = 0; i < inputSamples.length; i++) {
|
16755
|
-
if (inputSamples[i].dts > firstPTS
|
16477
|
+
if (inputSamples[i].dts > firstPTS) {
|
16756
16478
|
break;
|
16757
16479
|
}
|
16758
|
-
const prevPTS = inputSamples[i].pts;
|
16759
16480
|
inputSamples[i].dts -= delta;
|
16760
16481
|
inputSamples[i].pts -= delta;
|
16761
|
-
|
16762
|
-
// check to see if this sample's PTS order has changed
|
16763
|
-
// relative to the next one
|
16764
|
-
if (i < inputSamples.length - 1) {
|
16765
|
-
const nextSamplePTS = inputSamples[i + 1].pts;
|
16766
|
-
const currentSamplePTS = inputSamples[i].pts;
|
16767
|
-
const currentOrder = nextSamplePTS <= currentSamplePTS;
|
16768
|
-
const prevOrder = nextSamplePTS <= prevPTS;
|
16769
|
-
isPTSOrderRetained = currentOrder == prevOrder;
|
16770
|
-
}
|
16771
16482
|
}
|
16772
16483
|
}
|
16773
16484
|
logger.log(`Video: Initial PTS/DTS adjusted: ${toMsFromMpegTsClock(firstPTS, true)}/${toMsFromMpegTsClock(firstDTS, true)}, delta: ${toMsFromMpegTsClock(delta, true)} ms`);
|
@@ -16915,7 +16626,7 @@ class MP4Remuxer {
|
|
16915
16626
|
}
|
16916
16627
|
}
|
16917
16628
|
}
|
16918
|
-
// next AVC
|
16629
|
+
// next AVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
|
16919
16630
|
mp4SampleDuration = stretchedLastFrame || !mp4SampleDuration ? averageSampleDuration : mp4SampleDuration;
|
16920
16631
|
this.nextAvcDts = nextAvcDts = lastDTS + mp4SampleDuration;
|
16921
16632
|
this.videoSampleDuration = mp4SampleDuration;
|
@@ -17048,7 +16759,7 @@ class MP4Remuxer {
|
|
17048
16759
|
logger.warn(`[mp4-remuxer]: Injecting ${missing} audio frame @ ${(nextPts / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`);
|
17049
16760
|
for (let j = 0; j < missing; j++) {
|
17050
16761
|
const newStamp = Math.max(nextPts, 0);
|
17051
|
-
let fillFrame = AAC.getSilentFrame(track.
|
16762
|
+
let fillFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
|
17052
16763
|
if (!fillFrame) {
|
17053
16764
|
logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.');
|
17054
16765
|
fillFrame = sample.unit.subarray();
|
@@ -17176,7 +16887,7 @@ class MP4Remuxer {
|
|
17176
16887
|
// samples count of this segment's duration
|
17177
16888
|
const nbSamples = Math.ceil((endDTS - startDTS) / frameDuration);
|
17178
16889
|
// silent frame
|
17179
|
-
const silentFrame = AAC.getSilentFrame(track.
|
16890
|
+
const silentFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
|
17180
16891
|
logger.warn('[mp4-remuxer]: remux empty Audio');
|
17181
16892
|
// Can't remux if we can't generate a silent frame...
|
17182
16893
|
if (!silentFrame) {
|
@@ -17567,15 +17278,13 @@ class Transmuxer {
|
|
17567
17278
|
initSegmentData
|
17568
17279
|
} = transmuxConfig;
|
17569
17280
|
const keyData = getEncryptionType(uintData, decryptdata);
|
17570
|
-
if (keyData &&
|
17281
|
+
if (keyData && keyData.method === 'AES-128') {
|
17571
17282
|
const decrypter = this.getDecrypter();
|
17572
|
-
const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
|
17573
|
-
|
17574
17283
|
// Software decryption is synchronous; webCrypto is not
|
17575
17284
|
if (decrypter.isSync()) {
|
17576
17285
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
17577
17286
|
// data is handled in the flush() call
|
17578
|
-
let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer
|
17287
|
+
let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer);
|
17579
17288
|
// For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
|
17580
17289
|
const loadingParts = chunkMeta.part > -1;
|
17581
17290
|
if (loadingParts) {
|
@@ -17587,7 +17296,7 @@ class Transmuxer {
|
|
17587
17296
|
}
|
17588
17297
|
uintData = new Uint8Array(decryptedData);
|
17589
17298
|
} else {
|
17590
|
-
this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer
|
17299
|
+
this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer).then(decryptedData => {
|
17591
17300
|
// Calling push here is important; if flush() is called while this is still resolving, this ensures that
|
17592
17301
|
// the decrypted data has been transmuxed
|
17593
17302
|
const result = this.push(decryptedData, null, chunkMeta);
|
@@ -18241,7 +17950,14 @@ class TransmuxerInterface {
|
|
18241
17950
|
this.observer = new EventEmitter();
|
18242
17951
|
this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
|
18243
17952
|
this.observer.on(Events.ERROR, forwardMessage);
|
18244
|
-
const
|
17953
|
+
const MediaSource = getMediaSource(config.preferManagedMediaSource) || {
|
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
|
+
};
|
18245
17961
|
|
18246
17962
|
// navigator.vendor is not always available in Web Worker
|
18247
17963
|
// refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
|
@@ -18505,9 +18221,8 @@ const STALL_MINIMUM_DURATION_MS = 250;
|
|
18505
18221
|
const MAX_START_GAP_JUMP = 2.0;
|
18506
18222
|
const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
|
18507
18223
|
const SKIP_BUFFER_RANGE_START = 0.05;
|
18508
|
-
class GapController
|
18224
|
+
class GapController {
|
18509
18225
|
constructor(config, media, fragmentTracker, hls) {
|
18510
|
-
super('gap-controller', hls.logger);
|
18511
18226
|
this.config = void 0;
|
18512
18227
|
this.media = null;
|
18513
18228
|
this.fragmentTracker = void 0;
|
@@ -18517,7 +18232,6 @@ class GapController extends Logger {
|
|
18517
18232
|
this.stalled = null;
|
18518
18233
|
this.moved = false;
|
18519
18234
|
this.seeking = false;
|
18520
|
-
this.ended = 0;
|
18521
18235
|
this.config = config;
|
18522
18236
|
this.media = media;
|
18523
18237
|
this.fragmentTracker = fragmentTracker;
|
@@ -18535,7 +18249,7 @@ class GapController extends Logger {
|
|
18535
18249
|
*
|
18536
18250
|
* @param lastCurrentTime - Previously read playhead position
|
18537
18251
|
*/
|
18538
|
-
poll(lastCurrentTime, activeFrag
|
18252
|
+
poll(lastCurrentTime, activeFrag) {
|
18539
18253
|
const {
|
18540
18254
|
config,
|
18541
18255
|
media,
|
@@ -18554,7 +18268,6 @@ class GapController extends Logger {
|
|
18554
18268
|
|
18555
18269
|
// The playhead is moving, no-op
|
18556
18270
|
if (currentTime !== lastCurrentTime) {
|
18557
|
-
this.ended = 0;
|
18558
18271
|
this.moved = true;
|
18559
18272
|
if (!seeking) {
|
18560
18273
|
this.nudgeRetry = 0;
|
@@ -18563,7 +18276,7 @@ class GapController extends Logger {
|
|
18563
18276
|
// The playhead is now moving, but was previously stalled
|
18564
18277
|
if (this.stallReported) {
|
18565
18278
|
const _stalledDuration = self.performance.now() - stalled;
|
18566
|
-
|
18279
|
+
logger.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
|
18567
18280
|
this.stallReported = false;
|
18568
18281
|
}
|
18569
18282
|
this.stalled = null;
|
@@ -18599,6 +18312,7 @@ class GapController extends Logger {
|
|
18599
18312
|
// Skip start gaps if we haven't played, but the last poll detected the start of a stall
|
18600
18313
|
// The addition poll gives the browser a chance to jump the gap for us
|
18601
18314
|
if (!this.moved && this.stalled !== null) {
|
18315
|
+
var _level$details;
|
18602
18316
|
// There is no playable buffer (seeked, waiting for buffer)
|
18603
18317
|
const isBuffered = bufferInfo.len > 0;
|
18604
18318
|
if (!isBuffered && !nextStart) {
|
@@ -18610,8 +18324,9 @@ class GapController extends Logger {
|
|
18610
18324
|
// When joining a live stream with audio tracks, account for live playlist window sliding by allowing
|
18611
18325
|
// a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
|
18612
18326
|
// that begins over 1 target duration after the video start position.
|
18613
|
-
const
|
18614
|
-
const
|
18327
|
+
const level = this.hls.levels ? this.hls.levels[this.hls.currentLevel] : null;
|
18328
|
+
const isLive = level == null ? void 0 : (_level$details = level.details) == null ? void 0 : _level$details.live;
|
18329
|
+
const maxStartGapJump = isLive ? level.details.targetduration * 2 : MAX_START_GAP_JUMP;
|
18615
18330
|
const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
|
18616
18331
|
if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
|
18617
18332
|
if (!media.paused) {
|
@@ -18629,17 +18344,6 @@ class GapController extends Logger {
|
|
18629
18344
|
}
|
18630
18345
|
const stalledDuration = tnow - stalled;
|
18631
18346
|
if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) {
|
18632
|
-
// Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
|
18633
|
-
if (state === State.ENDED && !(levelDetails && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? void 0 : levelDetails.edge) || 0)) < 1) {
|
18634
|
-
if (stalledDuration < 1000 || this.ended) {
|
18635
|
-
return;
|
18636
|
-
}
|
18637
|
-
this.ended = currentTime;
|
18638
|
-
this.hls.trigger(Events.MEDIA_ENDED, {
|
18639
|
-
stalled: true
|
18640
|
-
});
|
18641
|
-
return;
|
18642
|
-
}
|
18643
18347
|
// Report stalling after trying to fix
|
18644
18348
|
this._reportStall(bufferInfo);
|
18645
18349
|
if (!this.media) {
|
@@ -18683,7 +18387,7 @@ class GapController extends Logger {
|
|
18683
18387
|
// needs to cross some sort of threshold covering all source-buffers content
|
18684
18388
|
// to start playing properly.
|
18685
18389
|
if ((bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
|
18686
|
-
|
18390
|
+
logger.warn('Trying to nudge playhead over buffer-hole');
|
18687
18391
|
// Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
|
18688
18392
|
// We only try to jump the hole if it's under the configured size
|
18689
18393
|
// Reset stalled so to rearm watchdog timer
|
@@ -18707,7 +18411,7 @@ class GapController extends Logger {
|
|
18707
18411
|
// Report stalled error once
|
18708
18412
|
this.stallReported = true;
|
18709
18413
|
const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${JSON.stringify(bufferInfo)})`);
|
18710
|
-
|
18414
|
+
logger.warn(error.message);
|
18711
18415
|
hls.trigger(Events.ERROR, {
|
18712
18416
|
type: ErrorTypes.MEDIA_ERROR,
|
18713
18417
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
@@ -18775,7 +18479,7 @@ class GapController extends Logger {
|
|
18775
18479
|
}
|
18776
18480
|
}
|
18777
18481
|
const targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
|
18778
|
-
|
18482
|
+
logger.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
|
18779
18483
|
this.moved = true;
|
18780
18484
|
this.stalled = null;
|
18781
18485
|
media.currentTime = targetTime;
|
@@ -18816,7 +18520,7 @@ class GapController extends Logger {
|
|
18816
18520
|
const targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset;
|
18817
18521
|
// playback stalled in buffered area ... let's nudge currentTime to try to overcome this
|
18818
18522
|
const error = new Error(`Nudging 'currentTime' from ${currentTime} to ${targetTime}`);
|
18819
|
-
|
18523
|
+
logger.warn(error.message);
|
18820
18524
|
media.currentTime = targetTime;
|
18821
18525
|
hls.trigger(Events.ERROR, {
|
18822
18526
|
type: ErrorTypes.MEDIA_ERROR,
|
@@ -18826,7 +18530,7 @@ class GapController extends Logger {
|
|
18826
18530
|
});
|
18827
18531
|
} else {
|
18828
18532
|
const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`);
|
18829
|
-
|
18533
|
+
logger.error(error.message);
|
18830
18534
|
hls.trigger(Events.ERROR, {
|
18831
18535
|
type: ErrorTypes.MEDIA_ERROR,
|
18832
18536
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
@@ -18841,7 +18545,7 @@ const TICK_INTERVAL = 100; // how often to tick in ms
|
|
18841
18545
|
|
18842
18546
|
class StreamController extends BaseStreamController {
|
18843
18547
|
constructor(hls, fragmentTracker, keyLoader) {
|
18844
|
-
super(hls, fragmentTracker, keyLoader, 'stream-controller', PlaylistLevelType.MAIN);
|
18548
|
+
super(hls, fragmentTracker, keyLoader, '[stream-controller]', PlaylistLevelType.MAIN);
|
18845
18549
|
this.audioCodecSwap = false;
|
18846
18550
|
this.gapController = null;
|
18847
18551
|
this.level = -1;
|
@@ -18849,43 +18553,27 @@ class StreamController extends BaseStreamController {
|
|
18849
18553
|
this.altAudio = false;
|
18850
18554
|
this.audioOnly = false;
|
18851
18555
|
this.fragPlaying = null;
|
18556
|
+
this.onvplaying = null;
|
18557
|
+
this.onvseeked = null;
|
18852
18558
|
this.fragLastKbps = 0;
|
18853
18559
|
this.couldBacktrack = false;
|
18854
18560
|
this.backtrackFragment = null;
|
18855
18561
|
this.audioCodecSwitch = false;
|
18856
18562
|
this.videoBuffer = null;
|
18857
|
-
this.
|
18858
|
-
// tick to speed up FRAG_CHANGED triggering
|
18859
|
-
this.tick();
|
18860
|
-
};
|
18861
|
-
this.onMediaSeeked = () => {
|
18862
|
-
const media = this.media;
|
18863
|
-
const currentTime = media ? media.currentTime : null;
|
18864
|
-
if (isFiniteNumber(currentTime)) {
|
18865
|
-
this.log(`Media seeked to ${currentTime.toFixed(3)}`);
|
18866
|
-
}
|
18867
|
-
|
18868
|
-
// If seeked was issued before buffer was appended do not tick immediately
|
18869
|
-
const bufferInfo = this.getMainFwdBufferInfo();
|
18870
|
-
if (bufferInfo === null || bufferInfo.len === 0) {
|
18871
|
-
this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
|
18872
|
-
return;
|
18873
|
-
}
|
18874
|
-
|
18875
|
-
// tick to speed up FRAG_CHANGED triggering
|
18876
|
-
this.tick();
|
18877
|
-
};
|
18878
|
-
this.registerListeners();
|
18563
|
+
this._registerListeners();
|
18879
18564
|
}
|
18880
|
-
|
18881
|
-
super.registerListeners();
|
18565
|
+
_registerListeners() {
|
18882
18566
|
const {
|
18883
18567
|
hls
|
18884
18568
|
} = 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);
|
18885
18572
|
hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
18886
18573
|
hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
|
18887
18574
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
18888
18575
|
hls.on(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
|
18576
|
+
hls.on(Events.ERROR, this.onError, this);
|
18889
18577
|
hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
18890
18578
|
hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
18891
18579
|
hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -18893,14 +18581,17 @@ class StreamController extends BaseStreamController {
|
|
18893
18581
|
hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
|
18894
18582
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
18895
18583
|
}
|
18896
|
-
|
18897
|
-
super.unregisterListeners();
|
18584
|
+
_unregisterListeners() {
|
18898
18585
|
const {
|
18899
18586
|
hls
|
18900
18587
|
} = 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);
|
18901
18591
|
hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
18902
18592
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
18903
18593
|
hls.off(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
|
18594
|
+
hls.off(Events.ERROR, this.onError, this);
|
18904
18595
|
hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
18905
18596
|
hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
18906
18597
|
hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -18909,9 +18600,7 @@ class StreamController extends BaseStreamController {
|
|
18909
18600
|
hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
18910
18601
|
}
|
18911
18602
|
onHandlerDestroying() {
|
18912
|
-
|
18913
|
-
this.onMediaPlaying = this.onMediaSeeked = null;
|
18914
|
-
this.unregisterListeners();
|
18603
|
+
this._unregisterListeners();
|
18915
18604
|
super.onHandlerDestroying();
|
18916
18605
|
}
|
18917
18606
|
startLoad(startPosition) {
|
@@ -19031,7 +18720,7 @@ class StreamController extends BaseStreamController {
|
|
19031
18720
|
if (this.altAudio && this.audioOnly) {
|
19032
18721
|
return;
|
19033
18722
|
}
|
19034
|
-
if (!
|
18723
|
+
if (!(levels != null && levels[level])) {
|
19035
18724
|
return;
|
19036
18725
|
}
|
19037
18726
|
const levelInfo = levels[level];
|
@@ -19239,17 +18928,20 @@ class StreamController extends BaseStreamController {
|
|
19239
18928
|
onMediaAttached(event, data) {
|
19240
18929
|
super.onMediaAttached(event, data);
|
19241
18930
|
const media = data.media;
|
19242
|
-
|
19243
|
-
|
18931
|
+
this.onvplaying = this.onMediaPlaying.bind(this);
|
18932
|
+
this.onvseeked = this.onMediaSeeked.bind(this);
|
18933
|
+
media.addEventListener('playing', this.onvplaying);
|
18934
|
+
media.addEventListener('seeked', this.onvseeked);
|
19244
18935
|
this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
|
19245
18936
|
}
|
19246
18937
|
onMediaDetaching() {
|
19247
18938
|
const {
|
19248
18939
|
media
|
19249
18940
|
} = this;
|
19250
|
-
if (media) {
|
19251
|
-
media.removeEventListener('playing', this.
|
19252
|
-
media.removeEventListener('seeked', this.
|
18941
|
+
if (media && this.onvplaying && this.onvseeked) {
|
18942
|
+
media.removeEventListener('playing', this.onvplaying);
|
18943
|
+
media.removeEventListener('seeked', this.onvseeked);
|
18944
|
+
this.onvplaying = this.onvseeked = null;
|
19253
18945
|
this.videoBuffer = null;
|
19254
18946
|
}
|
19255
18947
|
this.fragPlaying = null;
|
@@ -19259,6 +18951,27 @@ class StreamController extends BaseStreamController {
|
|
19259
18951
|
}
|
19260
18952
|
super.onMediaDetaching();
|
19261
18953
|
}
|
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
|
+
}
|
19262
18975
|
onManifestLoading() {
|
19263
18976
|
// reset buffer on manifest loading
|
19264
18977
|
this.log('Trigger BUFFER_RESET');
|
@@ -19550,10 +19263,8 @@ class StreamController extends BaseStreamController {
|
|
19550
19263
|
}
|
19551
19264
|
if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) {
|
19552
19265
|
// Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
|
19553
|
-
const
|
19554
|
-
|
19555
|
-
const levelDetails = this.getLevelDetails();
|
19556
|
-
gapController.poll(this.lastCurrentTime, activeFrag, levelDetails, state);
|
19266
|
+
const activeFrag = this.state !== State.IDLE ? this.fragCurrent : null;
|
19267
|
+
gapController.poll(this.lastCurrentTime, activeFrag);
|
19557
19268
|
}
|
19558
19269
|
this.lastCurrentTime = media.currentTime;
|
19559
19270
|
}
|
@@ -19991,7 +19702,7 @@ class Hls {
|
|
19991
19702
|
* Get the video-dev/hls.js package version.
|
19992
19703
|
*/
|
19993
19704
|
static get version() {
|
19994
|
-
return "1.5.5
|
19705
|
+
return "1.5.5";
|
19995
19706
|
}
|
19996
19707
|
|
19997
19708
|
/**
|
@@ -20054,12 +19765,9 @@ class Hls {
|
|
20054
19765
|
* The configuration object provided on player instantiation.
|
20055
19766
|
*/
|
20056
19767
|
this.userConfig = void 0;
|
20057
|
-
/**
|
20058
|
-
* The logger functions used by this player instance, configured on player instantiation.
|
20059
|
-
*/
|
20060
|
-
this.logger = void 0;
|
20061
19768
|
this.coreComponents = void 0;
|
20062
19769
|
this.networkControllers = void 0;
|
19770
|
+
this.started = false;
|
20063
19771
|
this._emitter = new EventEmitter();
|
20064
19772
|
this._autoLevelCapping = -1;
|
20065
19773
|
this._maxHdcpLevel = null;
|
@@ -20076,11 +19784,11 @@ class Hls {
|
|
20076
19784
|
this._media = null;
|
20077
19785
|
this.url = null;
|
20078
19786
|
this.triggeringException = void 0;
|
20079
|
-
|
20080
|
-
const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig
|
19787
|
+
enableLogs(userConfig.debug || false, 'Hls instance');
|
19788
|
+
const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig);
|
20081
19789
|
this.userConfig = userConfig;
|
20082
19790
|
if (config.progressive) {
|
20083
|
-
enableStreamingMode(config
|
19791
|
+
enableStreamingMode(config);
|
20084
19792
|
}
|
20085
19793
|
|
20086
19794
|
// core controllers and network loaders
|
@@ -20179,7 +19887,7 @@ class Hls {
|
|
20179
19887
|
try {
|
20180
19888
|
return this.emit(event, event, eventObject);
|
20181
19889
|
} catch (error) {
|
20182
|
-
|
19890
|
+
logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
|
20183
19891
|
// Prevent recursion in error event handlers that throw #5497
|
20184
19892
|
if (!this.triggeringException) {
|
20185
19893
|
this.triggeringException = true;
|
@@ -20205,7 +19913,7 @@ class Hls {
|
|
20205
19913
|
* Dispose of the instance
|
20206
19914
|
*/
|
20207
19915
|
destroy() {
|
20208
|
-
|
19916
|
+
logger.log('destroy');
|
20209
19917
|
this.trigger(Events.DESTROYING, undefined);
|
20210
19918
|
this.detachMedia();
|
20211
19919
|
this.removeAllListeners();
|
@@ -20226,7 +19934,7 @@ class Hls {
|
|
20226
19934
|
* Attaches Hls.js to a media element
|
20227
19935
|
*/
|
20228
19936
|
attachMedia(media) {
|
20229
|
-
|
19937
|
+
logger.log('attachMedia');
|
20230
19938
|
this._media = media;
|
20231
19939
|
this.trigger(Events.MEDIA_ATTACHING, {
|
20232
19940
|
media: media
|
@@ -20237,7 +19945,7 @@ class Hls {
|
|
20237
19945
|
* Detach Hls.js from the media
|
20238
19946
|
*/
|
20239
19947
|
detachMedia() {
|
20240
|
-
|
19948
|
+
logger.log('detachMedia');
|
20241
19949
|
this.trigger(Events.MEDIA_DETACHING, undefined);
|
20242
19950
|
this._media = null;
|
20243
19951
|
}
|
@@ -20254,7 +19962,7 @@ class Hls {
|
|
20254
19962
|
});
|
20255
19963
|
this._autoLevelCapping = -1;
|
20256
19964
|
this._maxHdcpLevel = null;
|
20257
|
-
|
19965
|
+
logger.log(`loadSource:${loadingSource}`);
|
20258
19966
|
if (media && loadedSource && (loadedSource !== loadingSource || this.bufferController.hasSourceTypes())) {
|
20259
19967
|
this.detachMedia();
|
20260
19968
|
this.attachMedia(media);
|
@@ -20273,7 +19981,8 @@ class Hls {
|
|
20273
19981
|
* Defaults to -1 (None: starts from earliest point)
|
20274
19982
|
*/
|
20275
19983
|
startLoad(startPosition = -1) {
|
20276
|
-
|
19984
|
+
logger.log(`startLoad(${startPosition})`);
|
19985
|
+
this.started = true;
|
20277
19986
|
this.networkControllers.forEach(controller => {
|
20278
19987
|
controller.startLoad(startPosition);
|
20279
19988
|
});
|
@@ -20283,31 +19992,34 @@ class Hls {
|
|
20283
19992
|
* Stop loading of any stream data.
|
20284
19993
|
*/
|
20285
19994
|
stopLoad() {
|
20286
|
-
|
19995
|
+
logger.log('stopLoad');
|
19996
|
+
this.started = false;
|
20287
19997
|
this.networkControllers.forEach(controller => {
|
20288
19998
|
controller.stopLoad();
|
20289
19999
|
});
|
20290
20000
|
}
|
20291
20001
|
|
20292
20002
|
/**
|
20293
|
-
* Resumes stream controller segment loading
|
20003
|
+
* Resumes stream controller segment loading if previously started.
|
20294
20004
|
*/
|
20295
20005
|
resumeBuffering() {
|
20296
|
-
this.
|
20297
|
-
|
20298
|
-
controller
|
20299
|
-
|
20300
|
-
|
20006
|
+
if (this.started) {
|
20007
|
+
this.networkControllers.forEach(controller => {
|
20008
|
+
if ('fragmentLoader' in controller) {
|
20009
|
+
controller.startLoad(-1);
|
20010
|
+
}
|
20011
|
+
});
|
20012
|
+
}
|
20301
20013
|
}
|
20302
20014
|
|
20303
20015
|
/**
|
20304
|
-
*
|
20016
|
+
* Stops stream controller segment loading without changing 'started' state like stopLoad().
|
20305
20017
|
* This allows for media buffering to be paused without interupting playlist loading.
|
20306
20018
|
*/
|
20307
20019
|
pauseBuffering() {
|
20308
20020
|
this.networkControllers.forEach(controller => {
|
20309
|
-
if (controller
|
20310
|
-
controller.
|
20021
|
+
if ('fragmentLoader' in controller) {
|
20022
|
+
controller.stopLoad();
|
20311
20023
|
}
|
20312
20024
|
});
|
20313
20025
|
}
|
@@ -20316,7 +20028,7 @@ class Hls {
|
|
20316
20028
|
* Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
|
20317
20029
|
*/
|
20318
20030
|
swapAudioCodec() {
|
20319
|
-
|
20031
|
+
logger.log('swapAudioCodec');
|
20320
20032
|
this.streamController.swapAudioCodec();
|
20321
20033
|
}
|
20322
20034
|
|
@@ -20327,7 +20039,7 @@ class Hls {
|
|
20327
20039
|
* Automatic recovery of media-errors by this process is configurable.
|
20328
20040
|
*/
|
20329
20041
|
recoverMediaError() {
|
20330
|
-
|
20042
|
+
logger.log('recoverMediaError');
|
20331
20043
|
const media = this._media;
|
20332
20044
|
this.detachMedia();
|
20333
20045
|
if (media) {
|
@@ -20357,7 +20069,7 @@ class Hls {
|
|
20357
20069
|
* 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.
|
20358
20070
|
*/
|
20359
20071
|
set currentLevel(newLevel) {
|
20360
|
-
|
20072
|
+
logger.log(`set currentLevel:${newLevel}`);
|
20361
20073
|
this.levelController.manualLevel = newLevel;
|
20362
20074
|
this.streamController.immediateLevelSwitch();
|
20363
20075
|
}
|
@@ -20376,7 +20088,7 @@ class Hls {
|
|
20376
20088
|
* @param newLevel - Pass -1 for automatic level selection
|
20377
20089
|
*/
|
20378
20090
|
set nextLevel(newLevel) {
|
20379
|
-
|
20091
|
+
logger.log(`set nextLevel:${newLevel}`);
|
20380
20092
|
this.levelController.manualLevel = newLevel;
|
20381
20093
|
this.streamController.nextLevelSwitch();
|
20382
20094
|
}
|
@@ -20395,7 +20107,7 @@ class Hls {
|
|
20395
20107
|
* @param newLevel - Pass -1 for automatic level selection
|
20396
20108
|
*/
|
20397
20109
|
set loadLevel(newLevel) {
|
20398
|
-
|
20110
|
+
logger.log(`set loadLevel:${newLevel}`);
|
20399
20111
|
this.levelController.manualLevel = newLevel;
|
20400
20112
|
}
|
20401
20113
|
|
@@ -20426,7 +20138,7 @@ class Hls {
|
|
20426
20138
|
* Sets "first-level", see getter.
|
20427
20139
|
*/
|
20428
20140
|
set firstLevel(newLevel) {
|
20429
|
-
|
20141
|
+
logger.log(`set firstLevel:${newLevel}`);
|
20430
20142
|
this.levelController.firstLevel = newLevel;
|
20431
20143
|
}
|
20432
20144
|
|
@@ -20451,7 +20163,7 @@ class Hls {
|
|
20451
20163
|
* (determined from download of first segment)
|
20452
20164
|
*/
|
20453
20165
|
set startLevel(newLevel) {
|
20454
|
-
|
20166
|
+
logger.log(`set startLevel:${newLevel}`);
|
20455
20167
|
// if not in automatic start level detection, ensure startLevel is greater than minAutoLevel
|
20456
20168
|
if (newLevel !== -1) {
|
20457
20169
|
newLevel = Math.max(newLevel, this.minAutoLevel);
|
@@ -20526,7 +20238,7 @@ class Hls {
|
|
20526
20238
|
*/
|
20527
20239
|
set autoLevelCapping(newLevel) {
|
20528
20240
|
if (this._autoLevelCapping !== newLevel) {
|
20529
|
-
|
20241
|
+
logger.log(`set autoLevelCapping:${newLevel}`);
|
20530
20242
|
this._autoLevelCapping = newLevel;
|
20531
20243
|
this.levelController.checkMaxAutoUpdated();
|
20532
20244
|
}
|
@@ -20805,5 +20517,5 @@ var KeySystemFormats = empty.KeySystemFormats;
|
|
20805
20517
|
var KeySystems = empty.KeySystems;
|
20806
20518
|
var SubtitleStreamController = empty.SubtitleStreamController;
|
20807
20519
|
var TimelineController = empty.TimelineController;
|
20808
|
-
export { AbrController, AttrList,
|
20520
|
+
export { AbrController, AttrList, Cues as AudioStreamController, Cues as AudioTrackController, BasePlaylistController, BaseSegment, BaseStreamController, BufferController, Cues as CMCDController, CapLevelController, ChunkMetadata, ContentSteeringController, DateRange, Cues as EMEController, ErrorActionFlags, ErrorController, ErrorDetails, ErrorTypes, Events, FPSController, Fragment, Hls, HlsSkip, HlsUrlParameters, KeySystemFormats, KeySystems, Level, LevelDetails, LevelKey, LoadStats, MetadataSchema, NetworkErrorAction, Part, PlaylistLevelType, SubtitleStreamController, Cues as SubtitleTrackController, TimelineController, Hls as default, getMediaSource, isMSESupported, isSupported };
|
20809
20521
|
//# sourceMappingURL=hls.light.mjs.map
|