hls.js 1.5.7 → 1.5.8-0.canary.10046
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 +2 -1
- package/dist/hls-demo.js +10 -0
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +2314 -1298
- package/dist/hls.js.d.ts +97 -84
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +1486 -1075
- 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 +1195 -789
- 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 +1979 -982
- 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 +22 -22
- package/src/config.ts +3 -2
- package/src/controller/abr-controller.ts +24 -20
- package/src/controller/audio-stream-controller.ts +68 -74
- package/src/controller/audio-track-controller.ts +1 -1
- package/src/controller/base-playlist-controller.ts +20 -8
- package/src/controller/base-stream-controller.ts +157 -36
- package/src/controller/buffer-controller.ts +203 -67
- package/src/controller/buffer-operation-queue.ts +16 -19
- package/src/controller/cap-level-controller.ts +2 -2
- package/src/controller/cmcd-controller.ts +27 -6
- package/src/controller/content-steering-controller.ts +8 -6
- package/src/controller/eme-controller.ts +9 -22
- package/src/controller/error-controller.ts +6 -8
- package/src/controller/fps-controller.ts +2 -3
- package/src/controller/fragment-tracker.ts +15 -11
- package/src/controller/gap-controller.ts +43 -16
- package/src/controller/latency-controller.ts +9 -11
- package/src/controller/level-controller.ts +12 -18
- package/src/controller/stream-controller.ts +36 -31
- package/src/controller/subtitle-stream-controller.ts +28 -40
- package/src/controller/subtitle-track-controller.ts +5 -3
- package/src/controller/timeline-controller.ts +23 -30
- package/src/crypt/aes-crypto.ts +21 -2
- package/src/crypt/decrypter-aes-mode.ts +4 -0
- package/src/crypt/decrypter.ts +32 -18
- package/src/crypt/fast-aes-key.ts +24 -5
- package/src/demux/audio/adts.ts +9 -4
- package/src/demux/sample-aes.ts +2 -0
- package/src/demux/transmuxer-interface.ts +4 -12
- package/src/demux/transmuxer-worker.ts +4 -4
- package/src/demux/transmuxer.ts +16 -3
- package/src/demux/tsdemuxer.ts +71 -37
- package/src/demux/video/avc-video-parser.ts +208 -119
- package/src/demux/video/base-video-parser.ts +134 -2
- package/src/demux/video/exp-golomb.ts +0 -208
- package/src/demux/video/hevc-video-parser.ts +746 -0
- package/src/events.ts +7 -0
- package/src/hls.ts +49 -37
- package/src/loader/fragment-loader.ts +9 -2
- package/src/loader/key-loader.ts +2 -0
- package/src/loader/level-key.ts +10 -9
- package/src/loader/playlist-loader.ts +4 -5
- package/src/remux/mp4-generator.ts +196 -1
- package/src/remux/mp4-remuxer.ts +23 -7
- package/src/task-loop.ts +5 -2
- package/src/types/component-api.ts +2 -0
- package/src/types/demuxer.ts +3 -0
- package/src/types/events.ts +4 -0
- package/src/utils/buffer-helper.ts +12 -31
- package/src/utils/codecs.ts +34 -5
- package/src/utils/encryption-methods-util.ts +21 -0
- package/src/utils/logger.ts +54 -24
- package/src/utils/mp4-tools.ts +4 -2
package/dist/hls.light.mjs
CHANGED
@@ -256,6 +256,7 @@ let Events = /*#__PURE__*/function (Events) {
|
|
256
256
|
Events["MEDIA_ATTACHED"] = "hlsMediaAttached";
|
257
257
|
Events["MEDIA_DETACHING"] = "hlsMediaDetaching";
|
258
258
|
Events["MEDIA_DETACHED"] = "hlsMediaDetached";
|
259
|
+
Events["MEDIA_ENDED"] = "hlsMediaEnded";
|
259
260
|
Events["BUFFER_RESET"] = "hlsBufferReset";
|
260
261
|
Events["BUFFER_CODECS"] = "hlsBufferCodecs";
|
261
262
|
Events["BUFFER_CREATED"] = "hlsBufferCreated";
|
@@ -369,58 +370,6 @@ let ErrorDetails = /*#__PURE__*/function (ErrorDetails) {
|
|
369
370
|
return ErrorDetails;
|
370
371
|
}({});
|
371
372
|
|
372
|
-
const noop = function noop() {};
|
373
|
-
const fakeLogger = {
|
374
|
-
trace: noop,
|
375
|
-
debug: noop,
|
376
|
-
log: noop,
|
377
|
-
warn: noop,
|
378
|
-
info: noop,
|
379
|
-
error: noop
|
380
|
-
};
|
381
|
-
let exportedLogger = fakeLogger;
|
382
|
-
|
383
|
-
// let lastCallTime;
|
384
|
-
// function formatMsgWithTimeInfo(type, msg) {
|
385
|
-
// const now = Date.now();
|
386
|
-
// const diff = lastCallTime ? '+' + (now - lastCallTime) : '0';
|
387
|
-
// lastCallTime = now;
|
388
|
-
// msg = (new Date(now)).toISOString() + ' | [' + type + '] > ' + msg + ' ( ' + diff + ' ms )';
|
389
|
-
// return msg;
|
390
|
-
// }
|
391
|
-
|
392
|
-
function consolePrintFn(type) {
|
393
|
-
const func = self.console[type];
|
394
|
-
if (func) {
|
395
|
-
return func.bind(self.console, `[${type}] >`);
|
396
|
-
}
|
397
|
-
return noop;
|
398
|
-
}
|
399
|
-
function exportLoggerFunctions(debugConfig, ...functions) {
|
400
|
-
functions.forEach(function (type) {
|
401
|
-
exportedLogger[type] = debugConfig[type] ? debugConfig[type].bind(debugConfig) : consolePrintFn(type);
|
402
|
-
});
|
403
|
-
}
|
404
|
-
function enableLogs(debugConfig, id) {
|
405
|
-
// check that console is available
|
406
|
-
if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
|
407
|
-
exportLoggerFunctions(debugConfig,
|
408
|
-
// Remove out from list here to hard-disable a log-level
|
409
|
-
// 'trace',
|
410
|
-
'debug', 'log', 'info', 'warn', 'error');
|
411
|
-
// Some browsers don't allow to use bind on console object anyway
|
412
|
-
// fallback to default if needed
|
413
|
-
try {
|
414
|
-
exportedLogger.log(`Debug logs enabled for "${id}" in hls.js version ${"1.5.7"}`);
|
415
|
-
} catch (e) {
|
416
|
-
exportedLogger = fakeLogger;
|
417
|
-
}
|
418
|
-
} else {
|
419
|
-
exportedLogger = fakeLogger;
|
420
|
-
}
|
421
|
-
}
|
422
|
-
const logger = exportedLogger;
|
423
|
-
|
424
373
|
const DECIMAL_RESOLUTION_REGEX = /^(\d+)x(\d+)$/;
|
425
374
|
const ATTR_LIST_REGEX = /(.+?)=(".*?"|.*?)(?:,|$)/g;
|
426
375
|
|
@@ -502,6 +451,79 @@ class AttrList {
|
|
502
451
|
}
|
503
452
|
}
|
504
453
|
|
454
|
+
class Logger {
|
455
|
+
constructor(label, logger) {
|
456
|
+
this.trace = void 0;
|
457
|
+
this.debug = void 0;
|
458
|
+
this.log = void 0;
|
459
|
+
this.warn = void 0;
|
460
|
+
this.info = void 0;
|
461
|
+
this.error = void 0;
|
462
|
+
const lb = `[${label}]:`;
|
463
|
+
this.trace = noop;
|
464
|
+
this.debug = logger.debug.bind(null, lb);
|
465
|
+
this.log = logger.log.bind(null, lb);
|
466
|
+
this.warn = logger.warn.bind(null, lb);
|
467
|
+
this.info = logger.info.bind(null, lb);
|
468
|
+
this.error = logger.error.bind(null, lb);
|
469
|
+
}
|
470
|
+
}
|
471
|
+
const noop = function noop() {};
|
472
|
+
const fakeLogger = {
|
473
|
+
trace: noop,
|
474
|
+
debug: noop,
|
475
|
+
log: noop,
|
476
|
+
warn: noop,
|
477
|
+
info: noop,
|
478
|
+
error: noop
|
479
|
+
};
|
480
|
+
function createLogger() {
|
481
|
+
return _extends({}, fakeLogger);
|
482
|
+
}
|
483
|
+
|
484
|
+
// let lastCallTime;
|
485
|
+
// function formatMsgWithTimeInfo(type, msg) {
|
486
|
+
// const now = Date.now();
|
487
|
+
// const diff = lastCallTime ? '+' + (now - lastCallTime) : '0';
|
488
|
+
// lastCallTime = now;
|
489
|
+
// msg = (new Date(now)).toISOString() + ' | [' + type + '] > ' + msg + ' ( ' + diff + ' ms )';
|
490
|
+
// return msg;
|
491
|
+
// }
|
492
|
+
|
493
|
+
function consolePrintFn(type, id) {
|
494
|
+
const func = self.console[type];
|
495
|
+
return func ? func.bind(self.console, `${id ? '[' + id + '] ' : ''}[${type}] >`) : noop;
|
496
|
+
}
|
497
|
+
function getLoggerFn(key, debugConfig, id) {
|
498
|
+
return debugConfig[key] ? debugConfig[key].bind(debugConfig) : consolePrintFn(key, id);
|
499
|
+
}
|
500
|
+
const exportedLogger = createLogger();
|
501
|
+
function enableLogs(debugConfig, context, id) {
|
502
|
+
// check that console is available
|
503
|
+
const newLogger = createLogger();
|
504
|
+
if (typeof console === 'object' && debugConfig === true || typeof debugConfig === 'object') {
|
505
|
+
const keys = [
|
506
|
+
// Remove out from list here to hard-disable a log-level
|
507
|
+
// 'trace',
|
508
|
+
'debug', 'log', 'info', 'warn', 'error'];
|
509
|
+
keys.forEach(key => {
|
510
|
+
newLogger[key] = getLoggerFn(key, debugConfig, id);
|
511
|
+
});
|
512
|
+
// Some browsers don't allow to use bind on console object anyway
|
513
|
+
// fallback to default if needed
|
514
|
+
try {
|
515
|
+
newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.5.8-0.canary.10046"}`);
|
516
|
+
} catch (e) {
|
517
|
+
/* log fn threw an exception. All logger methods are no-ops. */
|
518
|
+
return createLogger();
|
519
|
+
}
|
520
|
+
}
|
521
|
+
// global exported logger uses the log methods from last call to `enableLogs`
|
522
|
+
_extends(exportedLogger, newLogger);
|
523
|
+
return newLogger;
|
524
|
+
}
|
525
|
+
const logger = exportedLogger;
|
526
|
+
|
505
527
|
// Avoid exporting const enum so that these values can be inlined
|
506
528
|
|
507
529
|
function isDateRangeCueAttribute(attrName) {
|
@@ -991,10 +1013,30 @@ class LevelDetails {
|
|
991
1013
|
}
|
992
1014
|
}
|
993
1015
|
|
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
|
+
|
994
1036
|
// This file is inserted as a shim for modules which we do not want to include into the distro.
|
995
1037
|
// This replacement is done in the "alias" plugin of the rollup config.
|
996
1038
|
var empty = undefined;
|
997
|
-
var
|
1039
|
+
var HevcVideoParser = /*@__PURE__*/getDefaultExportFromCjs(empty);
|
998
1040
|
|
999
1041
|
function sliceUint8(array, start, end) {
|
1000
1042
|
// @ts-expect-error This polyfills IE11 usage of Uint8Array slice.
|
@@ -1626,7 +1668,7 @@ function parseStsd(stsd) {
|
|
1626
1668
|
{
|
1627
1669
|
const codecBox = findBox(sampleEntries, [fourCC])[0];
|
1628
1670
|
const esdsBox = findBox(codecBox.subarray(28), ['esds'])[0];
|
1629
|
-
if (esdsBox && esdsBox.length >
|
1671
|
+
if (esdsBox && esdsBox.length > 7) {
|
1630
1672
|
let i = 4;
|
1631
1673
|
// ES Descriptor tag
|
1632
1674
|
if (esdsBox[i++] !== 0x03) {
|
@@ -1741,7 +1783,9 @@ function parseStsd(stsd) {
|
|
1741
1783
|
}
|
1742
1784
|
function skipBERInteger(bytes, i) {
|
1743
1785
|
const limit = i + 5;
|
1744
|
-
while (bytes[i++] & 0x80 && i < limit) {
|
1786
|
+
while (bytes[i++] & 0x80 && i < limit) {
|
1787
|
+
/* do nothing */
|
1788
|
+
}
|
1745
1789
|
return i;
|
1746
1790
|
}
|
1747
1791
|
function toHex(x) {
|
@@ -2433,12 +2477,12 @@ class LevelKey {
|
|
2433
2477
|
this.keyFormatVersions = formatversions;
|
2434
2478
|
this.iv = iv;
|
2435
2479
|
this.encrypted = method ? method !== 'NONE' : false;
|
2436
|
-
this.isCommonEncryption = this.encrypted && method
|
2480
|
+
this.isCommonEncryption = this.encrypted && !isFullSegmentEncryption(method);
|
2437
2481
|
}
|
2438
2482
|
isSupported() {
|
2439
2483
|
// If it's Segment encryption or No encryption, just select that key system
|
2440
2484
|
if (this.method) {
|
2441
|
-
if (this.method
|
2485
|
+
if (isFullSegmentEncryption(this.method) || this.method === 'NONE') {
|
2442
2486
|
return true;
|
2443
2487
|
}
|
2444
2488
|
if (this.keyFormat === 'identity') {
|
@@ -2452,14 +2496,13 @@ class LevelKey {
|
|
2452
2496
|
if (!this.encrypted || !this.uri) {
|
2453
2497
|
return null;
|
2454
2498
|
}
|
2455
|
-
if (this.method
|
2499
|
+
if (isFullSegmentEncryption(this.method) && this.uri && !this.iv) {
|
2456
2500
|
if (typeof sn !== 'number') {
|
2457
2501
|
// We are fetching decryption data for a initialization segment
|
2458
|
-
// If the segment was encrypted with AES-128
|
2502
|
+
// If the segment was encrypted with AES-128/256
|
2459
2503
|
// It must have an IV defined. We cannot substitute the Segment Number in.
|
2460
|
-
|
2461
|
-
|
2462
|
-
}
|
2504
|
+
logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
|
2505
|
+
|
2463
2506
|
// Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
|
2464
2507
|
sn = 0;
|
2465
2508
|
}
|
@@ -2606,23 +2649,28 @@ function getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource =
|
|
2606
2649
|
if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) {
|
2607
2650
|
return CODEC_COMPATIBLE_NAMES[lowerCaseCodec];
|
2608
2651
|
}
|
2609
|
-
|
2610
|
-
// Idealy fLaC and Opus would be first (spec-compliant) but
|
2611
|
-
// some browsers will report that fLaC is supported then fail.
|
2612
|
-
// see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
|
2613
2652
|
const codecsToCheck = {
|
2653
|
+
// Idealy fLaC and Opus would be first (spec-compliant) but
|
2654
|
+
// some browsers will report that fLaC is supported then fail.
|
2655
|
+
// see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
|
2614
2656
|
flac: ['flac', 'fLaC', 'FLAC'],
|
2615
|
-
opus: ['opus', 'Opus']
|
2657
|
+
opus: ['opus', 'Opus'],
|
2658
|
+
// Replace audio codec info if browser does not support mp4a.40.34,
|
2659
|
+
// and demuxer can fallback to 'audio/mpeg' or 'audio/mp4;codecs="mp3"'
|
2660
|
+
'mp4a.40.34': ['mp3']
|
2616
2661
|
}[lowerCaseCodec];
|
2617
2662
|
for (let i = 0; i < codecsToCheck.length; i++) {
|
2663
|
+
var _getMediaSource;
|
2618
2664
|
if (isCodecMediaSourceSupported(codecsToCheck[i], 'audio', preferManagedMediaSource)) {
|
2619
2665
|
CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
|
2620
2666
|
return codecsToCheck[i];
|
2667
|
+
} else if (codecsToCheck[i] === 'mp3' && (_getMediaSource = getMediaSource(preferManagedMediaSource)) != null && _getMediaSource.isTypeSupported('audio/mpeg')) {
|
2668
|
+
return '';
|
2621
2669
|
}
|
2622
2670
|
}
|
2623
2671
|
return lowerCaseCodec;
|
2624
2672
|
}
|
2625
|
-
const AUDIO_CODEC_REGEXP = /flac|opus/i;
|
2673
|
+
const AUDIO_CODEC_REGEXP = /flac|opus|mp4a\.40\.34/i;
|
2626
2674
|
function getCodecCompatibleName(codec, preferManagedMediaSource = true) {
|
2627
2675
|
return codec.replace(AUDIO_CODEC_REGEXP, m => getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource));
|
2628
2676
|
}
|
@@ -2645,6 +2693,16 @@ function convertAVC1ToAVCOTI(codec) {
|
|
2645
2693
|
}
|
2646
2694
|
return codec;
|
2647
2695
|
}
|
2696
|
+
function getM2TSSupportedAudioTypes(preferManagedMediaSource) {
|
2697
|
+
const MediaSource = getMediaSource(preferManagedMediaSource) || {
|
2698
|
+
isTypeSupported: () => false
|
2699
|
+
};
|
2700
|
+
return {
|
2701
|
+
mpeg: MediaSource.isTypeSupported('audio/mpeg'),
|
2702
|
+
mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
|
2703
|
+
ac3: false
|
2704
|
+
};
|
2705
|
+
}
|
2648
2706
|
|
2649
2707
|
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;
|
2650
2708
|
const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g;
|
@@ -3445,10 +3503,10 @@ class PlaylistLoader {
|
|
3445
3503
|
const loaderContext = loader.context;
|
3446
3504
|
if (loaderContext && loaderContext.url === context.url && loaderContext.level === context.level) {
|
3447
3505
|
// same URL can't overlap
|
3448
|
-
logger.trace('[playlist-loader]: playlist request ongoing');
|
3506
|
+
this.hls.logger.trace('[playlist-loader]: playlist request ongoing');
|
3449
3507
|
return;
|
3450
3508
|
}
|
3451
|
-
logger.log(`[playlist-loader]: aborting previous loader for type: ${context.type}`);
|
3509
|
+
this.hls.logger.log(`[playlist-loader]: aborting previous loader for type: ${context.type}`);
|
3452
3510
|
loader.abort();
|
3453
3511
|
}
|
3454
3512
|
|
@@ -3558,7 +3616,7 @@ class PlaylistLoader {
|
|
3558
3616
|
// alt audio rendition in which quality levels (main)
|
3559
3617
|
// contains both audio+video. but with mixed audio track not signaled
|
3560
3618
|
if (!embeddedAudioFound && levels[0].audioCodec && !levels[0].attrs.AUDIO) {
|
3561
|
-
logger.log('[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one');
|
3619
|
+
this.hls.logger.log('[playlist-loader]: audio codec signaled in quality level, but no embedded audio track signaled, create one');
|
3562
3620
|
audioTracks.unshift({
|
3563
3621
|
type: 'main',
|
3564
3622
|
name: 'main',
|
@@ -3657,7 +3715,7 @@ class PlaylistLoader {
|
|
3657
3715
|
message += ` id: ${context.id} group-id: "${context.groupId}"`;
|
3658
3716
|
}
|
3659
3717
|
const error = new Error(message);
|
3660
|
-
logger.warn(`[playlist-loader]: ${message}`);
|
3718
|
+
this.hls.logger.warn(`[playlist-loader]: ${message}`);
|
3661
3719
|
let details = ErrorDetails.UNKNOWN;
|
3662
3720
|
let fatal = false;
|
3663
3721
|
const loader = this.getInternalLoader(context);
|
@@ -4222,7 +4280,47 @@ class LatencyController {
|
|
4222
4280
|
this.currentTime = 0;
|
4223
4281
|
this.stallCount = 0;
|
4224
4282
|
this._latency = null;
|
4225
|
-
this.
|
4283
|
+
this.onTimeupdate = () => {
|
4284
|
+
const {
|
4285
|
+
media,
|
4286
|
+
levelDetails
|
4287
|
+
} = this;
|
4288
|
+
if (!media || !levelDetails) {
|
4289
|
+
return;
|
4290
|
+
}
|
4291
|
+
this.currentTime = media.currentTime;
|
4292
|
+
const latency = this.computeLatency();
|
4293
|
+
if (latency === null) {
|
4294
|
+
return;
|
4295
|
+
}
|
4296
|
+
this._latency = latency;
|
4297
|
+
|
4298
|
+
// Adapt playbackRate to meet target latency in low-latency mode
|
4299
|
+
const {
|
4300
|
+
lowLatencyMode,
|
4301
|
+
maxLiveSyncPlaybackRate
|
4302
|
+
} = this.config;
|
4303
|
+
if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
|
4304
|
+
return;
|
4305
|
+
}
|
4306
|
+
const targetLatency = this.targetLatency;
|
4307
|
+
if (targetLatency === null) {
|
4308
|
+
return;
|
4309
|
+
}
|
4310
|
+
const distanceFromTarget = latency - targetLatency;
|
4311
|
+
// Only adjust playbackRate when within one target duration of targetLatency
|
4312
|
+
// and more than one second from under-buffering.
|
4313
|
+
// Playback further than one target duration from target can be considered DVR playback.
|
4314
|
+
const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
|
4315
|
+
const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
|
4316
|
+
if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
|
4317
|
+
const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
|
4318
|
+
const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
|
4319
|
+
media.playbackRate = Math.min(max, Math.max(1, rate));
|
4320
|
+
} else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
|
4321
|
+
media.playbackRate = 1;
|
4322
|
+
}
|
4323
|
+
};
|
4226
4324
|
this.hls = hls;
|
4227
4325
|
this.config = hls.config;
|
4228
4326
|
this.registerListeners();
|
@@ -4314,7 +4412,7 @@ class LatencyController {
|
|
4314
4412
|
this.onMediaDetaching();
|
4315
4413
|
this.levelDetails = null;
|
4316
4414
|
// @ts-ignore
|
4317
|
-
this.hls =
|
4415
|
+
this.hls = null;
|
4318
4416
|
}
|
4319
4417
|
registerListeners() {
|
4320
4418
|
this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
@@ -4332,11 +4430,11 @@ class LatencyController {
|
|
4332
4430
|
}
|
4333
4431
|
onMediaAttached(event, data) {
|
4334
4432
|
this.media = data.media;
|
4335
|
-
this.media.addEventListener('timeupdate', this.
|
4433
|
+
this.media.addEventListener('timeupdate', this.onTimeupdate);
|
4336
4434
|
}
|
4337
4435
|
onMediaDetaching() {
|
4338
4436
|
if (this.media) {
|
4339
|
-
this.media.removeEventListener('timeupdate', this.
|
4437
|
+
this.media.removeEventListener('timeupdate', this.onTimeupdate);
|
4340
4438
|
this.media = null;
|
4341
4439
|
}
|
4342
4440
|
}
|
@@ -4350,10 +4448,10 @@ class LatencyController {
|
|
4350
4448
|
}) {
|
4351
4449
|
this.levelDetails = details;
|
4352
4450
|
if (details.advanced) {
|
4353
|
-
this.
|
4451
|
+
this.onTimeupdate();
|
4354
4452
|
}
|
4355
4453
|
if (!details.live && this.media) {
|
4356
|
-
this.media.removeEventListener('timeupdate', this.
|
4454
|
+
this.media.removeEventListener('timeupdate', this.onTimeupdate);
|
4357
4455
|
}
|
4358
4456
|
}
|
4359
4457
|
onError(event, data) {
|
@@ -4363,48 +4461,7 @@ class LatencyController {
|
|
4363
4461
|
}
|
4364
4462
|
this.stallCount++;
|
4365
4463
|
if ((_this$levelDetails = this.levelDetails) != null && _this$levelDetails.live) {
|
4366
|
-
logger.warn('[
|
4367
|
-
}
|
4368
|
-
}
|
4369
|
-
timeupdate() {
|
4370
|
-
const {
|
4371
|
-
media,
|
4372
|
-
levelDetails
|
4373
|
-
} = this;
|
4374
|
-
if (!media || !levelDetails) {
|
4375
|
-
return;
|
4376
|
-
}
|
4377
|
-
this.currentTime = media.currentTime;
|
4378
|
-
const latency = this.computeLatency();
|
4379
|
-
if (latency === null) {
|
4380
|
-
return;
|
4381
|
-
}
|
4382
|
-
this._latency = latency;
|
4383
|
-
|
4384
|
-
// Adapt playbackRate to meet target latency in low-latency mode
|
4385
|
-
const {
|
4386
|
-
lowLatencyMode,
|
4387
|
-
maxLiveSyncPlaybackRate
|
4388
|
-
} = this.config;
|
4389
|
-
if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
|
4390
|
-
return;
|
4391
|
-
}
|
4392
|
-
const targetLatency = this.targetLatency;
|
4393
|
-
if (targetLatency === null) {
|
4394
|
-
return;
|
4395
|
-
}
|
4396
|
-
const distanceFromTarget = latency - targetLatency;
|
4397
|
-
// Only adjust playbackRate when within one target duration of targetLatency
|
4398
|
-
// and more than one second from under-buffering.
|
4399
|
-
// Playback further than one target duration from target can be considered DVR playback.
|
4400
|
-
const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
|
4401
|
-
const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
|
4402
|
-
if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
|
4403
|
-
const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
|
4404
|
-
const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
|
4405
|
-
media.playbackRate = Math.min(max, Math.max(1, rate));
|
4406
|
-
} else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
|
4407
|
-
media.playbackRate = 1;
|
4464
|
+
this.hls.logger.warn('[latency-controller]: Stall detected, adjusting target latency');
|
4408
4465
|
}
|
4409
4466
|
}
|
4410
4467
|
estimateLiveEdge() {
|
@@ -5176,18 +5233,13 @@ var ErrorActionFlags = {
|
|
5176
5233
|
MoveAllAlternatesMatchingHDCP: 2,
|
5177
5234
|
SwitchToSDR: 4
|
5178
5235
|
}; // Reserved for future use
|
5179
|
-
class ErrorController {
|
5236
|
+
class ErrorController extends Logger {
|
5180
5237
|
constructor(hls) {
|
5238
|
+
super('error-controller', hls.logger);
|
5181
5239
|
this.hls = void 0;
|
5182
5240
|
this.playlistError = 0;
|
5183
5241
|
this.penalizedRenditions = {};
|
5184
|
-
this.log = void 0;
|
5185
|
-
this.warn = void 0;
|
5186
|
-
this.error = void 0;
|
5187
5242
|
this.hls = hls;
|
5188
|
-
this.log = logger.log.bind(logger, `[info]:`);
|
5189
|
-
this.warn = logger.warn.bind(logger, `[warning]:`);
|
5190
|
-
this.error = logger.error.bind(logger, `[error]:`);
|
5191
5243
|
this.registerListeners();
|
5192
5244
|
}
|
5193
5245
|
registerListeners() {
|
@@ -5539,16 +5591,13 @@ class ErrorController {
|
|
5539
5591
|
}
|
5540
5592
|
}
|
5541
5593
|
|
5542
|
-
class BasePlaylistController {
|
5594
|
+
class BasePlaylistController extends Logger {
|
5543
5595
|
constructor(hls, logPrefix) {
|
5596
|
+
super(logPrefix, hls.logger);
|
5544
5597
|
this.hls = void 0;
|
5545
5598
|
this.timer = -1;
|
5546
5599
|
this.requestScheduled = -1;
|
5547
5600
|
this.canLoad = false;
|
5548
|
-
this.log = void 0;
|
5549
|
-
this.warn = void 0;
|
5550
|
-
this.log = logger.log.bind(logger, `${logPrefix}:`);
|
5551
|
-
this.warn = logger.warn.bind(logger, `${logPrefix}:`);
|
5552
5601
|
this.hls = hls;
|
5553
5602
|
}
|
5554
5603
|
destroy() {
|
@@ -5581,7 +5630,7 @@ class BasePlaylistController {
|
|
5581
5630
|
try {
|
5582
5631
|
uri = new self.URL(attr.URI, previous.url).href;
|
5583
5632
|
} catch (error) {
|
5584
|
-
|
5633
|
+
this.warn(`Could not construct new URL for Rendition Report: ${error}`);
|
5585
5634
|
uri = attr.URI || '';
|
5586
5635
|
}
|
5587
5636
|
// Use exact match. Otherwise, the last partial match, if any, will be used
|
@@ -5668,7 +5717,12 @@ class BasePlaylistController {
|
|
5668
5717
|
const cdnAge = lastAdvanced + details.ageHeader;
|
5669
5718
|
let currentGoal = Math.min(cdnAge - details.partTarget, details.targetduration * 1.5);
|
5670
5719
|
if (currentGoal > 0) {
|
5671
|
-
if (
|
5720
|
+
if (cdnAge > details.targetduration * 3) {
|
5721
|
+
// Omit segment and part directives when the last response was more than 3 target durations ago,
|
5722
|
+
this.log(`Playlist last advanced ${lastAdvanced.toFixed(2)}s ago. Omitting segment and part directives.`);
|
5723
|
+
msn = undefined;
|
5724
|
+
part = undefined;
|
5725
|
+
} else if (previousDetails != null && previousDetails.tuneInGoal && cdnAge - details.partTarget > previousDetails.tuneInGoal) {
|
5672
5726
|
// If we attempted to get the next or latest playlist update, but currentGoal increased,
|
5673
5727
|
// then we either can't catchup, or the "age" header cannot be trusted.
|
5674
5728
|
this.warn(`CDN Tune-in goal increased from: ${previousDetails.tuneInGoal} to: ${currentGoal} with playlist age: ${details.age}`);
|
@@ -6127,8 +6181,9 @@ function getCodecTiers(levels, audioTracksByGroup, minAutoLevel, maxAutoLevel) {
|
|
6127
6181
|
}, {});
|
6128
6182
|
}
|
6129
6183
|
|
6130
|
-
class AbrController {
|
6184
|
+
class AbrController extends Logger {
|
6131
6185
|
constructor(_hls) {
|
6186
|
+
super('abr', _hls.logger);
|
6132
6187
|
this.hls = void 0;
|
6133
6188
|
this.lastLevelLoadSec = 0;
|
6134
6189
|
this.lastLoadedFragLevel = -1;
|
@@ -6242,7 +6297,7 @@ class AbrController {
|
|
6242
6297
|
this.resetEstimator(nextLoadLevelBitrate);
|
6243
6298
|
}
|
6244
6299
|
this.clearTimer();
|
6245
|
-
|
6300
|
+
this.warn(`Fragment ${frag.sn}${part ? ' part ' + part.index : ''} of level ${frag.level} is loading too slowly;
|
6246
6301
|
Time to underbuffer: ${bufferStarvationDelay.toFixed(3)} s
|
6247
6302
|
Estimated load time for current fragment: ${fragLoadedDelay.toFixed(3)} s
|
6248
6303
|
Estimated load time for down switch fragment: ${fragLevelNextLoadedDelay.toFixed(3)} s
|
@@ -6262,7 +6317,7 @@ class AbrController {
|
|
6262
6317
|
}
|
6263
6318
|
resetEstimator(abrEwmaDefaultEstimate) {
|
6264
6319
|
if (abrEwmaDefaultEstimate) {
|
6265
|
-
|
6320
|
+
this.log(`setting initial bwe to ${abrEwmaDefaultEstimate}`);
|
6266
6321
|
this.hls.config.abrEwmaDefaultEstimate = abrEwmaDefaultEstimate;
|
6267
6322
|
}
|
6268
6323
|
this.firstSelection = -1;
|
@@ -6494,7 +6549,7 @@ class AbrController {
|
|
6494
6549
|
}
|
6495
6550
|
const firstLevel = this.hls.firstLevel;
|
6496
6551
|
const clamped = Math.min(Math.max(firstLevel, minAutoLevel), maxAutoLevel);
|
6497
|
-
|
6552
|
+
this.warn(`Could not find best starting auto level. Defaulting to first in playlist ${firstLevel} clamped to ${clamped}`);
|
6498
6553
|
return clamped;
|
6499
6554
|
}
|
6500
6555
|
get forcedAutoLevel() {
|
@@ -6540,6 +6595,9 @@ class AbrController {
|
|
6540
6595
|
partCurrent,
|
6541
6596
|
hls
|
6542
6597
|
} = this;
|
6598
|
+
if (hls.levels.length <= 1) {
|
6599
|
+
return hls.loadLevel;
|
6600
|
+
}
|
6543
6601
|
const {
|
6544
6602
|
maxAutoLevel,
|
6545
6603
|
config,
|
@@ -6572,13 +6630,13 @@ class AbrController {
|
|
6572
6630
|
// cap maxLoadingDelay and ensure it is not bigger 'than bitrate test' frag duration
|
6573
6631
|
const maxLoadingDelay = currentFragDuration ? Math.min(currentFragDuration, config.maxLoadingDelay) : config.maxLoadingDelay;
|
6574
6632
|
maxStarvationDelay = maxLoadingDelay - bitrateTestDelay;
|
6575
|
-
|
6633
|
+
this.info(`bitrate test took ${Math.round(1000 * bitrateTestDelay)}ms, set first fragment max fetchDuration to ${Math.round(1000 * maxStarvationDelay)} ms`);
|
6576
6634
|
// don't use conservative factor on bitrate test
|
6577
6635
|
bwFactor = bwUpFactor = 1;
|
6578
6636
|
}
|
6579
6637
|
}
|
6580
6638
|
const bestLevel = this.findBestLevel(avgbw, minAutoLevel, maxAutoLevel, bufferStarvationDelay, maxStarvationDelay, bwFactor, bwUpFactor);
|
6581
|
-
|
6639
|
+
this.info(`${bufferStarvationDelay ? 'rebuffering expected' : 'buffer is empty'}, optimal quality level ${bestLevel}`);
|
6582
6640
|
if (bestLevel > -1) {
|
6583
6641
|
return bestLevel;
|
6584
6642
|
}
|
@@ -6652,7 +6710,7 @@ class AbrController {
|
|
6652
6710
|
currentVideoRange = preferHDR ? videoRanges[videoRanges.length - 1] : videoRanges[0];
|
6653
6711
|
currentFrameRate = minFramerate;
|
6654
6712
|
currentBw = Math.max(currentBw, minBitrate);
|
6655
|
-
|
6713
|
+
this.log(`picked start tier ${JSON.stringify(startTier)}`);
|
6656
6714
|
} else {
|
6657
6715
|
currentCodecSet = level == null ? void 0 : level.codecSet;
|
6658
6716
|
currentVideoRange = level == null ? void 0 : level.videoRange;
|
@@ -6705,9 +6763,9 @@ class AbrController {
|
|
6705
6763
|
const forcedAutoLevel = this.forcedAutoLevel;
|
6706
6764
|
if (i !== loadLevel && (forcedAutoLevel === -1 || forcedAutoLevel !== loadLevel)) {
|
6707
6765
|
if (levelsSkipped.length) {
|
6708
|
-
|
6766
|
+
this.trace(`Skipped level(s) ${levelsSkipped.join(',')} of ${maxAutoLevel} max with CODECS and VIDEO-RANGE:"${levels[levelsSkipped[0]].codecs}" ${levels[levelsSkipped[0]].videoRange}; not compatible with "${level.codecs}" ${currentVideoRange}`);
|
6709
6767
|
}
|
6710
|
-
|
6768
|
+
this.info(`switch candidate:${selectionBaseLevel}->${i} adjustedbw(${Math.round(adjustedbw)})-bitrate=${Math.round(adjustedbw - bitrate)} ttfb:${ttfbEstimateSec.toFixed(1)} avgDuration:${avgDuration.toFixed(1)} maxFetchDuration:${maxFetchDuration.toFixed(1)} fetchDuration:${fetchDuration.toFixed(1)} firstSelection:${firstSelection} codecSet:${currentCodecSet} videoRange:${currentVideoRange} hls.loadLevel:${loadLevel}`);
|
6711
6769
|
}
|
6712
6770
|
if (firstSelection) {
|
6713
6771
|
this.firstSelection = i;
|
@@ -6750,40 +6808,29 @@ class BufferHelper {
|
|
6750
6808
|
* Return true if `media`'s buffered include `position`
|
6751
6809
|
*/
|
6752
6810
|
static isBuffered(media, position) {
|
6753
|
-
|
6754
|
-
|
6755
|
-
|
6756
|
-
|
6757
|
-
|
6758
|
-
return true;
|
6759
|
-
}
|
6811
|
+
if (media) {
|
6812
|
+
const buffered = BufferHelper.getBuffered(media);
|
6813
|
+
for (let i = buffered.length; i--;) {
|
6814
|
+
if (position >= buffered.start(i) && position <= buffered.end(i)) {
|
6815
|
+
return true;
|
6760
6816
|
}
|
6761
6817
|
}
|
6762
|
-
} catch (error) {
|
6763
|
-
// this is to catch
|
6764
|
-
// InvalidStateError: Failed to read the 'buffered' property from 'SourceBuffer':
|
6765
|
-
// This SourceBuffer has been removed from the parent media source
|
6766
6818
|
}
|
6767
6819
|
return false;
|
6768
6820
|
}
|
6769
6821
|
static bufferInfo(media, pos, maxHoleDuration) {
|
6770
|
-
|
6771
|
-
|
6772
|
-
|
6822
|
+
if (media) {
|
6823
|
+
const vbuffered = BufferHelper.getBuffered(media);
|
6824
|
+
if (vbuffered.length) {
|
6773
6825
|
const buffered = [];
|
6774
|
-
let i;
|
6775
|
-
for (i = 0; i < vbuffered.length; i++) {
|
6826
|
+
for (let i = 0; i < vbuffered.length; i++) {
|
6776
6827
|
buffered.push({
|
6777
6828
|
start: vbuffered.start(i),
|
6778
6829
|
end: vbuffered.end(i)
|
6779
6830
|
});
|
6780
6831
|
}
|
6781
|
-
return
|
6832
|
+
return BufferHelper.bufferedInfo(buffered, pos, maxHoleDuration);
|
6782
6833
|
}
|
6783
|
-
} catch (error) {
|
6784
|
-
// this is to catch
|
6785
|
-
// InvalidStateError: Failed to read the 'buffered' property from 'SourceBuffer':
|
6786
|
-
// This SourceBuffer has been removed from the parent media source
|
6787
6834
|
}
|
6788
6835
|
return {
|
6789
6836
|
len: 0,
|
@@ -6795,14 +6842,7 @@ class BufferHelper {
|
|
6795
6842
|
static bufferedInfo(buffered, pos, maxHoleDuration) {
|
6796
6843
|
pos = Math.max(0, pos);
|
6797
6844
|
// sort on buffer.start/smaller end (IE does not always return sorted buffered range)
|
6798
|
-
buffered.sort(
|
6799
|
-
const diff = a.start - b.start;
|
6800
|
-
if (diff) {
|
6801
|
-
return diff;
|
6802
|
-
} else {
|
6803
|
-
return b.end - a.end;
|
6804
|
-
}
|
6805
|
-
});
|
6845
|
+
buffered.sort((a, b) => a.start - b.start || b.end - a.end);
|
6806
6846
|
let buffered2 = [];
|
6807
6847
|
if (maxHoleDuration) {
|
6808
6848
|
// there might be some small holes between buffer time range
|
@@ -6869,7 +6909,7 @@ class BufferHelper {
|
|
6869
6909
|
*/
|
6870
6910
|
static getBuffered(media) {
|
6871
6911
|
try {
|
6872
|
-
return media.buffered;
|
6912
|
+
return media.buffered || noopBuffered;
|
6873
6913
|
} catch (e) {
|
6874
6914
|
logger.log('failed to get media.buffered', e);
|
6875
6915
|
return noopBuffered;
|
@@ -6894,24 +6934,22 @@ class BufferOperationQueue {
|
|
6894
6934
|
this.executeNext(type);
|
6895
6935
|
}
|
6896
6936
|
}
|
6897
|
-
insertAbort(operation, type) {
|
6898
|
-
const queue = this.queues[type];
|
6899
|
-
queue.unshift(operation);
|
6900
|
-
this.executeNext(type);
|
6901
|
-
}
|
6902
6937
|
appendBlocker(type) {
|
6903
|
-
|
6904
|
-
|
6905
|
-
|
6938
|
+
return new Promise(resolve => {
|
6939
|
+
const operation = {
|
6940
|
+
execute: resolve,
|
6941
|
+
onStart: () => {},
|
6942
|
+
onComplete: () => {},
|
6943
|
+
onError: () => {}
|
6944
|
+
};
|
6945
|
+
this.append(operation, type);
|
6906
6946
|
});
|
6907
|
-
|
6908
|
-
|
6909
|
-
|
6910
|
-
|
6911
|
-
|
6912
|
-
}
|
6913
|
-
this.append(operation, type);
|
6914
|
-
return promise;
|
6947
|
+
}
|
6948
|
+
unblockAudio(op) {
|
6949
|
+
const queue = this.queues.audio;
|
6950
|
+
if (queue[0] === op) {
|
6951
|
+
this.shiftAndExecuteNext('audio');
|
6952
|
+
}
|
6915
6953
|
}
|
6916
6954
|
executeNext(type) {
|
6917
6955
|
const queue = this.queues[type];
|
@@ -6943,8 +6981,9 @@ class BufferOperationQueue {
|
|
6943
6981
|
}
|
6944
6982
|
|
6945
6983
|
const VIDEO_CODEC_PROFILE_REPLACE = /(avc[1234]|hvc1|hev1|dvh[1e]|vp09|av01)(?:\.[^.,]+)+/;
|
6946
|
-
class BufferController {
|
6947
|
-
constructor(hls) {
|
6984
|
+
class BufferController extends Logger {
|
6985
|
+
constructor(hls, fragmentTracker) {
|
6986
|
+
super('buffer-controller', hls.logger);
|
6948
6987
|
// The level details used to determine duration, target-duration and live
|
6949
6988
|
this.details = null;
|
6950
6989
|
// cache the self generated object url to detect hijack of video tag
|
@@ -6954,6 +6993,7 @@ class BufferController {
|
|
6954
6993
|
// References to event listeners for each SourceBuffer, so that they can be referenced for event removal
|
6955
6994
|
this.listeners = void 0;
|
6956
6995
|
this.hls = void 0;
|
6996
|
+
this.fragmentTracker = void 0;
|
6957
6997
|
// The number of BUFFER_CODEC events received before any sourceBuffers are created
|
6958
6998
|
this.bufferCodecEventsExpected = 0;
|
6959
6999
|
// The total number of BUFFER_CODEC events received
|
@@ -6964,6 +7004,10 @@ class BufferController {
|
|
6964
7004
|
this.mediaSource = null;
|
6965
7005
|
// Last MP3 audio chunk appended
|
6966
7006
|
this.lastMpegAudioChunk = null;
|
7007
|
+
// Audio fragment blocked from appending until corresponding video appends or context changes
|
7008
|
+
this.blockedAudioAppend = null;
|
7009
|
+
// Keep track of video append position for unblocking audio
|
7010
|
+
this.lastVideoAppendEnd = 0;
|
6967
7011
|
this.appendSource = void 0;
|
6968
7012
|
// counters
|
6969
7013
|
this.appendErrors = {
|
@@ -6974,9 +7018,6 @@ class BufferController {
|
|
6974
7018
|
this.tracks = {};
|
6975
7019
|
this.pendingTracks = {};
|
6976
7020
|
this.sourceBuffer = void 0;
|
6977
|
-
this.log = void 0;
|
6978
|
-
this.warn = void 0;
|
6979
|
-
this.error = void 0;
|
6980
7021
|
this._onEndStreaming = event => {
|
6981
7022
|
if (!this.hls) {
|
6982
7023
|
return;
|
@@ -6998,7 +7039,10 @@ class BufferController {
|
|
6998
7039
|
this.log('Media source opened');
|
6999
7040
|
if (media) {
|
7000
7041
|
media.removeEventListener('emptied', this._onMediaEmptied);
|
7001
|
-
this.
|
7042
|
+
const durationAndRange = this.getDurationAndRange();
|
7043
|
+
if (durationAndRange) {
|
7044
|
+
this.updateMediaSource(durationAndRange);
|
7045
|
+
}
|
7002
7046
|
this.hls.trigger(Events.MEDIA_ATTACHED, {
|
7003
7047
|
media,
|
7004
7048
|
mediaSource: mediaSource
|
@@ -7022,15 +7066,12 @@ class BufferController {
|
|
7022
7066
|
_objectUrl
|
7023
7067
|
} = this;
|
7024
7068
|
if (mediaSrc !== _objectUrl) {
|
7025
|
-
|
7069
|
+
this.error(`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`);
|
7026
7070
|
}
|
7027
7071
|
};
|
7028
7072
|
this.hls = hls;
|
7029
|
-
|
7073
|
+
this.fragmentTracker = fragmentTracker;
|
7030
7074
|
this.appendSource = hls.config.preferManagedMediaSource && typeof self !== 'undefined' && self.ManagedMediaSource;
|
7031
|
-
this.log = logger.log.bind(logger, logPrefix);
|
7032
|
-
this.warn = logger.warn.bind(logger, logPrefix);
|
7033
|
-
this.error = logger.error.bind(logger, logPrefix);
|
7034
7075
|
this._initSourceBuffer();
|
7035
7076
|
this.registerListeners();
|
7036
7077
|
}
|
@@ -7042,7 +7083,13 @@ class BufferController {
|
|
7042
7083
|
this.details = null;
|
7043
7084
|
this.lastMpegAudioChunk = null;
|
7044
7085
|
// @ts-ignore
|
7045
|
-
this.hls = null;
|
7086
|
+
this.hls = this.fragmentTracker = null;
|
7087
|
+
// @ts-ignore
|
7088
|
+
this._onMediaSourceOpen = this._onMediaSourceClose = null;
|
7089
|
+
// @ts-ignore
|
7090
|
+
this._onMediaSourceEnded = null;
|
7091
|
+
// @ts-ignore
|
7092
|
+
this._onStartStreaming = this._onEndStreaming = null;
|
7046
7093
|
}
|
7047
7094
|
registerListeners() {
|
7048
7095
|
const {
|
@@ -7092,6 +7139,8 @@ class BufferController {
|
|
7092
7139
|
audiovideo: 0
|
7093
7140
|
};
|
7094
7141
|
this.lastMpegAudioChunk = null;
|
7142
|
+
this.blockedAudioAppend = null;
|
7143
|
+
this.lastVideoAppendEnd = 0;
|
7095
7144
|
}
|
7096
7145
|
onManifestLoading() {
|
7097
7146
|
this.bufferCodecEventsExpected = this._bufferCodecEventsTotal = 0;
|
@@ -7209,6 +7258,7 @@ class BufferController {
|
|
7209
7258
|
this.resetBuffer(type);
|
7210
7259
|
});
|
7211
7260
|
this._initSourceBuffer();
|
7261
|
+
this.hls.resumeBuffering();
|
7212
7262
|
}
|
7213
7263
|
resetBuffer(type) {
|
7214
7264
|
const sb = this.sourceBuffer[type];
|
@@ -7232,9 +7282,10 @@ class BufferController {
|
|
7232
7282
|
const trackNames = Object.keys(data);
|
7233
7283
|
trackNames.forEach(trackName => {
|
7234
7284
|
if (sourceBufferCount) {
|
7285
|
+
var _track$buffer;
|
7235
7286
|
// check if SourceBuffer codec needs to change
|
7236
7287
|
const track = this.tracks[trackName];
|
7237
|
-
if (track && typeof track.buffer.changeType === 'function') {
|
7288
|
+
if (track && typeof ((_track$buffer = track.buffer) == null ? void 0 : _track$buffer.changeType) === 'function') {
|
7238
7289
|
var _trackCodec;
|
7239
7290
|
const {
|
7240
7291
|
id,
|
@@ -7304,20 +7355,54 @@ class BufferController {
|
|
7304
7355
|
};
|
7305
7356
|
operationQueue.append(operation, type, !!this.pendingTracks[type]);
|
7306
7357
|
}
|
7358
|
+
blockAudio(partOrFrag) {
|
7359
|
+
var _this$fragmentTracker;
|
7360
|
+
const pStart = partOrFrag.start;
|
7361
|
+
const pTime = pStart + partOrFrag.duration * 0.05;
|
7362
|
+
const atGap = ((_this$fragmentTracker = this.fragmentTracker.getAppendedFrag(pStart, PlaylistLevelType.MAIN)) == null ? void 0 : _this$fragmentTracker.gap) === true;
|
7363
|
+
if (atGap) {
|
7364
|
+
return;
|
7365
|
+
}
|
7366
|
+
const op = {
|
7367
|
+
execute: () => {
|
7368
|
+
var _this$fragmentTracker2;
|
7369
|
+
if (this.lastVideoAppendEnd > pTime || this.sourceBuffer.video && BufferHelper.isBuffered(this.sourceBuffer.video, pTime) || ((_this$fragmentTracker2 = this.fragmentTracker.getAppendedFrag(pTime, PlaylistLevelType.MAIN)) == null ? void 0 : _this$fragmentTracker2.gap) === true) {
|
7370
|
+
this.blockedAudioAppend = null;
|
7371
|
+
this.operationQueue.shiftAndExecuteNext('audio');
|
7372
|
+
}
|
7373
|
+
},
|
7374
|
+
onStart: () => {},
|
7375
|
+
onComplete: () => {},
|
7376
|
+
onError: () => {}
|
7377
|
+
};
|
7378
|
+
this.blockedAudioAppend = {
|
7379
|
+
op,
|
7380
|
+
frag: partOrFrag
|
7381
|
+
};
|
7382
|
+
this.operationQueue.append(op, 'audio', true);
|
7383
|
+
}
|
7384
|
+
unblockAudio() {
|
7385
|
+
const blockedAudioAppend = this.blockedAudioAppend;
|
7386
|
+
if (blockedAudioAppend) {
|
7387
|
+
this.blockedAudioAppend = null;
|
7388
|
+
this.operationQueue.unblockAudio(blockedAudioAppend.op);
|
7389
|
+
}
|
7390
|
+
}
|
7307
7391
|
onBufferAppending(event, eventData) {
|
7308
7392
|
const {
|
7309
|
-
hls,
|
7310
7393
|
operationQueue,
|
7311
7394
|
tracks
|
7312
7395
|
} = this;
|
7313
7396
|
const {
|
7314
7397
|
data,
|
7315
7398
|
type,
|
7399
|
+
parent,
|
7316
7400
|
frag,
|
7317
7401
|
part,
|
7318
7402
|
chunkMeta
|
7319
7403
|
} = eventData;
|
7320
7404
|
const chunkStats = chunkMeta.buffering[type];
|
7405
|
+
const sn = frag.sn;
|
7321
7406
|
const bufferAppendingStart = self.performance.now();
|
7322
7407
|
chunkStats.start = bufferAppendingStart;
|
7323
7408
|
const fragBuffering = frag.stats.buffering;
|
@@ -7340,7 +7425,36 @@ class BufferController {
|
|
7340
7425
|
checkTimestampOffset = !this.lastMpegAudioChunk || chunkMeta.id === 1 || this.lastMpegAudioChunk.sn !== chunkMeta.sn;
|
7341
7426
|
this.lastMpegAudioChunk = chunkMeta;
|
7342
7427
|
}
|
7343
|
-
|
7428
|
+
|
7429
|
+
// Block audio append until overlapping video append
|
7430
|
+
const videoSb = this.sourceBuffer.video;
|
7431
|
+
if (videoSb && sn !== 'initSegment') {
|
7432
|
+
const partOrFrag = part || frag;
|
7433
|
+
const blockedAudioAppend = this.blockedAudioAppend;
|
7434
|
+
if (type === 'audio' && parent !== 'main' && !this.blockedAudioAppend) {
|
7435
|
+
const pStart = partOrFrag.start;
|
7436
|
+
const pTime = pStart + partOrFrag.duration * 0.05;
|
7437
|
+
const vbuffered = videoSb.buffered;
|
7438
|
+
const vappending = this.operationQueue.current('video');
|
7439
|
+
if (!vbuffered.length && !vappending) {
|
7440
|
+
// wait for video before appending audio
|
7441
|
+
this.blockAudio(partOrFrag);
|
7442
|
+
} else if (!vappending && !BufferHelper.isBuffered(videoSb, pTime) && this.lastVideoAppendEnd < pTime) {
|
7443
|
+
// audio is ahead of video
|
7444
|
+
this.blockAudio(partOrFrag);
|
7445
|
+
}
|
7446
|
+
} else if (type === 'video') {
|
7447
|
+
const videoAppendEnd = partOrFrag.end;
|
7448
|
+
if (blockedAudioAppend) {
|
7449
|
+
const audioStart = blockedAudioAppend.frag.start;
|
7450
|
+
if (videoAppendEnd > audioStart || videoAppendEnd < this.lastVideoAppendEnd || BufferHelper.isBuffered(videoSb, audioStart)) {
|
7451
|
+
this.unblockAudio();
|
7452
|
+
}
|
7453
|
+
}
|
7454
|
+
this.lastVideoAppendEnd = videoAppendEnd;
|
7455
|
+
}
|
7456
|
+
}
|
7457
|
+
const fragStart = (part || frag).start;
|
7344
7458
|
const operation = {
|
7345
7459
|
execute: () => {
|
7346
7460
|
chunkStats.executeStart = self.performance.now();
|
@@ -7349,7 +7463,7 @@ class BufferController {
|
|
7349
7463
|
if (sb) {
|
7350
7464
|
const delta = fragStart - sb.timestampOffset;
|
7351
7465
|
if (Math.abs(delta) >= 0.1) {
|
7352
|
-
this.log(`Updating audio SourceBuffer timestampOffset to ${fragStart} (delta: ${delta}) sn: ${
|
7466
|
+
this.log(`Updating audio SourceBuffer timestampOffset to ${fragStart} (delta: ${delta}) sn: ${sn})`);
|
7353
7467
|
sb.timestampOffset = fragStart;
|
7354
7468
|
}
|
7355
7469
|
}
|
@@ -7416,22 +7530,21 @@ class BufferController {
|
|
7416
7530
|
/* with UHD content, we could get loop of quota exceeded error until
|
7417
7531
|
browser is able to evict some data from sourcebuffer. Retrying can help recover.
|
7418
7532
|
*/
|
7419
|
-
this.warn(`Failed ${appendErrorCount}/${hls.config.appendErrorMaxRetry} times to append segment in "${type}" sourceBuffer`);
|
7420
|
-
if (appendErrorCount >= hls.config.appendErrorMaxRetry) {
|
7533
|
+
this.warn(`Failed ${appendErrorCount}/${this.hls.config.appendErrorMaxRetry} times to append segment in "${type}" sourceBuffer`);
|
7534
|
+
if (appendErrorCount >= this.hls.config.appendErrorMaxRetry) {
|
7421
7535
|
event.fatal = true;
|
7422
7536
|
}
|
7423
7537
|
}
|
7424
|
-
hls.trigger(Events.ERROR, event);
|
7538
|
+
this.hls.trigger(Events.ERROR, event);
|
7425
7539
|
}
|
7426
7540
|
};
|
7427
7541
|
operationQueue.append(operation, type, !!this.pendingTracks[type]);
|
7428
7542
|
}
|
7429
|
-
|
7430
|
-
|
7431
|
-
|
7432
|
-
|
7433
|
-
|
7434
|
-
execute: this.removeExecutor.bind(this, type, data.startOffset, data.endOffset),
|
7543
|
+
getFlushOp(type, start, end) {
|
7544
|
+
return {
|
7545
|
+
execute: () => {
|
7546
|
+
this.removeExecutor(type, start, end);
|
7547
|
+
},
|
7435
7548
|
onStart: () => {
|
7436
7549
|
// logger.debug(`[buffer-controller]: Started flushing ${data.startOffset} -> ${data.endOffset} for ${type} Source Buffer`);
|
7437
7550
|
},
|
@@ -7444,12 +7557,22 @@ class BufferController {
|
|
7444
7557
|
onError: error => {
|
7445
7558
|
this.warn(`Failed to remove from ${type} SourceBuffer`, error);
|
7446
7559
|
}
|
7447
|
-
}
|
7448
|
-
|
7449
|
-
|
7560
|
+
};
|
7561
|
+
}
|
7562
|
+
onBufferFlushing(event, data) {
|
7563
|
+
const {
|
7564
|
+
operationQueue
|
7565
|
+
} = this;
|
7566
|
+
const {
|
7567
|
+
type,
|
7568
|
+
startOffset,
|
7569
|
+
endOffset
|
7570
|
+
} = data;
|
7571
|
+
if (type) {
|
7572
|
+
operationQueue.append(this.getFlushOp(type, startOffset, endOffset), type);
|
7450
7573
|
} else {
|
7451
|
-
this.getSourceBufferTypes().forEach(
|
7452
|
-
operationQueue.append(
|
7574
|
+
this.getSourceBufferTypes().forEach(sbType => {
|
7575
|
+
operationQueue.append(this.getFlushOp(sbType, startOffset, endOffset), sbType);
|
7453
7576
|
});
|
7454
7577
|
}
|
7455
7578
|
}
|
@@ -7496,6 +7619,9 @@ class BufferController {
|
|
7496
7619
|
// on BUFFER_EOS mark matching sourcebuffer(s) as ended and trigger checkEos()
|
7497
7620
|
// an undefined data.type will mark all buffers as EOS.
|
7498
7621
|
onBufferEos(event, data) {
|
7622
|
+
if (data.type === 'video') {
|
7623
|
+
this.unblockAudio();
|
7624
|
+
}
|
7499
7625
|
const ended = this.getSourceBufferTypes().reduce((acc, type) => {
|
7500
7626
|
const sb = this.sourceBuffer[type];
|
7501
7627
|
if (sb && (!data.type || data.type === type)) {
|
@@ -7538,10 +7664,14 @@ class BufferController {
|
|
7538
7664
|
return;
|
7539
7665
|
}
|
7540
7666
|
this.details = details;
|
7667
|
+
const durationAndRange = this.getDurationAndRange();
|
7668
|
+
if (!durationAndRange) {
|
7669
|
+
return;
|
7670
|
+
}
|
7541
7671
|
if (this.getSourceBufferTypes().length) {
|
7542
|
-
this.blockBuffers(this.
|
7672
|
+
this.blockBuffers(() => this.updateMediaSource(durationAndRange));
|
7543
7673
|
} else {
|
7544
|
-
this.
|
7674
|
+
this.updateMediaSource(durationAndRange);
|
7545
7675
|
}
|
7546
7676
|
}
|
7547
7677
|
trimBuffers() {
|
@@ -7646,9 +7776,9 @@ class BufferController {
|
|
7646
7776
|
* 'liveDurationInfinity` is set to `true`
|
7647
7777
|
* More details: https://github.com/video-dev/hls.js/issues/355
|
7648
7778
|
*/
|
7649
|
-
|
7779
|
+
getDurationAndRange() {
|
7650
7780
|
if (!this.details || !this.media || !this.mediaSource || this.mediaSource.readyState !== 'open') {
|
7651
|
-
return;
|
7781
|
+
return null;
|
7652
7782
|
}
|
7653
7783
|
const {
|
7654
7784
|
details,
|
@@ -7662,25 +7792,41 @@ class BufferController {
|
|
7662
7792
|
if (details.live && hls.config.liveDurationInfinity) {
|
7663
7793
|
// Override duration to Infinity
|
7664
7794
|
mediaSource.duration = Infinity;
|
7665
|
-
|
7795
|
+
const len = details.fragments.length;
|
7796
|
+
if (len && details.live && !!mediaSource.setLiveSeekableRange) {
|
7797
|
+
const start = Math.max(0, details.fragments[0].start);
|
7798
|
+
const end = Math.max(start, start + details.totalduration);
|
7799
|
+
return {
|
7800
|
+
duration: Infinity,
|
7801
|
+
start,
|
7802
|
+
end
|
7803
|
+
};
|
7804
|
+
}
|
7805
|
+
return {
|
7806
|
+
duration: Infinity
|
7807
|
+
};
|
7666
7808
|
} else if (levelDuration > msDuration && levelDuration > mediaDuration || !isFiniteNumber(mediaDuration)) {
|
7667
|
-
|
7668
|
-
|
7669
|
-
|
7670
|
-
// flushing already buffered portion when switching between quality level
|
7671
|
-
this.log(`Updating Media Source duration to ${levelDuration.toFixed(3)}`);
|
7672
|
-
mediaSource.duration = levelDuration;
|
7809
|
+
return {
|
7810
|
+
duration: levelDuration
|
7811
|
+
};
|
7673
7812
|
}
|
7813
|
+
return null;
|
7674
7814
|
}
|
7675
|
-
|
7676
|
-
|
7677
|
-
|
7678
|
-
|
7679
|
-
|
7680
|
-
|
7681
|
-
|
7682
|
-
|
7683
|
-
|
7815
|
+
updateMediaSource({
|
7816
|
+
duration,
|
7817
|
+
start,
|
7818
|
+
end
|
7819
|
+
}) {
|
7820
|
+
if (!this.media || !this.mediaSource || this.mediaSource.readyState !== 'open') {
|
7821
|
+
return;
|
7822
|
+
}
|
7823
|
+
if (isFiniteNumber(duration)) {
|
7824
|
+
this.log(`Updating Media Source duration to ${duration.toFixed(3)}`);
|
7825
|
+
}
|
7826
|
+
this.mediaSource.duration = duration;
|
7827
|
+
if (start !== undefined && end !== undefined) {
|
7828
|
+
this.log(`Media Source duration is set to ${this.mediaSource.duration}. Setting seekable range to ${start}-${end}.`);
|
7829
|
+
this.mediaSource.setLiveSeekableRange(start, end);
|
7684
7830
|
}
|
7685
7831
|
}
|
7686
7832
|
checkPendingTracks() {
|
@@ -7865,6 +8011,7 @@ class BufferController {
|
|
7865
8011
|
}
|
7866
8012
|
return;
|
7867
8013
|
}
|
8014
|
+
sb.ending = false;
|
7868
8015
|
sb.ended = false;
|
7869
8016
|
sb.appendBuffer(data);
|
7870
8017
|
}
|
@@ -7884,10 +8031,14 @@ class BufferController {
|
|
7884
8031
|
|
7885
8032
|
// logger.debug(`[buffer-controller]: Blocking ${buffers} SourceBuffer`);
|
7886
8033
|
const blockingOperations = buffers.map(type => operationQueue.appendBlocker(type));
|
7887
|
-
|
8034
|
+
const audioBlocked = buffers.length > 1 && !!this.blockedAudioAppend;
|
8035
|
+
if (audioBlocked) {
|
8036
|
+
this.unblockAudio();
|
8037
|
+
}
|
8038
|
+
Promise.all(blockingOperations).then(result => {
|
7888
8039
|
// logger.debug(`[buffer-controller]: Blocking operation resolved; unblocking ${buffers} SourceBuffer`);
|
7889
8040
|
onUnblocked();
|
7890
|
-
buffers.forEach(type => {
|
8041
|
+
buffers.forEach((type, i) => {
|
7891
8042
|
const sb = this.sourceBuffer[type];
|
7892
8043
|
// Only cycle the queue if the SB is not updating. There's a bug in Chrome which sets the SB updating flag to
|
7893
8044
|
// true when changing the MediaSource duration (https://bugs.chromium.org/p/chromium/issues/detail?id=959359&can=2&q=mediasource%20duration)
|
@@ -8048,10 +8199,10 @@ class CapLevelController {
|
|
8048
8199
|
const hls = this.hls;
|
8049
8200
|
const maxLevel = this.getMaxLevel(levels.length - 1);
|
8050
8201
|
if (maxLevel !== this.autoLevelCapping) {
|
8051
|
-
logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
|
8202
|
+
hls.logger.log(`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`);
|
8052
8203
|
}
|
8053
8204
|
hls.autoLevelCapping = maxLevel;
|
8054
|
-
if (hls.autoLevelCapping > this.autoLevelCapping && this.streamController) {
|
8205
|
+
if (hls.autoLevelEnabled && hls.autoLevelCapping > this.autoLevelCapping && this.streamController) {
|
8055
8206
|
// if auto level capping has a higher value for the previous one, flush the buffer using nextLevelSwitch
|
8056
8207
|
// usually happen when the user go to the fullscreen mode.
|
8057
8208
|
this.streamController.nextLevelSwitch();
|
@@ -8226,10 +8377,10 @@ class FPSController {
|
|
8226
8377
|
totalDroppedFrames: droppedFrames
|
8227
8378
|
});
|
8228
8379
|
if (droppedFPS > 0) {
|
8229
|
-
// logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
|
8380
|
+
// hls.logger.log('checkFPS : droppedFPS/decodedFPS:' + droppedFPS/(1000 * currentDecoded / currentPeriod));
|
8230
8381
|
if (currentDropped > hls.config.fpsDroppedMonitoringThreshold * currentDecoded) {
|
8231
8382
|
let currentLevel = hls.currentLevel;
|
8232
|
-
logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
|
8383
|
+
hls.logger.warn('drop FPS ratio greater than max allowed value for currentLevel: ' + currentLevel);
|
8233
8384
|
if (currentLevel > 0 && (hls.autoLevelCapping === -1 || hls.autoLevelCapping >= currentLevel)) {
|
8234
8385
|
currentLevel = currentLevel - 1;
|
8235
8386
|
hls.trigger(Events.FPS_DROP_LEVEL_CAPPING, {
|
@@ -8262,10 +8413,10 @@ class FPSController {
|
|
8262
8413
|
}
|
8263
8414
|
|
8264
8415
|
const PATHWAY_PENALTY_DURATION_MS = 300000;
|
8265
|
-
class ContentSteeringController {
|
8416
|
+
class ContentSteeringController extends Logger {
|
8266
8417
|
constructor(hls) {
|
8418
|
+
super('content-steering', hls.logger);
|
8267
8419
|
this.hls = void 0;
|
8268
|
-
this.log = void 0;
|
8269
8420
|
this.loader = null;
|
8270
8421
|
this.uri = null;
|
8271
8422
|
this.pathwayId = '.';
|
@@ -8280,7 +8431,6 @@ class ContentSteeringController {
|
|
8280
8431
|
this.subtitleTracks = null;
|
8281
8432
|
this.penalizedPathways = {};
|
8282
8433
|
this.hls = hls;
|
8283
|
-
this.log = logger.log.bind(logger, `[content-steering]:`);
|
8284
8434
|
this.registerListeners();
|
8285
8435
|
}
|
8286
8436
|
registerListeners() {
|
@@ -8404,7 +8554,7 @@ class ContentSteeringController {
|
|
8404
8554
|
errorAction.resolved = this.pathwayId !== errorPathway;
|
8405
8555
|
}
|
8406
8556
|
if (!errorAction.resolved) {
|
8407
|
-
|
8557
|
+
this.warn(`Could not resolve ${data.details} ("${data.error.message}") with content-steering for Pathway: ${errorPathway} levels: ${levels ? levels.length : levels} priorities: ${JSON.stringify(pathwayPriority)} penalized: ${JSON.stringify(this.penalizedPathways)}`);
|
8408
8558
|
}
|
8409
8559
|
}
|
8410
8560
|
}
|
@@ -8575,7 +8725,7 @@ class ContentSteeringController {
|
|
8575
8725
|
onSuccess: (response, stats, context, networkDetails) => {
|
8576
8726
|
this.log(`Loaded steering manifest: "${url}"`);
|
8577
8727
|
const steeringData = response.data;
|
8578
|
-
if (steeringData.VERSION !== 1) {
|
8728
|
+
if ((steeringData == null ? void 0 : steeringData.VERSION) !== 1) {
|
8579
8729
|
this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
|
8580
8730
|
return;
|
8581
8731
|
}
|
@@ -9483,7 +9633,7 @@ const hlsDefaultConfig = _objectSpread2(_objectSpread2({
|
|
9483
9633
|
});
|
9484
9634
|
function timelineConfig() {
|
9485
9635
|
return {
|
9486
|
-
cueHandler:
|
9636
|
+
cueHandler: HevcVideoParser,
|
9487
9637
|
// used by timeline-controller
|
9488
9638
|
enableWebVTT: false,
|
9489
9639
|
// used by timeline-controller
|
@@ -9514,7 +9664,7 @@ function timelineConfig() {
|
|
9514
9664
|
/**
|
9515
9665
|
* @ignore
|
9516
9666
|
*/
|
9517
|
-
function mergeConfig(defaultConfig, userConfig) {
|
9667
|
+
function mergeConfig(defaultConfig, userConfig, logger) {
|
9518
9668
|
if ((userConfig.liveSyncDurationCount || userConfig.liveMaxLatencyDurationCount) && (userConfig.liveSyncDuration || userConfig.liveMaxLatencyDuration)) {
|
9519
9669
|
throw new Error("Illegal hls.js config: don't mix up liveSyncDurationCount/liveMaxLatencyDurationCount and liveSyncDuration/liveMaxLatencyDuration");
|
9520
9670
|
}
|
@@ -9584,7 +9734,7 @@ function deepCpy(obj) {
|
|
9584
9734
|
/**
|
9585
9735
|
* @ignore
|
9586
9736
|
*/
|
9587
|
-
function enableStreamingMode(config) {
|
9737
|
+
function enableStreamingMode(config, logger) {
|
9588
9738
|
const currentLoader = config.loader;
|
9589
9739
|
if (currentLoader !== FetchLoader && currentLoader !== XhrLoader) {
|
9590
9740
|
// If a developer has configured their own loader, respect that choice
|
@@ -9601,10 +9751,9 @@ function enableStreamingMode(config) {
|
|
9601
9751
|
}
|
9602
9752
|
}
|
9603
9753
|
|
9604
|
-
let chromeOrFirefox;
|
9605
9754
|
class LevelController extends BasePlaylistController {
|
9606
9755
|
constructor(hls, contentSteeringController) {
|
9607
|
-
super(hls, '
|
9756
|
+
super(hls, 'level-controller');
|
9608
9757
|
this._levels = [];
|
9609
9758
|
this._firstLevel = -1;
|
9610
9759
|
this._maxAutoLevel = -1;
|
@@ -9675,23 +9824,15 @@ class LevelController extends BasePlaylistController {
|
|
9675
9824
|
let videoCodecFound = false;
|
9676
9825
|
let audioCodecFound = false;
|
9677
9826
|
data.levels.forEach(levelParsed => {
|
9678
|
-
var
|
9827
|
+
var _videoCodec;
|
9679
9828
|
const attributes = levelParsed.attrs;
|
9680
|
-
|
9681
|
-
// erase audio codec info if browser does not support mp4a.40.34.
|
9682
|
-
// demuxer will autodetect codec and fallback to mpeg/audio
|
9683
9829
|
let {
|
9684
9830
|
audioCodec,
|
9685
9831
|
videoCodec
|
9686
9832
|
} = levelParsed;
|
9687
|
-
if (((_audioCodec = audioCodec) == null ? void 0 : _audioCodec.indexOf('mp4a.40.34')) !== -1) {
|
9688
|
-
chromeOrFirefox || (chromeOrFirefox = /chrome|firefox/i.test(navigator.userAgent));
|
9689
|
-
if (chromeOrFirefox) {
|
9690
|
-
levelParsed.audioCodec = audioCodec = undefined;
|
9691
|
-
}
|
9692
|
-
}
|
9693
9833
|
if (audioCodec) {
|
9694
|
-
|
9834
|
+
// Returns empty and set to undefined for 'mp4a.40.34' with fallback to 'audio/mpeg' SourceBuffer
|
9835
|
+
levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource) || undefined;
|
9695
9836
|
}
|
9696
9837
|
if (((_videoCodec = videoCodec) == null ? void 0 : _videoCodec.indexOf('avc1')) === 0) {
|
9697
9838
|
videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
|
@@ -10033,7 +10174,12 @@ class LevelController extends BasePlaylistController {
|
|
10033
10174
|
if (curLevel.fragmentError === 0) {
|
10034
10175
|
curLevel.loadError = 0;
|
10035
10176
|
}
|
10036
|
-
|
10177
|
+
// Ignore matching details populated by loading a Media Playlist directly
|
10178
|
+
let previousDetails = curLevel.details;
|
10179
|
+
if (previousDetails === data.details && previousDetails.advanced) {
|
10180
|
+
previousDetails = undefined;
|
10181
|
+
}
|
10182
|
+
this.playlistLoaded(level, data, previousDetails);
|
10037
10183
|
} else if ((_data$deliveryDirecti2 = data.deliveryDirectives) != null && _data$deliveryDirecti2.skip) {
|
10038
10184
|
// received a delta playlist update that cannot be merged
|
10039
10185
|
details.deltaUpdateFailed = true;
|
@@ -10211,13 +10357,16 @@ class FragmentTracker {
|
|
10211
10357
|
* If not found any Fragment, return null
|
10212
10358
|
*/
|
10213
10359
|
getBufferedFrag(position, levelType) {
|
10360
|
+
return this.getFragAtPos(position, levelType, true);
|
10361
|
+
}
|
10362
|
+
getFragAtPos(position, levelType, buffered) {
|
10214
10363
|
const {
|
10215
10364
|
fragments
|
10216
10365
|
} = this;
|
10217
10366
|
const keys = Object.keys(fragments);
|
10218
10367
|
for (let i = keys.length; i--;) {
|
10219
10368
|
const fragmentEntity = fragments[keys[i]];
|
10220
|
-
if ((fragmentEntity == null ? void 0 : fragmentEntity.body.type) === levelType && fragmentEntity.buffered) {
|
10369
|
+
if ((fragmentEntity == null ? void 0 : fragmentEntity.body.type) === levelType && (!buffered || fragmentEntity.buffered)) {
|
10221
10370
|
const frag = fragmentEntity.body;
|
10222
10371
|
if (frag.start <= position && position <= frag.end) {
|
10223
10372
|
return frag;
|
@@ -10472,7 +10621,8 @@ class FragmentTracker {
|
|
10472
10621
|
const {
|
10473
10622
|
frag,
|
10474
10623
|
part,
|
10475
|
-
timeRanges
|
10624
|
+
timeRanges,
|
10625
|
+
type
|
10476
10626
|
} = data;
|
10477
10627
|
if (frag.sn === 'initSegment') {
|
10478
10628
|
return;
|
@@ -10487,10 +10637,8 @@ class FragmentTracker {
|
|
10487
10637
|
}
|
10488
10638
|
// Store the latest timeRanges loaded in the buffer
|
10489
10639
|
this.timeRanges = timeRanges;
|
10490
|
-
|
10491
|
-
|
10492
|
-
this.detectEvictedFragments(elementaryStream, timeRange, playlistType, part);
|
10493
|
-
});
|
10640
|
+
const timeRange = timeRanges[type];
|
10641
|
+
this.detectEvictedFragments(type, timeRange, playlistType, part);
|
10494
10642
|
}
|
10495
10643
|
onFragBuffered(event, data) {
|
10496
10644
|
this.detectPartialFragments(data);
|
@@ -10819,8 +10967,8 @@ function createLoaderContext(frag, part = null) {
|
|
10819
10967
|
var _frag$decryptdata;
|
10820
10968
|
let byteRangeStart = start;
|
10821
10969
|
let byteRangeEnd = end;
|
10822
|
-
if (frag.sn === 'initSegment' && ((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method)
|
10823
|
-
// MAP segment encrypted with method 'AES-128', when served with HTTP Range,
|
10970
|
+
if (frag.sn === 'initSegment' && isMethodFullSegmentAesCbc((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method)) {
|
10971
|
+
// MAP segment encrypted with method 'AES-128' or 'AES-256' (cbc), when served with HTTP Range,
|
10824
10972
|
// has the unencrypted size specified in the range.
|
10825
10973
|
// Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
|
10826
10974
|
const fragmentLen = end - start;
|
@@ -10853,6 +11001,9 @@ function createGapLoadError(frag, part) {
|
|
10853
11001
|
(part ? part : frag).stats.aborted = true;
|
10854
11002
|
return new LoadError(errorData);
|
10855
11003
|
}
|
11004
|
+
function isMethodFullSegmentAesCbc(method) {
|
11005
|
+
return method === 'AES-128' || method === 'AES-256';
|
11006
|
+
}
|
10856
11007
|
class LoadError extends Error {
|
10857
11008
|
constructor(data) {
|
10858
11009
|
super(data.error.message);
|
@@ -10998,6 +11149,8 @@ class KeyLoader {
|
|
10998
11149
|
}
|
10999
11150
|
return this.loadKeyEME(keyInfo, frag);
|
11000
11151
|
case 'AES-128':
|
11152
|
+
case 'AES-256':
|
11153
|
+
case 'AES-256-CTR':
|
11001
11154
|
return this.loadKeyHTTP(keyInfo, frag);
|
11002
11155
|
default:
|
11003
11156
|
return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`)));
|
@@ -11133,8 +11286,9 @@ class KeyLoader {
|
|
11133
11286
|
* we are limiting the task execution per call stack to exactly one, but scheduling/post-poning further
|
11134
11287
|
* task processing on the next main loop iteration (also known as "next tick" in the Node/JS runtime lingo).
|
11135
11288
|
*/
|
11136
|
-
class TaskLoop {
|
11137
|
-
constructor() {
|
11289
|
+
class TaskLoop extends Logger {
|
11290
|
+
constructor(label, logger) {
|
11291
|
+
super(label, logger);
|
11138
11292
|
this._boundTick = void 0;
|
11139
11293
|
this._tickTimer = null;
|
11140
11294
|
this._tickInterval = null;
|
@@ -11402,33 +11556,61 @@ function alignMediaPlaylistByPDT(details, refDetails) {
|
|
11402
11556
|
}
|
11403
11557
|
|
11404
11558
|
class AESCrypto {
|
11405
|
-
constructor(subtle, iv) {
|
11559
|
+
constructor(subtle, iv, aesMode) {
|
11406
11560
|
this.subtle = void 0;
|
11407
11561
|
this.aesIV = void 0;
|
11562
|
+
this.aesMode = void 0;
|
11408
11563
|
this.subtle = subtle;
|
11409
11564
|
this.aesIV = iv;
|
11565
|
+
this.aesMode = aesMode;
|
11410
11566
|
}
|
11411
11567
|
decrypt(data, key) {
|
11412
|
-
|
11413
|
-
|
11414
|
-
|
11415
|
-
|
11568
|
+
switch (this.aesMode) {
|
11569
|
+
case DecrypterAesMode.cbc:
|
11570
|
+
return this.subtle.decrypt({
|
11571
|
+
name: 'AES-CBC',
|
11572
|
+
iv: this.aesIV
|
11573
|
+
}, key, data);
|
11574
|
+
case DecrypterAesMode.ctr:
|
11575
|
+
return this.subtle.decrypt({
|
11576
|
+
name: 'AES-CTR',
|
11577
|
+
counter: this.aesIV,
|
11578
|
+
length: 64
|
11579
|
+
},
|
11580
|
+
//64 : NIST SP800-38A standard suggests that the counter should occupy half of the counter block
|
11581
|
+
key, data);
|
11582
|
+
default:
|
11583
|
+
throw new Error(`[AESCrypto] invalid aes mode ${this.aesMode}`);
|
11584
|
+
}
|
11416
11585
|
}
|
11417
11586
|
}
|
11418
11587
|
|
11419
11588
|
class FastAESKey {
|
11420
|
-
constructor(subtle, key) {
|
11589
|
+
constructor(subtle, key, aesMode) {
|
11421
11590
|
this.subtle = void 0;
|
11422
11591
|
this.key = void 0;
|
11592
|
+
this.aesMode = void 0;
|
11423
11593
|
this.subtle = subtle;
|
11424
11594
|
this.key = key;
|
11595
|
+
this.aesMode = aesMode;
|
11425
11596
|
}
|
11426
11597
|
expandKey() {
|
11598
|
+
const subtleAlgoName = getSubtleAlgoName(this.aesMode);
|
11427
11599
|
return this.subtle.importKey('raw', this.key, {
|
11428
|
-
name:
|
11600
|
+
name: subtleAlgoName
|
11429
11601
|
}, false, ['encrypt', 'decrypt']);
|
11430
11602
|
}
|
11431
11603
|
}
|
11604
|
+
function getSubtleAlgoName(aesMode) {
|
11605
|
+
switch (aesMode) {
|
11606
|
+
case DecrypterAesMode.cbc:
|
11607
|
+
return 'AES-CBC';
|
11608
|
+
case DecrypterAesMode.ctr:
|
11609
|
+
return 'AES-CTR';
|
11610
|
+
default:
|
11611
|
+
throw new Error(`[FastAESKey] invalid aes mode ${aesMode}`);
|
11612
|
+
}
|
11613
|
+
}
|
11432
11614
|
|
11433
11615
|
// PKCS7
|
11434
11616
|
function removePadding(array) {
|
@@ -11678,7 +11860,8 @@ class Decrypter {
|
|
11678
11860
|
this.currentIV = null;
|
11679
11861
|
this.currentResult = null;
|
11680
11862
|
this.useSoftware = void 0;
|
11681
|
-
this.
|
11863
|
+
this.enableSoftwareAES = void 0;
|
11864
|
+
this.enableSoftwareAES = config.enableSoftwareAES;
|
11682
11865
|
this.removePKCS7Padding = removePKCS7Padding;
|
11683
11866
|
// built in decryptor expects PKCS7 padding
|
11684
11867
|
if (removePKCS7Padding) {
|
@@ -11691,9 +11874,7 @@ class Decrypter {
|
|
11691
11874
|
/* no-op */
|
11692
11875
|
}
|
11693
11876
|
}
|
11694
|
-
|
11695
|
-
this.useSoftware = true;
|
11696
|
-
}
|
11877
|
+
this.useSoftware = this.subtle === null;
|
11697
11878
|
}
|
11698
11879
|
destroy() {
|
11699
11880
|
this.subtle = null;
|
@@ -11731,10 +11912,10 @@ class Decrypter {
|
|
11731
11912
|
this.softwareDecrypter = null;
|
11732
11913
|
}
|
11733
11914
|
}
|
11734
|
-
decrypt(data, key, iv) {
|
11915
|
+
decrypt(data, key, iv, aesMode) {
|
11735
11916
|
if (this.useSoftware) {
|
11736
11917
|
return new Promise((resolve, reject) => {
|
11737
|
-
this.softwareDecrypt(new Uint8Array(data), key, iv);
|
11918
|
+
this.softwareDecrypt(new Uint8Array(data), key, iv, aesMode);
|
11738
11919
|
const decryptResult = this.flush();
|
11739
11920
|
if (decryptResult) {
|
11740
11921
|
resolve(decryptResult.buffer);
|
@@ -11743,17 +11924,21 @@ class Decrypter {
|
|
11743
11924
|
}
|
11744
11925
|
});
|
11745
11926
|
}
|
11746
|
-
return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
|
11927
|
+
return this.webCryptoDecrypt(new Uint8Array(data), key, iv, aesMode);
|
11747
11928
|
}
|
11748
11929
|
|
11749
11930
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
11750
11931
|
// data is handled in the flush() call
|
11751
|
-
softwareDecrypt(data, key, iv) {
|
11932
|
+
softwareDecrypt(data, key, iv, aesMode) {
|
11752
11933
|
const {
|
11753
11934
|
currentIV,
|
11754
11935
|
currentResult,
|
11755
11936
|
remainderData
|
11756
11937
|
} = this;
|
11938
|
+
if (aesMode !== DecrypterAesMode.cbc || key.byteLength !== 16) {
|
11939
|
+
logger.warn('SoftwareDecrypt: can only handle AES-128-CBC');
|
11940
|
+
return null;
|
11941
|
+
}
|
11757
11942
|
this.logOnce('JS AES decrypt');
|
11758
11943
|
// The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
|
11759
11944
|
// This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached
|
@@ -11786,11 +11971,11 @@ class Decrypter {
|
|
11786
11971
|
}
|
11787
11972
|
return result;
|
11788
11973
|
}
|
11789
|
-
webCryptoDecrypt(data, key, iv) {
|
11974
|
+
webCryptoDecrypt(data, key, iv, aesMode) {
|
11790
11975
|
const subtle = this.subtle;
|
11791
11976
|
if (this.key !== key || !this.fastAesKey) {
|
11792
11977
|
this.key = key;
|
11793
|
-
this.fastAesKey = new FastAESKey(subtle, key);
|
11978
|
+
this.fastAesKey = new FastAESKey(subtle, key, aesMode);
|
11794
11979
|
}
|
11795
11980
|
return this.fastAesKey.expandKey().then(aesKey => {
|
11796
11981
|
// decrypt using web crypto
|
@@ -11798,22 +11983,25 @@ class Decrypter {
|
|
11798
11983
|
return Promise.reject(new Error('web crypto not initialized'));
|
11799
11984
|
}
|
11800
11985
|
this.logOnce('WebCrypto AES decrypt');
|
11801
|
-
const crypto = new AESCrypto(subtle, new Uint8Array(iv));
|
11986
|
+
const crypto = new AESCrypto(subtle, new Uint8Array(iv), aesMode);
|
11802
11987
|
return crypto.decrypt(data.buffer, aesKey);
|
11803
11988
|
}).catch(err => {
|
11804
11989
|
logger.warn(`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`);
|
11805
|
-
return this.onWebCryptoError(data, key, iv);
|
11990
|
+
return this.onWebCryptoError(data, key, iv, aesMode);
|
11806
11991
|
});
|
11807
11992
|
}
|
11808
|
-
onWebCryptoError(data, key, iv) {
|
11809
|
-
|
11810
|
-
|
11811
|
-
|
11812
|
-
|
11813
|
-
|
11814
|
-
|
11993
|
+
onWebCryptoError(data, key, iv, aesMode) {
|
11994
|
+
const enableSoftwareAES = this.enableSoftwareAES;
|
11995
|
+
if (enableSoftwareAES) {
|
11996
|
+
this.useSoftware = true;
|
11997
|
+
this.logEnabled = true;
|
11998
|
+
this.softwareDecrypt(data, key, iv, aesMode);
|
11999
|
+
const decryptResult = this.flush();
|
12000
|
+
if (decryptResult) {
|
12001
|
+
return decryptResult.buffer;
|
12002
|
+
}
|
11815
12003
|
}
|
11816
|
-
throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data');
|
12004
|
+
throw new Error('WebCrypto' + (enableSoftwareAES ? ' and softwareDecrypt' : '') + ': failed to decrypt data');
|
11817
12005
|
}
|
11818
12006
|
getValidChunk(data) {
|
11819
12007
|
let currentChunk = data;
|
@@ -11864,7 +12052,7 @@ const State = {
|
|
11864
12052
|
};
|
11865
12053
|
class BaseStreamController extends TaskLoop {
|
11866
12054
|
constructor(hls, fragmentTracker, keyLoader, logPrefix, playlistType) {
|
11867
|
-
super();
|
12055
|
+
super(logPrefix, hls.logger);
|
11868
12056
|
this.hls = void 0;
|
11869
12057
|
this.fragPrevious = null;
|
11870
12058
|
this.fragCurrent = null;
|
@@ -11889,22 +12077,98 @@ class BaseStreamController extends TaskLoop {
|
|
11889
12077
|
this.startFragRequested = false;
|
11890
12078
|
this.decrypter = void 0;
|
11891
12079
|
this.initPTS = [];
|
11892
|
-
this.
|
11893
|
-
this.
|
11894
|
-
this.
|
11895
|
-
|
11896
|
-
|
12080
|
+
this.buffering = true;
|
12081
|
+
this.loadingParts = false;
|
12082
|
+
this.onMediaSeeking = () => {
|
12083
|
+
const {
|
12084
|
+
config,
|
12085
|
+
fragCurrent,
|
12086
|
+
media,
|
12087
|
+
mediaBuffer,
|
12088
|
+
state
|
12089
|
+
} = this;
|
12090
|
+
const currentTime = media ? media.currentTime : 0;
|
12091
|
+
const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
|
12092
|
+
this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
|
12093
|
+
if (this.state === State.ENDED) {
|
12094
|
+
this.resetLoadingState();
|
12095
|
+
} else if (fragCurrent) {
|
12096
|
+
// Seeking while frag load is in progress
|
12097
|
+
const tolerance = config.maxFragLookUpTolerance;
|
12098
|
+
const fragStartOffset = fragCurrent.start - tolerance;
|
12099
|
+
const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
|
12100
|
+
// if seeking out of buffered range or into new one
|
12101
|
+
if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
|
12102
|
+
const pastFragment = currentTime > fragEndOffset;
|
12103
|
+
// if the seek position is outside the current fragment range
|
12104
|
+
if (currentTime < fragStartOffset || pastFragment) {
|
12105
|
+
if (pastFragment && fragCurrent.loader) {
|
12106
|
+
this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
|
12107
|
+
fragCurrent.abortRequests();
|
12108
|
+
this.resetLoadingState();
|
12109
|
+
}
|
12110
|
+
this.fragPrevious = null;
|
12111
|
+
}
|
12112
|
+
}
|
12113
|
+
}
|
12114
|
+
if (media) {
|
12115
|
+
// Remove gap fragments
|
12116
|
+
this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
|
12117
|
+
this.lastCurrentTime = currentTime;
|
12118
|
+
if (!this.loadingParts) {
|
12119
|
+
const bufferEnd = Math.max(bufferInfo.end, currentTime);
|
12120
|
+
const shouldLoadParts = this.shouldLoadParts(this.getLevelDetails(), bufferEnd);
|
12121
|
+
if (shouldLoadParts) {
|
12122
|
+
this.log(`LL-Part loading ON after seeking to ${currentTime.toFixed(2)} with buffer @${bufferEnd.toFixed(2)}`);
|
12123
|
+
this.loadingParts = shouldLoadParts;
|
12124
|
+
}
|
12125
|
+
}
|
12126
|
+
}
|
12127
|
+
|
12128
|
+
// in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
|
12129
|
+
if (!this.loadedmetadata && !bufferInfo.len) {
|
12130
|
+
this.nextLoadPosition = this.startPosition = currentTime;
|
12131
|
+
}
|
12132
|
+
|
12133
|
+
// Async tick to speed up processing
|
12134
|
+
this.tickImmediate();
|
12135
|
+
};
|
12136
|
+
this.onMediaEnded = () => {
|
12137
|
+
// reset startPosition and lastCurrentTime to restart playback @ stream beginning
|
12138
|
+
this.startPosition = this.lastCurrentTime = 0;
|
12139
|
+
if (this.playlistType === PlaylistLevelType.MAIN) {
|
12140
|
+
this.hls.trigger(Events.MEDIA_ENDED, {
|
12141
|
+
stalled: false
|
12142
|
+
});
|
12143
|
+
}
|
12144
|
+
};
|
11897
12145
|
this.playlistType = playlistType;
|
11898
|
-
this.logPrefix = logPrefix;
|
11899
|
-
this.log = logger.log.bind(logger, `${logPrefix}:`);
|
11900
|
-
this.warn = logger.warn.bind(logger, `${logPrefix}:`);
|
11901
12146
|
this.hls = hls;
|
11902
12147
|
this.fragmentLoader = new FragmentLoader(hls.config);
|
11903
12148
|
this.keyLoader = keyLoader;
|
11904
12149
|
this.fragmentTracker = fragmentTracker;
|
11905
12150
|
this.config = hls.config;
|
11906
12151
|
this.decrypter = new Decrypter(hls.config);
|
12152
|
+
}
|
12153
|
+
registerListeners() {
|
12154
|
+
const {
|
12155
|
+
hls
|
12156
|
+
} = this;
|
12157
|
+
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
12158
|
+
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
12159
|
+
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
11907
12160
|
hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
12161
|
+
hls.on(Events.ERROR, this.onError, this);
|
12162
|
+
}
|
12163
|
+
unregisterListeners() {
|
12164
|
+
const {
|
12165
|
+
hls
|
12166
|
+
} = this;
|
12167
|
+
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
12168
|
+
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
12169
|
+
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
12170
|
+
hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
12171
|
+
hls.off(Events.ERROR, this.onError, this);
|
11908
12172
|
}
|
11909
12173
|
doTick() {
|
11910
12174
|
this.onTickEnd();
|
@@ -11928,6 +12192,12 @@ class BaseStreamController extends TaskLoop {
|
|
11928
12192
|
this.clearNextTick();
|
11929
12193
|
this.state = State.STOPPED;
|
11930
12194
|
}
|
12195
|
+
pauseBuffering() {
|
12196
|
+
this.buffering = false;
|
12197
|
+
}
|
12198
|
+
resumeBuffering() {
|
12199
|
+
this.buffering = true;
|
12200
|
+
}
|
11931
12201
|
_streamEnded(bufferInfo, levelDetails) {
|
11932
12202
|
// If playlist is live, there is another buffered range after the current range, nothing buffered, media is detached,
|
11933
12203
|
// of nothing loading/loaded return false
|
@@ -11958,10 +12228,8 @@ class BaseStreamController extends TaskLoop {
|
|
11958
12228
|
}
|
11959
12229
|
onMediaAttached(event, data) {
|
11960
12230
|
const media = this.media = this.mediaBuffer = data.media;
|
11961
|
-
|
11962
|
-
|
11963
|
-
media.addEventListener('seeking', this.onvseeking);
|
11964
|
-
media.addEventListener('ended', this.onvended);
|
12231
|
+
media.addEventListener('seeking', this.onMediaSeeking);
|
12232
|
+
media.addEventListener('ended', this.onMediaEnded);
|
11965
12233
|
const config = this.config;
|
11966
12234
|
if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
|
11967
12235
|
this.startLoad(config.startPosition);
|
@@ -11975,10 +12243,9 @@ class BaseStreamController extends TaskLoop {
|
|
11975
12243
|
}
|
11976
12244
|
|
11977
12245
|
// remove video listeners
|
11978
|
-
if (media
|
11979
|
-
media.removeEventListener('seeking', this.
|
11980
|
-
media.removeEventListener('ended', this.
|
11981
|
-
this.onvseeking = this.onvended = null;
|
12246
|
+
if (media) {
|
12247
|
+
media.removeEventListener('seeking', this.onMediaSeeking);
|
12248
|
+
media.removeEventListener('ended', this.onMediaEnded);
|
11982
12249
|
}
|
11983
12250
|
if (this.keyLoader) {
|
11984
12251
|
this.keyLoader.detach();
|
@@ -11988,56 +12255,8 @@ class BaseStreamController extends TaskLoop {
|
|
11988
12255
|
this.fragmentTracker.removeAllFragments();
|
11989
12256
|
this.stopLoad();
|
11990
12257
|
}
|
11991
|
-
|
11992
|
-
|
11993
|
-
config,
|
11994
|
-
fragCurrent,
|
11995
|
-
media,
|
11996
|
-
mediaBuffer,
|
11997
|
-
state
|
11998
|
-
} = this;
|
11999
|
-
const currentTime = media ? media.currentTime : 0;
|
12000
|
-
const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
|
12001
|
-
this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
|
12002
|
-
if (this.state === State.ENDED) {
|
12003
|
-
this.resetLoadingState();
|
12004
|
-
} else if (fragCurrent) {
|
12005
|
-
// Seeking while frag load is in progress
|
12006
|
-
const tolerance = config.maxFragLookUpTolerance;
|
12007
|
-
const fragStartOffset = fragCurrent.start - tolerance;
|
12008
|
-
const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
|
12009
|
-
// if seeking out of buffered range or into new one
|
12010
|
-
if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
|
12011
|
-
const pastFragment = currentTime > fragEndOffset;
|
12012
|
-
// if the seek position is outside the current fragment range
|
12013
|
-
if (currentTime < fragStartOffset || pastFragment) {
|
12014
|
-
if (pastFragment && fragCurrent.loader) {
|
12015
|
-
this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
|
12016
|
-
fragCurrent.abortRequests();
|
12017
|
-
this.resetLoadingState();
|
12018
|
-
}
|
12019
|
-
this.fragPrevious = null;
|
12020
|
-
}
|
12021
|
-
}
|
12022
|
-
}
|
12023
|
-
if (media) {
|
12024
|
-
// Remove gap fragments
|
12025
|
-
this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
|
12026
|
-
this.lastCurrentTime = currentTime;
|
12027
|
-
}
|
12028
|
-
|
12029
|
-
// in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
|
12030
|
-
if (!this.loadedmetadata && !bufferInfo.len) {
|
12031
|
-
this.nextLoadPosition = this.startPosition = currentTime;
|
12032
|
-
}
|
12033
|
-
|
12034
|
-
// Async tick to speed up processing
|
12035
|
-
this.tickImmediate();
|
12036
|
-
}
|
12037
|
-
onMediaEnded() {
|
12038
|
-
// reset startPosition and lastCurrentTime to restart playback @ stream beginning
|
12039
|
-
this.startPosition = this.lastCurrentTime = 0;
|
12040
|
-
}
|
12258
|
+
onManifestLoading() {}
|
12259
|
+
onError(event, data) {}
|
12041
12260
|
onManifestLoaded(event, data) {
|
12042
12261
|
this.startTimeOffset = data.startTimeOffset;
|
12043
12262
|
this.initPTS = [];
|
@@ -12047,7 +12266,7 @@ class BaseStreamController extends TaskLoop {
|
|
12047
12266
|
this.stopLoad();
|
12048
12267
|
super.onHandlerDestroying();
|
12049
12268
|
// @ts-ignore
|
12050
|
-
this.hls = null;
|
12269
|
+
this.hls = this.onMediaSeeking = this.onMediaEnded = null;
|
12051
12270
|
}
|
12052
12271
|
onHandlerDestroyed() {
|
12053
12272
|
this.state = State.STOPPED;
|
@@ -12178,10 +12397,10 @@ class BaseStreamController extends TaskLoop {
|
|
12178
12397
|
const decryptData = frag.decryptdata;
|
12179
12398
|
|
12180
12399
|
// check to see if the payload needs to be decrypted
|
12181
|
-
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method
|
12400
|
+
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && isFullSegmentEncryption(decryptData.method)) {
|
12182
12401
|
const startTime = self.performance.now();
|
12183
12402
|
// decrypt init segment data
|
12184
|
-
return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
|
12403
|
+
return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer, getAesModeFromFullSegmentMethod(decryptData.method)).catch(err => {
|
12185
12404
|
hls.trigger(Events.ERROR, {
|
12186
12405
|
type: ErrorTypes.MEDIA_ERROR,
|
12187
12406
|
details: ErrorDetails.FRAG_DECRYPT_ERROR,
|
@@ -12293,7 +12512,7 @@ class BaseStreamController extends TaskLoop {
|
|
12293
12512
|
}
|
12294
12513
|
let keyLoadingPromise = null;
|
12295
12514
|
if (frag.encrypted && !((_frag$decryptdata = frag.decryptdata) != null && _frag$decryptdata.key)) {
|
12296
|
-
this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.
|
12515
|
+
this.log(`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'} ${frag.level}`);
|
12297
12516
|
this.state = State.KEY_LOADING;
|
12298
12517
|
this.fragCurrent = frag;
|
12299
12518
|
keyLoadingPromise = this.keyLoader.load(frag).then(keyLoadedData => {
|
@@ -12314,8 +12533,16 @@ class BaseStreamController extends TaskLoop {
|
|
12314
12533
|
} else if (!frag.encrypted && details.encryptedFragments.length) {
|
12315
12534
|
this.keyLoader.loadClear(frag, details.encryptedFragments);
|
12316
12535
|
}
|
12536
|
+
const fragPrevious = this.fragPrevious;
|
12537
|
+
if (frag.sn !== 'initSegment' && (!fragPrevious || frag.sn !== fragPrevious.sn)) {
|
12538
|
+
const shouldLoadParts = this.shouldLoadParts(level.details, frag.end);
|
12539
|
+
if (shouldLoadParts !== this.loadingParts) {
|
12540
|
+
this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} loading sn ${fragPrevious == null ? void 0 : fragPrevious.sn}->${frag.sn}`);
|
12541
|
+
this.loadingParts = shouldLoadParts;
|
12542
|
+
}
|
12543
|
+
}
|
12317
12544
|
targetBufferTime = Math.max(frag.start, targetBufferTime || 0);
|
12318
|
-
if (this.
|
12545
|
+
if (this.loadingParts && frag.sn !== 'initSegment') {
|
12319
12546
|
const partList = details.partList;
|
12320
12547
|
if (partList && progressCallback) {
|
12321
12548
|
if (targetBufferTime > frag.end && details.fragmentHint) {
|
@@ -12324,7 +12551,7 @@ class BaseStreamController extends TaskLoop {
|
|
12324
12551
|
const partIndex = this.getNextPart(partList, frag, targetBufferTime);
|
12325
12552
|
if (partIndex > -1) {
|
12326
12553
|
const part = partList[partIndex];
|
12327
|
-
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.
|
12554
|
+
this.log(`Loading part sn: ${frag.sn} p: ${part.index} cc: ${frag.cc} of playlist [${details.startSN}-${details.endSN}] parts [0-${partIndex}-${partList.length - 1}] ${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'}: ${frag.level}, target: ${parseFloat(targetBufferTime.toFixed(3))}`);
|
12328
12555
|
this.nextLoadPosition = part.start + part.duration;
|
12329
12556
|
this.state = State.FRAG_LOADING;
|
12330
12557
|
let _result;
|
@@ -12353,7 +12580,14 @@ class BaseStreamController extends TaskLoop {
|
|
12353
12580
|
}
|
12354
12581
|
}
|
12355
12582
|
}
|
12356
|
-
|
12583
|
+
if (frag.sn !== 'initSegment' && this.loadingParts) {
|
12584
|
+
this.log(`LL-Part loading OFF after next part miss @${targetBufferTime.toFixed(2)}`);
|
12585
|
+
this.loadingParts = false;
|
12586
|
+
} else if (!frag.url) {
|
12587
|
+
// Selected fragment hint for part but not loading parts
|
12588
|
+
return Promise.resolve(null);
|
12589
|
+
}
|
12590
|
+
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))}`);
|
12357
12591
|
// Don't update nextLoadPosition for fragments which are not buffered
|
12358
12592
|
if (isFiniteNumber(frag.sn) && !this.bitrateTest) {
|
12359
12593
|
this.nextLoadPosition = frag.start + frag.duration;
|
@@ -12451,8 +12685,36 @@ class BaseStreamController extends TaskLoop {
|
|
12451
12685
|
if (part) {
|
12452
12686
|
part.stats.parsing.end = now;
|
12453
12687
|
}
|
12688
|
+
// See if part loading should be disabled/enabled based on buffer and playback position.
|
12689
|
+
if (frag.sn !== 'initSegment') {
|
12690
|
+
const levelDetails = this.getLevelDetails();
|
12691
|
+
const loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
|
12692
|
+
const shouldLoadParts = loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
|
12693
|
+
if (shouldLoadParts !== this.loadingParts) {
|
12694
|
+
this.log(`LL-Part loading ${shouldLoadParts ? 'ON' : 'OFF'} after parsing segment ending @${frag.end.toFixed(2)}`);
|
12695
|
+
this.loadingParts = shouldLoadParts;
|
12696
|
+
}
|
12697
|
+
}
|
12454
12698
|
this.updateLevelTiming(frag, part, level, chunkMeta.partial);
|
12455
12699
|
}
|
12700
|
+
shouldLoadParts(details, bufferEnd) {
|
12701
|
+
if (this.config.lowLatencyMode) {
|
12702
|
+
if (!details) {
|
12703
|
+
return this.loadingParts;
|
12704
|
+
}
|
12705
|
+
if (details != null && details.partList) {
|
12706
|
+
var _details$fragmentHint;
|
12707
|
+
// Buffer must be ahead of first part + duration of parts after last segment
|
12708
|
+
// and playback must be at or past segment adjacent to part list
|
12709
|
+
const firstPart = details.partList[0];
|
12710
|
+
const safePartStart = firstPart.end + (((_details$fragmentHint = details.fragmentHint) == null ? void 0 : _details$fragmentHint.duration) || 0);
|
12711
|
+
if (bufferEnd >= safePartStart && this.lastCurrentTime > firstPart.start - firstPart.fragment.duration) {
|
12712
|
+
return true;
|
12713
|
+
}
|
12714
|
+
}
|
12715
|
+
}
|
12716
|
+
return false;
|
12717
|
+
}
|
12456
12718
|
getCurrentContext(chunkMeta) {
|
12457
12719
|
const {
|
12458
12720
|
levels,
|
@@ -12553,7 +12815,7 @@ class BaseStreamController extends TaskLoop {
|
|
12553
12815
|
// Workaround flaw in getting forward buffer when maxBufferHole is smaller than gap at current pos
|
12554
12816
|
if (bufferInfo.len === 0 && bufferInfo.nextStart !== undefined) {
|
12555
12817
|
const bufferedFragAtPos = this.fragmentTracker.getBufferedFrag(pos, type);
|
12556
|
-
if (bufferedFragAtPos && bufferInfo.nextStart
|
12818
|
+
if (bufferedFragAtPos && (bufferInfo.nextStart <= bufferedFragAtPos.end || bufferedFragAtPos.gap)) {
|
12557
12819
|
return BufferHelper.bufferInfo(bufferable, pos, Math.max(bufferInfo.nextStart, maxBufferHole));
|
12558
12820
|
}
|
12559
12821
|
}
|
@@ -12601,7 +12863,8 @@ class BaseStreamController extends TaskLoop {
|
|
12601
12863
|
config
|
12602
12864
|
} = this;
|
12603
12865
|
const start = fragments[0].start;
|
12604
|
-
|
12866
|
+
const canLoadParts = config.lowLatencyMode && !!levelDetails.partList;
|
12867
|
+
let frag = null;
|
12605
12868
|
if (levelDetails.live) {
|
12606
12869
|
const initialLiveManifestSize = config.initialLiveManifestSize;
|
12607
12870
|
if (fragLen < initialLiveManifestSize) {
|
@@ -12613,6 +12876,10 @@ class BaseStreamController extends TaskLoop {
|
|
12613
12876
|
// Do not load using live logic if the starting frag is requested - we want to use getFragmentAtPosition() so that
|
12614
12877
|
// we get the fragment matching that start time
|
12615
12878
|
if (!levelDetails.PTSKnown && !this.startFragRequested && this.startPosition === -1 || pos < start) {
|
12879
|
+
if (canLoadParts && !this.loadingParts) {
|
12880
|
+
this.log(`LL-Part loading ON for initial live fragment`);
|
12881
|
+
this.loadingParts = true;
|
12882
|
+
}
|
12616
12883
|
frag = this.getInitialLiveFragment(levelDetails, fragments);
|
12617
12884
|
this.startPosition = this.nextLoadPosition = frag ? this.hls.liveSyncPosition || frag.start : pos;
|
12618
12885
|
}
|
@@ -12623,7 +12890,7 @@ class BaseStreamController extends TaskLoop {
|
|
12623
12890
|
|
12624
12891
|
// If we haven't run into any special cases already, just load the fragment most closely matching the requested position
|
12625
12892
|
if (!frag) {
|
12626
|
-
const end =
|
12893
|
+
const end = this.loadingParts ? levelDetails.partEnd : levelDetails.fragmentEnd;
|
12627
12894
|
frag = this.getFragmentAtPosition(pos, end, levelDetails);
|
12628
12895
|
}
|
12629
12896
|
return this.mapToInitFragWhenRequired(frag);
|
@@ -12745,7 +13012,7 @@ class BaseStreamController extends TaskLoop {
|
|
12745
13012
|
} = levelDetails;
|
12746
13013
|
const tolerance = config.maxFragLookUpTolerance;
|
12747
13014
|
const partList = levelDetails.partList;
|
12748
|
-
const loadingParts = !!(
|
13015
|
+
const loadingParts = !!(this.loadingParts && partList != null && partList.length && fragmentHint);
|
12749
13016
|
if (loadingParts && fragmentHint && !this.bitrateTest) {
|
12750
13017
|
// Include incomplete fragment with parts at end
|
12751
13018
|
fragments = fragments.concat(fragmentHint);
|
@@ -12938,7 +13205,7 @@ class BaseStreamController extends TaskLoop {
|
|
12938
13205
|
errorAction.resolved = true;
|
12939
13206
|
}
|
12940
13207
|
} else {
|
12941
|
-
|
13208
|
+
this.warn(`${data.details} reached or exceeded max retry (${retryCount})`);
|
12942
13209
|
return;
|
12943
13210
|
}
|
12944
13211
|
} else if ((errorAction == null ? void 0 : errorAction.action) === NetworkErrorAction.SendAlternateToPenaltyBox) {
|
@@ -13006,7 +13273,9 @@ class BaseStreamController extends TaskLoop {
|
|
13006
13273
|
this.log('Reset loading state');
|
13007
13274
|
this.fragCurrent = null;
|
13008
13275
|
this.fragPrevious = null;
|
13009
|
-
this.state
|
13276
|
+
if (this.state !== State.STOPPED) {
|
13277
|
+
this.state = State.IDLE;
|
13278
|
+
}
|
13010
13279
|
}
|
13011
13280
|
resetStartWhenNotLoaded(level) {
|
13012
13281
|
// if loadedmetadata is not set, it means that first frag request failed
|
@@ -13333,6 +13602,7 @@ const initPTSFn = (timestamp, timeOffset, initPTS) => {
|
|
13333
13602
|
*/
|
13334
13603
|
function getAudioConfig(observer, data, offset, audioCodec) {
|
13335
13604
|
let adtsObjectType;
|
13605
|
+
let originalAdtsObjectType;
|
13336
13606
|
let adtsExtensionSamplingIndex;
|
13337
13607
|
let adtsChannelConfig;
|
13338
13608
|
let config;
|
@@ -13340,7 +13610,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
13340
13610
|
const manifestCodec = audioCodec;
|
13341
13611
|
const adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
|
13342
13612
|
// byte 2
|
13343
|
-
adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
|
13613
|
+
adtsObjectType = originalAdtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
|
13344
13614
|
const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
|
13345
13615
|
if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
|
13346
13616
|
const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
|
@@ -13357,8 +13627,8 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
13357
13627
|
// byte 3
|
13358
13628
|
adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6;
|
13359
13629
|
logger.log(`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`);
|
13360
|
-
//
|
13361
|
-
if (/firefox/i.test(userAgent)) {
|
13630
|
+
// Firefox and Pale Moon: freq less than 24kHz = AAC SBR (HE-AAC)
|
13631
|
+
if (/firefox|palemoon/i.test(userAgent)) {
|
13362
13632
|
if (adtsSamplingIndex >= 6) {
|
13363
13633
|
adtsObjectType = 5;
|
13364
13634
|
config = new Array(4);
|
@@ -13452,6 +13722,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
13452
13722
|
samplerate: adtsSamplingRates[adtsSamplingIndex],
|
13453
13723
|
channelCount: adtsChannelConfig,
|
13454
13724
|
codec: 'mp4a.40.' + adtsObjectType,
|
13725
|
+
parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
|
13455
13726
|
manifestCodec
|
13456
13727
|
};
|
13457
13728
|
}
|
@@ -13506,7 +13777,8 @@ function initTrackConfig(track, observer, data, offset, audioCodec) {
|
|
13506
13777
|
track.channelCount = config.channelCount;
|
13507
13778
|
track.codec = config.codec;
|
13508
13779
|
track.manifestCodec = config.manifestCodec;
|
13509
|
-
|
13780
|
+
track.parsedCodec = config.parsedCodec;
|
13781
|
+
logger.log(`parsed codec:${track.parsedCodec}, codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
|
13510
13782
|
}
|
13511
13783
|
}
|
13512
13784
|
function getFrameDuration(samplerate) {
|
@@ -13984,6 +14256,110 @@ class BaseVideoParser {
|
|
13984
14256
|
logger.log(VideoSample.pts + '/' + VideoSample.dts + ':' + VideoSample.debug);
|
13985
14257
|
}
|
13986
14258
|
}
|
14259
|
+
parseNALu(track, array) {
|
14260
|
+
const len = array.byteLength;
|
14261
|
+
let state = track.naluState || 0;
|
14262
|
+
const lastState = state;
|
14263
|
+
const units = [];
|
14264
|
+
let i = 0;
|
14265
|
+
let value;
|
14266
|
+
let overflow;
|
14267
|
+
let unitType;
|
14268
|
+
let lastUnitStart = -1;
|
14269
|
+
let lastUnitType = 0;
|
14270
|
+
// logger.log('PES:' + Hex.hexDump(array));
|
14271
|
+
|
14272
|
+
if (state === -1) {
|
14273
|
+
// special use case where we found 3 or 4-byte start codes exactly at the end of previous PES packet
|
14274
|
+
lastUnitStart = 0;
|
14275
|
+
// NALu type is value read from offset 0
|
14276
|
+
lastUnitType = this.getNALuType(array, 0);
|
14277
|
+
state = 0;
|
14278
|
+
i = 1;
|
14279
|
+
}
|
14280
|
+
while (i < len) {
|
14281
|
+
value = array[i++];
|
14282
|
+
// optimization. state 0 and 1 are the predominant case. let's handle them outside of the switch/case
|
14283
|
+
if (!state) {
|
14284
|
+
state = value ? 0 : 1;
|
14285
|
+
continue;
|
14286
|
+
}
|
14287
|
+
if (state === 1) {
|
14288
|
+
state = value ? 0 : 2;
|
14289
|
+
continue;
|
14290
|
+
}
|
14291
|
+
// here we have state either equal to 2 or 3
|
14292
|
+
if (!value) {
|
14293
|
+
state = 3;
|
14294
|
+
} else if (value === 1) {
|
14295
|
+
overflow = i - state - 1;
|
14296
|
+
if (lastUnitStart >= 0) {
|
14297
|
+
const unit = {
|
14298
|
+
data: array.subarray(lastUnitStart, overflow),
|
14299
|
+
type: lastUnitType
|
14300
|
+
};
|
14301
|
+
// logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
|
14302
|
+
units.push(unit);
|
14303
|
+
} else {
|
14304
|
+
// lastUnitStart is undefined => this is the first start code found in this PES packet
|
14305
|
+
// first check if start code delimiter is overlapping between 2 PES packets,
|
14306
|
+
// ie it started in last packet (lastState not zero)
|
14307
|
+
// and ended at the beginning of this PES packet (i <= 4 - lastState)
|
14308
|
+
const lastUnit = this.getLastNalUnit(track.samples);
|
14309
|
+
if (lastUnit) {
|
14310
|
+
if (lastState && i <= 4 - lastState) {
|
14311
|
+
// start delimiter overlapping between PES packets
|
14312
|
+
// strip start delimiter bytes from the end of last NAL unit
|
14313
|
+
// check if lastUnit had a state different from zero
|
14314
|
+
if (lastUnit.state) {
|
14315
|
+
// strip last bytes
|
14316
|
+
lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
|
14317
|
+
}
|
14318
|
+
}
|
14319
|
+
// If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
|
14320
|
+
|
14321
|
+
if (overflow > 0) {
|
14322
|
+
// logger.log('first NALU found with overflow:' + overflow);
|
14323
|
+
lastUnit.data = appendUint8Array(lastUnit.data, array.subarray(0, overflow));
|
14324
|
+
lastUnit.state = 0;
|
14325
|
+
}
|
14326
|
+
}
|
14327
|
+
}
|
14328
|
+
// check if we can read unit type
|
14329
|
+
if (i < len) {
|
14330
|
+
unitType = this.getNALuType(array, i);
|
14331
|
+
// logger.log('find NALU @ offset:' + i + ',type:' + unitType);
|
14332
|
+
lastUnitStart = i;
|
14333
|
+
lastUnitType = unitType;
|
14334
|
+
state = 0;
|
14335
|
+
} else {
|
14336
|
+
// not enough byte to read unit type. let's read it on next PES parsing
|
14337
|
+
state = -1;
|
14338
|
+
}
|
14339
|
+
} else {
|
14340
|
+
state = 0;
|
14341
|
+
}
|
14342
|
+
}
|
14343
|
+
if (lastUnitStart >= 0 && state >= 0) {
|
14344
|
+
const unit = {
|
14345
|
+
data: array.subarray(lastUnitStart, len),
|
14346
|
+
type: lastUnitType,
|
14347
|
+
state: state
|
14348
|
+
};
|
14349
|
+
units.push(unit);
|
14350
|
+
// logger.log('pushing NALU, type/size/state:' + unit.type + '/' + unit.data.byteLength + '/' + state);
|
14351
|
+
}
|
14352
|
+
// no NALu found
|
14353
|
+
if (units.length === 0) {
|
14354
|
+
// append pes.data to previous NAL unit
|
14355
|
+
const lastUnit = this.getLastNalUnit(track.samples);
|
14356
|
+
if (lastUnit) {
|
14357
|
+
lastUnit.data = appendUint8Array(lastUnit.data, array);
|
14358
|
+
}
|
14359
|
+
}
|
14360
|
+
track.naluState = state;
|
14361
|
+
return units;
|
14362
|
+
}
|
13987
14363
|
}
|
13988
14364
|
|
13989
14365
|
/**
|
@@ -14126,194 +14502,11 @@ class ExpGolomb {
|
|
14126
14502
|
readUInt() {
|
14127
14503
|
return this.readBits(32);
|
14128
14504
|
}
|
14129
|
-
|
14130
|
-
/**
|
14131
|
-
* Advance the ExpGolomb decoder past a scaling list. The scaling
|
14132
|
-
* list is optionally transmitted as part of a sequence parameter
|
14133
|
-
* set and is not relevant to transmuxing.
|
14134
|
-
* @param count the number of entries in this scaling list
|
14135
|
-
* @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
|
14136
|
-
*/
|
14137
|
-
skipScalingList(count) {
|
14138
|
-
let lastScale = 8;
|
14139
|
-
let nextScale = 8;
|
14140
|
-
let deltaScale;
|
14141
|
-
for (let j = 0; j < count; j++) {
|
14142
|
-
if (nextScale !== 0) {
|
14143
|
-
deltaScale = this.readEG();
|
14144
|
-
nextScale = (lastScale + deltaScale + 256) % 256;
|
14145
|
-
}
|
14146
|
-
lastScale = nextScale === 0 ? lastScale : nextScale;
|
14147
|
-
}
|
14148
|
-
}
|
14149
|
-
|
14150
|
-
/**
|
14151
|
-
* Read a sequence parameter set and return some interesting video
|
14152
|
-
* properties. A sequence parameter set is the H264 metadata that
|
14153
|
-
* describes the properties of upcoming video frames.
|
14154
|
-
* @returns an object with configuration parsed from the
|
14155
|
-
* sequence parameter set, including the dimensions of the
|
14156
|
-
* associated video frames.
|
14157
|
-
*/
|
14158
|
-
readSPS() {
|
14159
|
-
let frameCropLeftOffset = 0;
|
14160
|
-
let frameCropRightOffset = 0;
|
14161
|
-
let frameCropTopOffset = 0;
|
14162
|
-
let frameCropBottomOffset = 0;
|
14163
|
-
let numRefFramesInPicOrderCntCycle;
|
14164
|
-
let scalingListCount;
|
14165
|
-
let i;
|
14166
|
-
const readUByte = this.readUByte.bind(this);
|
14167
|
-
const readBits = this.readBits.bind(this);
|
14168
|
-
const readUEG = this.readUEG.bind(this);
|
14169
|
-
const readBoolean = this.readBoolean.bind(this);
|
14170
|
-
const skipBits = this.skipBits.bind(this);
|
14171
|
-
const skipEG = this.skipEG.bind(this);
|
14172
|
-
const skipUEG = this.skipUEG.bind(this);
|
14173
|
-
const skipScalingList = this.skipScalingList.bind(this);
|
14174
|
-
readUByte();
|
14175
|
-
const profileIdc = readUByte(); // profile_idc
|
14176
|
-
readBits(5); // profileCompat constraint_set[0-4]_flag, u(5)
|
14177
|
-
skipBits(3); // reserved_zero_3bits u(3),
|
14178
|
-
readUByte(); // level_idc u(8)
|
14179
|
-
skipUEG(); // seq_parameter_set_id
|
14180
|
-
// some profiles have more optional data we don't need
|
14181
|
-
if (profileIdc === 100 || profileIdc === 110 || profileIdc === 122 || profileIdc === 244 || profileIdc === 44 || profileIdc === 83 || profileIdc === 86 || profileIdc === 118 || profileIdc === 128) {
|
14182
|
-
const chromaFormatIdc = readUEG();
|
14183
|
-
if (chromaFormatIdc === 3) {
|
14184
|
-
skipBits(1);
|
14185
|
-
} // separate_colour_plane_flag
|
14186
|
-
|
14187
|
-
skipUEG(); // bit_depth_luma_minus8
|
14188
|
-
skipUEG(); // bit_depth_chroma_minus8
|
14189
|
-
skipBits(1); // qpprime_y_zero_transform_bypass_flag
|
14190
|
-
if (readBoolean()) {
|
14191
|
-
// seq_scaling_matrix_present_flag
|
14192
|
-
scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
|
14193
|
-
for (i = 0; i < scalingListCount; i++) {
|
14194
|
-
if (readBoolean()) {
|
14195
|
-
// seq_scaling_list_present_flag[ i ]
|
14196
|
-
if (i < 6) {
|
14197
|
-
skipScalingList(16);
|
14198
|
-
} else {
|
14199
|
-
skipScalingList(64);
|
14200
|
-
}
|
14201
|
-
}
|
14202
|
-
}
|
14203
|
-
}
|
14204
|
-
}
|
14205
|
-
skipUEG(); // log2_max_frame_num_minus4
|
14206
|
-
const picOrderCntType = readUEG();
|
14207
|
-
if (picOrderCntType === 0) {
|
14208
|
-
readUEG(); // log2_max_pic_order_cnt_lsb_minus4
|
14209
|
-
} else if (picOrderCntType === 1) {
|
14210
|
-
skipBits(1); // delta_pic_order_always_zero_flag
|
14211
|
-
skipEG(); // offset_for_non_ref_pic
|
14212
|
-
skipEG(); // offset_for_top_to_bottom_field
|
14213
|
-
numRefFramesInPicOrderCntCycle = readUEG();
|
14214
|
-
for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
|
14215
|
-
skipEG();
|
14216
|
-
} // offset_for_ref_frame[ i ]
|
14217
|
-
}
|
14218
|
-
skipUEG(); // max_num_ref_frames
|
14219
|
-
skipBits(1); // gaps_in_frame_num_value_allowed_flag
|
14220
|
-
const picWidthInMbsMinus1 = readUEG();
|
14221
|
-
const picHeightInMapUnitsMinus1 = readUEG();
|
14222
|
-
const frameMbsOnlyFlag = readBits(1);
|
14223
|
-
if (frameMbsOnlyFlag === 0) {
|
14224
|
-
skipBits(1);
|
14225
|
-
} // mb_adaptive_frame_field_flag
|
14226
|
-
|
14227
|
-
skipBits(1); // direct_8x8_inference_flag
|
14228
|
-
if (readBoolean()) {
|
14229
|
-
// frame_cropping_flag
|
14230
|
-
frameCropLeftOffset = readUEG();
|
14231
|
-
frameCropRightOffset = readUEG();
|
14232
|
-
frameCropTopOffset = readUEG();
|
14233
|
-
frameCropBottomOffset = readUEG();
|
14234
|
-
}
|
14235
|
-
let pixelRatio = [1, 1];
|
14236
|
-
if (readBoolean()) {
|
14237
|
-
// vui_parameters_present_flag
|
14238
|
-
if (readBoolean()) {
|
14239
|
-
// aspect_ratio_info_present_flag
|
14240
|
-
const aspectRatioIdc = readUByte();
|
14241
|
-
switch (aspectRatioIdc) {
|
14242
|
-
case 1:
|
14243
|
-
pixelRatio = [1, 1];
|
14244
|
-
break;
|
14245
|
-
case 2:
|
14246
|
-
pixelRatio = [12, 11];
|
14247
|
-
break;
|
14248
|
-
case 3:
|
14249
|
-
pixelRatio = [10, 11];
|
14250
|
-
break;
|
14251
|
-
case 4:
|
14252
|
-
pixelRatio = [16, 11];
|
14253
|
-
break;
|
14254
|
-
case 5:
|
14255
|
-
pixelRatio = [40, 33];
|
14256
|
-
break;
|
14257
|
-
case 6:
|
14258
|
-
pixelRatio = [24, 11];
|
14259
|
-
break;
|
14260
|
-
case 7:
|
14261
|
-
pixelRatio = [20, 11];
|
14262
|
-
break;
|
14263
|
-
case 8:
|
14264
|
-
pixelRatio = [32, 11];
|
14265
|
-
break;
|
14266
|
-
case 9:
|
14267
|
-
pixelRatio = [80, 33];
|
14268
|
-
break;
|
14269
|
-
case 10:
|
14270
|
-
pixelRatio = [18, 11];
|
14271
|
-
break;
|
14272
|
-
case 11:
|
14273
|
-
pixelRatio = [15, 11];
|
14274
|
-
break;
|
14275
|
-
case 12:
|
14276
|
-
pixelRatio = [64, 33];
|
14277
|
-
break;
|
14278
|
-
case 13:
|
14279
|
-
pixelRatio = [160, 99];
|
14280
|
-
break;
|
14281
|
-
case 14:
|
14282
|
-
pixelRatio = [4, 3];
|
14283
|
-
break;
|
14284
|
-
case 15:
|
14285
|
-
pixelRatio = [3, 2];
|
14286
|
-
break;
|
14287
|
-
case 16:
|
14288
|
-
pixelRatio = [2, 1];
|
14289
|
-
break;
|
14290
|
-
case 255:
|
14291
|
-
{
|
14292
|
-
pixelRatio = [readUByte() << 8 | readUByte(), readUByte() << 8 | readUByte()];
|
14293
|
-
break;
|
14294
|
-
}
|
14295
|
-
}
|
14296
|
-
}
|
14297
|
-
}
|
14298
|
-
return {
|
14299
|
-
width: Math.ceil((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2),
|
14300
|
-
height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - (frameMbsOnlyFlag ? 2 : 4) * (frameCropTopOffset + frameCropBottomOffset),
|
14301
|
-
pixelRatio: pixelRatio
|
14302
|
-
};
|
14303
|
-
}
|
14304
|
-
readSliceType() {
|
14305
|
-
// skip NALu type
|
14306
|
-
this.readUByte();
|
14307
|
-
// discard first_mb_in_slice
|
14308
|
-
this.readUEG();
|
14309
|
-
// return slice_type
|
14310
|
-
return this.readUEG();
|
14311
|
-
}
|
14312
14505
|
}
|
14313
14506
|
|
14314
14507
|
class AvcVideoParser extends BaseVideoParser {
|
14315
|
-
|
14316
|
-
const units = this.
|
14508
|
+
parsePES(track, textTrack, pes, last, duration) {
|
14509
|
+
const units = this.parseNALu(track, pes.data);
|
14317
14510
|
let VideoSample = this.VideoSample;
|
14318
14511
|
let push;
|
14319
14512
|
let spsfound = false;
|
@@ -14338,7 +14531,7 @@ class AvcVideoParser extends BaseVideoParser {
|
|
14338
14531
|
// only check slice type to detect KF in case SPS found in same packet (any keyframe is preceded by SPS ...)
|
14339
14532
|
if (spsfound && data.length > 4) {
|
14340
14533
|
// retrieve slice type by parsing beginning of NAL unit (follow H264 spec, slice_header definition) to detect keyframe embedded in NDR
|
14341
|
-
const sliceType =
|
14534
|
+
const sliceType = this.readSliceType(data);
|
14342
14535
|
// 2 : I slice, 4 : SI slice, 7 : I slice, 9: SI slice
|
14343
14536
|
// SI slice : A slice that is coded using intra prediction only and using quantisation of the prediction samples.
|
14344
14537
|
// An SI slice can be coded such that its decoded samples can be constructed identically to an SP slice.
|
@@ -14392,8 +14585,7 @@ class AvcVideoParser extends BaseVideoParser {
|
|
14392
14585
|
push = true;
|
14393
14586
|
spsfound = true;
|
14394
14587
|
const sps = unit.data;
|
14395
|
-
const
|
14396
|
-
const config = expGolombDecoder.readSPS();
|
14588
|
+
const config = this.readSPS(sps);
|
14397
14589
|
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]) {
|
14398
14590
|
track.width = config.width;
|
14399
14591
|
track.height = config.height;
|
@@ -14449,109 +14641,192 @@ class AvcVideoParser extends BaseVideoParser {
|
|
14449
14641
|
this.VideoSample = null;
|
14450
14642
|
}
|
14451
14643
|
}
|
14452
|
-
|
14453
|
-
|
14454
|
-
|
14455
|
-
|
14456
|
-
const
|
14457
|
-
|
14458
|
-
|
14459
|
-
|
14460
|
-
|
14461
|
-
|
14462
|
-
|
14463
|
-
|
14644
|
+
getNALuType(data, offset) {
|
14645
|
+
return data[offset] & 0x1f;
|
14646
|
+
}
|
14647
|
+
readSliceType(data) {
|
14648
|
+
const eg = new ExpGolomb(data);
|
14649
|
+
// skip NALu type
|
14650
|
+
eg.readUByte();
|
14651
|
+
// discard first_mb_in_slice
|
14652
|
+
eg.readUEG();
|
14653
|
+
// return slice_type
|
14654
|
+
return eg.readUEG();
|
14655
|
+
}
|
14464
14656
|
|
14465
|
-
|
14466
|
-
|
14467
|
-
|
14468
|
-
|
14469
|
-
|
14470
|
-
|
14471
|
-
|
14472
|
-
|
14473
|
-
|
14474
|
-
|
14475
|
-
|
14476
|
-
if (
|
14477
|
-
|
14478
|
-
|
14479
|
-
}
|
14480
|
-
if (state === 1) {
|
14481
|
-
state = value ? 0 : 2;
|
14482
|
-
continue;
|
14657
|
+
/**
|
14658
|
+
* The scaling list is optionally transmitted as part of a sequence parameter
|
14659
|
+
* set and is not relevant to transmuxing.
|
14660
|
+
* @param count the number of entries in this scaling list
|
14661
|
+
* @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
|
14662
|
+
*/
|
14663
|
+
skipScalingList(count, reader) {
|
14664
|
+
let lastScale = 8;
|
14665
|
+
let nextScale = 8;
|
14666
|
+
let deltaScale;
|
14667
|
+
for (let j = 0; j < count; j++) {
|
14668
|
+
if (nextScale !== 0) {
|
14669
|
+
deltaScale = reader.readEG();
|
14670
|
+
nextScale = (lastScale + deltaScale + 256) % 256;
|
14483
14671
|
}
|
14484
|
-
|
14485
|
-
|
14486
|
-
|
14487
|
-
} else if (value === 1) {
|
14488
|
-
overflow = i - state - 1;
|
14489
|
-
if (lastUnitStart >= 0) {
|
14490
|
-
const unit = {
|
14491
|
-
data: array.subarray(lastUnitStart, overflow),
|
14492
|
-
type: lastUnitType
|
14493
|
-
};
|
14494
|
-
// logger.log('pushing NALU, type/size:' + unit.type + '/' + unit.data.byteLength);
|
14495
|
-
units.push(unit);
|
14496
|
-
} else {
|
14497
|
-
// lastUnitStart is undefined => this is the first start code found in this PES packet
|
14498
|
-
// first check if start code delimiter is overlapping between 2 PES packets,
|
14499
|
-
// ie it started in last packet (lastState not zero)
|
14500
|
-
// and ended at the beginning of this PES packet (i <= 4 - lastState)
|
14501
|
-
const lastUnit = this.getLastNalUnit(track.samples);
|
14502
|
-
if (lastUnit) {
|
14503
|
-
if (lastState && i <= 4 - lastState) {
|
14504
|
-
// start delimiter overlapping between PES packets
|
14505
|
-
// strip start delimiter bytes from the end of last NAL unit
|
14506
|
-
// check if lastUnit had a state different from zero
|
14507
|
-
if (lastUnit.state) {
|
14508
|
-
// strip last bytes
|
14509
|
-
lastUnit.data = lastUnit.data.subarray(0, lastUnit.data.byteLength - lastState);
|
14510
|
-
}
|
14511
|
-
}
|
14512
|
-
// If NAL units are not starting right at the beginning of the PES packet, push preceding data into previous NAL unit.
|
14672
|
+
lastScale = nextScale === 0 ? lastScale : nextScale;
|
14673
|
+
}
|
14674
|
+
}
|
14513
14675
|
|
14514
|
-
|
14515
|
-
|
14516
|
-
|
14517
|
-
|
14518
|
-
|
14519
|
-
|
14520
|
-
|
14521
|
-
|
14522
|
-
|
14523
|
-
|
14524
|
-
|
14525
|
-
|
14526
|
-
|
14527
|
-
|
14528
|
-
|
14529
|
-
|
14530
|
-
|
14676
|
+
/**
|
14677
|
+
* Read a sequence parameter set and return some interesting video
|
14678
|
+
* properties. A sequence parameter set is the H264 metadata that
|
14679
|
+
* describes the properties of upcoming video frames.
|
14680
|
+
* @returns an object with configuration parsed from the
|
14681
|
+
* sequence parameter set, including the dimensions of the
|
14682
|
+
* associated video frames.
|
14683
|
+
*/
|
14684
|
+
readSPS(sps) {
|
14685
|
+
const eg = new ExpGolomb(sps);
|
14686
|
+
let frameCropLeftOffset = 0;
|
14687
|
+
let frameCropRightOffset = 0;
|
14688
|
+
let frameCropTopOffset = 0;
|
14689
|
+
let frameCropBottomOffset = 0;
|
14690
|
+
let numRefFramesInPicOrderCntCycle;
|
14691
|
+
let scalingListCount;
|
14692
|
+
let i;
|
14693
|
+
const readUByte = eg.readUByte.bind(eg);
|
14694
|
+
const readBits = eg.readBits.bind(eg);
|
14695
|
+
const readUEG = eg.readUEG.bind(eg);
|
14696
|
+
const readBoolean = eg.readBoolean.bind(eg);
|
14697
|
+
const skipBits = eg.skipBits.bind(eg);
|
14698
|
+
const skipEG = eg.skipEG.bind(eg);
|
14699
|
+
const skipUEG = eg.skipUEG.bind(eg);
|
14700
|
+
const skipScalingList = this.skipScalingList.bind(this);
|
14701
|
+
readUByte();
|
14702
|
+
const profileIdc = readUByte(); // profile_idc
|
14703
|
+
readBits(5); // profileCompat constraint_set[0-4]_flag, u(5)
|
14704
|
+
skipBits(3); // reserved_zero_3bits u(3),
|
14705
|
+
readUByte(); // level_idc u(8)
|
14706
|
+
skipUEG(); // seq_parameter_set_id
|
14707
|
+
// some profiles have more optional data we don't need
|
14708
|
+
if (profileIdc === 100 || profileIdc === 110 || profileIdc === 122 || profileIdc === 244 || profileIdc === 44 || profileIdc === 83 || profileIdc === 86 || profileIdc === 118 || profileIdc === 128) {
|
14709
|
+
const chromaFormatIdc = readUEG();
|
14710
|
+
if (chromaFormatIdc === 3) {
|
14711
|
+
skipBits(1);
|
14712
|
+
} // separate_colour_plane_flag
|
14713
|
+
|
14714
|
+
skipUEG(); // bit_depth_luma_minus8
|
14715
|
+
skipUEG(); // bit_depth_chroma_minus8
|
14716
|
+
skipBits(1); // qpprime_y_zero_transform_bypass_flag
|
14717
|
+
if (readBoolean()) {
|
14718
|
+
// seq_scaling_matrix_present_flag
|
14719
|
+
scalingListCount = chromaFormatIdc !== 3 ? 8 : 12;
|
14720
|
+
for (i = 0; i < scalingListCount; i++) {
|
14721
|
+
if (readBoolean()) {
|
14722
|
+
// seq_scaling_list_present_flag[ i ]
|
14723
|
+
if (i < 6) {
|
14724
|
+
skipScalingList(16, eg);
|
14725
|
+
} else {
|
14726
|
+
skipScalingList(64, eg);
|
14727
|
+
}
|
14728
|
+
}
|
14531
14729
|
}
|
14532
|
-
} else {
|
14533
|
-
state = 0;
|
14534
14730
|
}
|
14535
14731
|
}
|
14536
|
-
|
14537
|
-
|
14538
|
-
|
14539
|
-
|
14540
|
-
|
14541
|
-
|
14542
|
-
|
14543
|
-
|
14732
|
+
skipUEG(); // log2_max_frame_num_minus4
|
14733
|
+
const picOrderCntType = readUEG();
|
14734
|
+
if (picOrderCntType === 0) {
|
14735
|
+
readUEG(); // log2_max_pic_order_cnt_lsb_minus4
|
14736
|
+
} else if (picOrderCntType === 1) {
|
14737
|
+
skipBits(1); // delta_pic_order_always_zero_flag
|
14738
|
+
skipEG(); // offset_for_non_ref_pic
|
14739
|
+
skipEG(); // offset_for_top_to_bottom_field
|
14740
|
+
numRefFramesInPicOrderCntCycle = readUEG();
|
14741
|
+
for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
|
14742
|
+
skipEG();
|
14743
|
+
} // offset_for_ref_frame[ i ]
|
14544
14744
|
}
|
14545
|
-
//
|
14546
|
-
|
14547
|
-
|
14548
|
-
|
14549
|
-
|
14550
|
-
|
14745
|
+
skipUEG(); // max_num_ref_frames
|
14746
|
+
skipBits(1); // gaps_in_frame_num_value_allowed_flag
|
14747
|
+
const picWidthInMbsMinus1 = readUEG();
|
14748
|
+
const picHeightInMapUnitsMinus1 = readUEG();
|
14749
|
+
const frameMbsOnlyFlag = readBits(1);
|
14750
|
+
if (frameMbsOnlyFlag === 0) {
|
14751
|
+
skipBits(1);
|
14752
|
+
} // mb_adaptive_frame_field_flag
|
14753
|
+
|
14754
|
+
skipBits(1); // direct_8x8_inference_flag
|
14755
|
+
if (readBoolean()) {
|
14756
|
+
// frame_cropping_flag
|
14757
|
+
frameCropLeftOffset = readUEG();
|
14758
|
+
frameCropRightOffset = readUEG();
|
14759
|
+
frameCropTopOffset = readUEG();
|
14760
|
+
frameCropBottomOffset = readUEG();
|
14761
|
+
}
|
14762
|
+
let pixelRatio = [1, 1];
|
14763
|
+
if (readBoolean()) {
|
14764
|
+
// vui_parameters_present_flag
|
14765
|
+
if (readBoolean()) {
|
14766
|
+
// aspect_ratio_info_present_flag
|
14767
|
+
const aspectRatioIdc = readUByte();
|
14768
|
+
switch (aspectRatioIdc) {
|
14769
|
+
case 1:
|
14770
|
+
pixelRatio = [1, 1];
|
14771
|
+
break;
|
14772
|
+
case 2:
|
14773
|
+
pixelRatio = [12, 11];
|
14774
|
+
break;
|
14775
|
+
case 3:
|
14776
|
+
pixelRatio = [10, 11];
|
14777
|
+
break;
|
14778
|
+
case 4:
|
14779
|
+
pixelRatio = [16, 11];
|
14780
|
+
break;
|
14781
|
+
case 5:
|
14782
|
+
pixelRatio = [40, 33];
|
14783
|
+
break;
|
14784
|
+
case 6:
|
14785
|
+
pixelRatio = [24, 11];
|
14786
|
+
break;
|
14787
|
+
case 7:
|
14788
|
+
pixelRatio = [20, 11];
|
14789
|
+
break;
|
14790
|
+
case 8:
|
14791
|
+
pixelRatio = [32, 11];
|
14792
|
+
break;
|
14793
|
+
case 9:
|
14794
|
+
pixelRatio = [80, 33];
|
14795
|
+
break;
|
14796
|
+
case 10:
|
14797
|
+
pixelRatio = [18, 11];
|
14798
|
+
break;
|
14799
|
+
case 11:
|
14800
|
+
pixelRatio = [15, 11];
|
14801
|
+
break;
|
14802
|
+
case 12:
|
14803
|
+
pixelRatio = [64, 33];
|
14804
|
+
break;
|
14805
|
+
case 13:
|
14806
|
+
pixelRatio = [160, 99];
|
14807
|
+
break;
|
14808
|
+
case 14:
|
14809
|
+
pixelRatio = [4, 3];
|
14810
|
+
break;
|
14811
|
+
case 15:
|
14812
|
+
pixelRatio = [3, 2];
|
14813
|
+
break;
|
14814
|
+
case 16:
|
14815
|
+
pixelRatio = [2, 1];
|
14816
|
+
break;
|
14817
|
+
case 255:
|
14818
|
+
{
|
14819
|
+
pixelRatio = [readUByte() << 8 | readUByte(), readUByte() << 8 | readUByte()];
|
14820
|
+
break;
|
14821
|
+
}
|
14822
|
+
}
|
14551
14823
|
}
|
14552
14824
|
}
|
14553
|
-
|
14554
|
-
|
14825
|
+
return {
|
14826
|
+
width: Math.ceil((picWidthInMbsMinus1 + 1) * 16 - frameCropLeftOffset * 2 - frameCropRightOffset * 2),
|
14827
|
+
height: (2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16 - (frameMbsOnlyFlag ? 2 : 4) * (frameCropTopOffset + frameCropBottomOffset),
|
14828
|
+
pixelRatio: pixelRatio
|
14829
|
+
};
|
14555
14830
|
}
|
14556
14831
|
}
|
14557
14832
|
|
@@ -14569,7 +14844,7 @@ class SampleAesDecrypter {
|
|
14569
14844
|
});
|
14570
14845
|
}
|
14571
14846
|
decryptBuffer(encryptedData) {
|
14572
|
-
return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer);
|
14847
|
+
return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer, DecrypterAesMode.cbc);
|
14573
14848
|
}
|
14574
14849
|
|
14575
14850
|
// AAC - encrypt all full 16 bytes blocks starting from offset 16
|
@@ -14683,7 +14958,7 @@ class TSDemuxer {
|
|
14683
14958
|
this.observer = observer;
|
14684
14959
|
this.config = config;
|
14685
14960
|
this.typeSupported = typeSupported;
|
14686
|
-
this.videoParser =
|
14961
|
+
this.videoParser = null;
|
14687
14962
|
}
|
14688
14963
|
static probe(data) {
|
14689
14964
|
const syncOffset = TSDemuxer.syncOffset(data);
|
@@ -14848,7 +15123,16 @@ class TSDemuxer {
|
|
14848
15123
|
case videoPid:
|
14849
15124
|
if (stt) {
|
14850
15125
|
if (videoData && (pes = parsePES(videoData))) {
|
14851
|
-
this.videoParser
|
15126
|
+
if (this.videoParser === null) {
|
15127
|
+
switch (videoTrack.segmentCodec) {
|
15128
|
+
case 'avc':
|
15129
|
+
this.videoParser = new AvcVideoParser();
|
15130
|
+
break;
|
15131
|
+
}
|
15132
|
+
}
|
15133
|
+
if (this.videoParser !== null) {
|
15134
|
+
this.videoParser.parsePES(videoTrack, textTrack, pes, false, this._duration);
|
15135
|
+
}
|
14852
15136
|
}
|
14853
15137
|
videoData = {
|
14854
15138
|
data: [],
|
@@ -15010,8 +15294,17 @@ class TSDemuxer {
|
|
15010
15294
|
// try to parse last PES packets
|
15011
15295
|
let pes;
|
15012
15296
|
if (videoData && (pes = parsePES(videoData))) {
|
15013
|
-
this.videoParser
|
15014
|
-
|
15297
|
+
if (this.videoParser === null) {
|
15298
|
+
switch (videoTrack.segmentCodec) {
|
15299
|
+
case 'avc':
|
15300
|
+
this.videoParser = new AvcVideoParser();
|
15301
|
+
break;
|
15302
|
+
}
|
15303
|
+
}
|
15304
|
+
if (this.videoParser !== null) {
|
15305
|
+
this.videoParser.parsePES(videoTrack, textTrack, pes, true, this._duration);
|
15306
|
+
videoTrack.pesData = null;
|
15307
|
+
}
|
15015
15308
|
} else {
|
15016
15309
|
// either avcData null or PES truncated, keep it for next frag parsing
|
15017
15310
|
videoTrack.pesData = videoData;
|
@@ -15314,7 +15607,10 @@ function parsePMT(data, offset, typeSupported, isSampleAes) {
|
|
15314
15607
|
logger.warn('Unsupported EC-3 in M2TS found');
|
15315
15608
|
break;
|
15316
15609
|
case 0x24:
|
15317
|
-
|
15610
|
+
// ITU-T Rec. H.265 and ISO/IEC 23008-2 (HEVC)
|
15611
|
+
{
|
15612
|
+
logger.warn('Unsupported HEVC in M2TS found');
|
15613
|
+
}
|
15318
15614
|
break;
|
15319
15615
|
}
|
15320
15616
|
// move to the next table entry
|
@@ -15537,6 +15833,8 @@ class MP4 {
|
|
15537
15833
|
avc1: [],
|
15538
15834
|
// codingname
|
15539
15835
|
avcC: [],
|
15836
|
+
hvc1: [],
|
15837
|
+
hvcC: [],
|
15540
15838
|
btrt: [],
|
15541
15839
|
dinf: [],
|
15542
15840
|
dref: [],
|
@@ -15961,8 +16259,10 @@ class MP4 {
|
|
15961
16259
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.ac3(track));
|
15962
16260
|
}
|
15963
16261
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.mp4a(track));
|
15964
|
-
} else {
|
16262
|
+
} else if (track.segmentCodec === 'avc') {
|
15965
16263
|
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.avc1(track));
|
16264
|
+
} else {
|
16265
|
+
return MP4.box(MP4.types.stsd, MP4.STSD, MP4.hvc1(track));
|
15966
16266
|
}
|
15967
16267
|
}
|
15968
16268
|
static tkhd(track) {
|
@@ -16100,6 +16400,84 @@ class MP4 {
|
|
16100
16400
|
const result = appendUint8Array(MP4.FTYP, movie);
|
16101
16401
|
return result;
|
16102
16402
|
}
|
16403
|
+
static hvc1(track) {
|
16404
|
+
const ps = track.params;
|
16405
|
+
const units = [track.vps, track.sps, track.pps];
|
16406
|
+
const NALuLengthSize = 4;
|
16407
|
+
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]);
|
16408
|
+
|
16409
|
+
// compute hvcC size in bytes
|
16410
|
+
let length = config.length;
|
16411
|
+
for (let i = 0; i < units.length; i += 1) {
|
16412
|
+
length += 3;
|
16413
|
+
for (let j = 0; j < units[i].length; j += 1) {
|
16414
|
+
length += 2 + units[i][j].length;
|
16415
|
+
}
|
16416
|
+
}
|
16417
|
+
const hvcC = new Uint8Array(length);
|
16418
|
+
hvcC.set(config, 0);
|
16419
|
+
length = config.length;
|
16420
|
+
// append parameter set units: one vps, one or more sps and pps
|
16421
|
+
const iMax = units.length - 1;
|
16422
|
+
for (let i = 0; i < units.length; i += 1) {
|
16423
|
+
hvcC.set(new Uint8Array([32 + i | (i === iMax ? 128 : 0), 0x00, units[i].length]), length);
|
16424
|
+
length += 3;
|
16425
|
+
for (let j = 0; j < units[i].length; j += 1) {
|
16426
|
+
hvcC.set(new Uint8Array([units[i][j].length >> 8, units[i][j].length & 255]), length);
|
16427
|
+
length += 2;
|
16428
|
+
hvcC.set(units[i][j], length);
|
16429
|
+
length += units[i][j].length;
|
16430
|
+
}
|
16431
|
+
}
|
16432
|
+
const hvcc = MP4.box(MP4.types.hvcC, hvcC);
|
16433
|
+
const width = track.width;
|
16434
|
+
const height = track.height;
|
16435
|
+
const hSpacing = track.pixelRatio[0];
|
16436
|
+
const vSpacing = track.pixelRatio[1];
|
16437
|
+
return MP4.box(MP4.types.hvc1, new Uint8Array([0x00, 0x00, 0x00,
|
16438
|
+
// reserved
|
16439
|
+
0x00, 0x00, 0x00,
|
16440
|
+
// reserved
|
16441
|
+
0x00, 0x01,
|
16442
|
+
// data_reference_index
|
16443
|
+
0x00, 0x00,
|
16444
|
+
// pre_defined
|
16445
|
+
0x00, 0x00,
|
16446
|
+
// reserved
|
16447
|
+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
16448
|
+
// pre_defined
|
16449
|
+
width >> 8 & 0xff, width & 0xff,
|
16450
|
+
// width
|
16451
|
+
height >> 8 & 0xff, height & 0xff,
|
16452
|
+
// height
|
16453
|
+
0x00, 0x48, 0x00, 0x00,
|
16454
|
+
// horizresolution
|
16455
|
+
0x00, 0x48, 0x00, 0x00,
|
16456
|
+
// vertresolution
|
16457
|
+
0x00, 0x00, 0x00, 0x00,
|
16458
|
+
// reserved
|
16459
|
+
0x00, 0x01,
|
16460
|
+
// frame_count
|
16461
|
+
0x12, 0x64, 0x61, 0x69, 0x6c,
|
16462
|
+
// dailymotion/hls.js
|
16463
|
+
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,
|
16464
|
+
// compressorname
|
16465
|
+
0x00, 0x18,
|
16466
|
+
// depth = 24
|
16467
|
+
0x11, 0x11]),
|
16468
|
+
// pre_defined = -1
|
16469
|
+
hvcc, MP4.box(MP4.types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80,
|
16470
|
+
// bufferSizeDB
|
16471
|
+
0x00, 0x2d, 0xc6, 0xc0,
|
16472
|
+
// maxBitrate
|
16473
|
+
0x00, 0x2d, 0xc6, 0xc0])),
|
16474
|
+
// avgBitrate
|
16475
|
+
MP4.box(MP4.types.pasp, new Uint8Array([hSpacing >> 24,
|
16476
|
+
// hSpacing
|
16477
|
+
hSpacing >> 16 & 0xff, hSpacing >> 8 & 0xff, hSpacing & 0xff, vSpacing >> 24,
|
16478
|
+
// vSpacing
|
16479
|
+
vSpacing >> 16 & 0xff, vSpacing >> 8 & 0xff, vSpacing & 0xff])));
|
16480
|
+
}
|
16103
16481
|
}
|
16104
16482
|
MP4.types = void 0;
|
16105
16483
|
MP4.HDLR_TYPES = void 0;
|
@@ -16475,9 +16853,9 @@ class MP4Remuxer {
|
|
16475
16853
|
const foundOverlap = delta < -1;
|
16476
16854
|
if (foundHole || foundOverlap) {
|
16477
16855
|
if (foundHole) {
|
16478
|
-
logger.warn(
|
16856
|
+
logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(delta, true)} ms (${delta}dts) hole between fragments detected at ${timeOffset.toFixed(3)}`);
|
16479
16857
|
} else {
|
16480
|
-
logger.warn(
|
16858
|
+
logger.warn(`${(track.segmentCodec || '').toUpperCase()}: ${toMsFromMpegTsClock(-delta, true)} ms (${delta}dts) overlapping between fragments detected at ${timeOffset.toFixed(3)}`);
|
16481
16859
|
}
|
16482
16860
|
if (!foundOverlap || nextAvcDts >= inputSamples[0].pts || chromeVersion) {
|
16483
16861
|
firstDTS = nextAvcDts;
|
@@ -16486,12 +16864,24 @@ class MP4Remuxer {
|
|
16486
16864
|
inputSamples[0].dts = firstDTS;
|
16487
16865
|
inputSamples[0].pts = firstPTS;
|
16488
16866
|
} else {
|
16867
|
+
let isPTSOrderRetained = true;
|
16489
16868
|
for (let i = 0; i < inputSamples.length; i++) {
|
16490
|
-
if (inputSamples[i].dts > firstPTS) {
|
16869
|
+
if (inputSamples[i].dts > firstPTS && isPTSOrderRetained) {
|
16491
16870
|
break;
|
16492
16871
|
}
|
16872
|
+
const prevPTS = inputSamples[i].pts;
|
16493
16873
|
inputSamples[i].dts -= delta;
|
16494
16874
|
inputSamples[i].pts -= delta;
|
16875
|
+
|
16876
|
+
// check to see if this sample's PTS order has changed
|
16877
|
+
// relative to the next one
|
16878
|
+
if (i < inputSamples.length - 1) {
|
16879
|
+
const nextSamplePTS = inputSamples[i + 1].pts;
|
16880
|
+
const currentSamplePTS = inputSamples[i].pts;
|
16881
|
+
const currentOrder = nextSamplePTS <= currentSamplePTS;
|
16882
|
+
const prevOrder = nextSamplePTS <= prevPTS;
|
16883
|
+
isPTSOrderRetained = currentOrder == prevOrder;
|
16884
|
+
}
|
16495
16885
|
}
|
16496
16886
|
}
|
16497
16887
|
logger.log(`Video: Initial PTS/DTS adjusted: ${toMsFromMpegTsClock(firstPTS, true)}/${toMsFromMpegTsClock(firstDTS, true)}, delta: ${toMsFromMpegTsClock(delta, true)} ms`);
|
@@ -16639,7 +17029,7 @@ class MP4Remuxer {
|
|
16639
17029
|
}
|
16640
17030
|
}
|
16641
17031
|
}
|
16642
|
-
// next AVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
|
17032
|
+
// next AVC/HEVC sample DTS should be equal to last sample DTS + last sample duration (in PES timescale)
|
16643
17033
|
mp4SampleDuration = stretchedLastFrame || !mp4SampleDuration ? averageSampleDuration : mp4SampleDuration;
|
16644
17034
|
this.nextAvcDts = nextAvcDts = lastDTS + mp4SampleDuration;
|
16645
17035
|
this.videoSampleDuration = mp4SampleDuration;
|
@@ -16772,7 +17162,7 @@ class MP4Remuxer {
|
|
16772
17162
|
logger.warn(`[mp4-remuxer]: Injecting ${missing} audio frame @ ${(nextPts / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`);
|
16773
17163
|
for (let j = 0; j < missing; j++) {
|
16774
17164
|
const newStamp = Math.max(nextPts, 0);
|
16775
|
-
let fillFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
|
17165
|
+
let fillFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
|
16776
17166
|
if (!fillFrame) {
|
16777
17167
|
logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.');
|
16778
17168
|
fillFrame = sample.unit.subarray();
|
@@ -16900,7 +17290,7 @@ class MP4Remuxer {
|
|
16900
17290
|
// samples count of this segment's duration
|
16901
17291
|
const nbSamples = Math.ceil((endDTS - startDTS) / frameDuration);
|
16902
17292
|
// silent frame
|
16903
|
-
const silentFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
|
17293
|
+
const silentFrame = AAC.getSilentFrame(track.parsedCodec || track.manifestCodec || track.codec, track.channelCount);
|
16904
17294
|
logger.warn('[mp4-remuxer]: remux empty Audio');
|
16905
17295
|
// Can't remux if we can't generate a silent frame...
|
16906
17296
|
if (!silentFrame) {
|
@@ -17291,13 +17681,15 @@ class Transmuxer {
|
|
17291
17681
|
initSegmentData
|
17292
17682
|
} = transmuxConfig;
|
17293
17683
|
const keyData = getEncryptionType(uintData, decryptdata);
|
17294
|
-
if (keyData && keyData.method
|
17684
|
+
if (keyData && isFullSegmentEncryption(keyData.method)) {
|
17295
17685
|
const decrypter = this.getDecrypter();
|
17686
|
+
const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
|
17687
|
+
|
17296
17688
|
// Software decryption is synchronous; webCrypto is not
|
17297
17689
|
if (decrypter.isSync()) {
|
17298
17690
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
17299
17691
|
// data is handled in the flush() call
|
17300
|
-
let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer);
|
17692
|
+
let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode);
|
17301
17693
|
// For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
|
17302
17694
|
const loadingParts = chunkMeta.part > -1;
|
17303
17695
|
if (loadingParts) {
|
@@ -17309,7 +17701,7 @@ class Transmuxer {
|
|
17309
17701
|
}
|
17310
17702
|
uintData = new Uint8Array(decryptedData);
|
17311
17703
|
} else {
|
17312
|
-
this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer).then(decryptedData => {
|
17704
|
+
this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer, aesMode).then(decryptedData => {
|
17313
17705
|
// Calling push here is important; if flush() is called while this is still resolving, this ensures that
|
17314
17706
|
// the decrypted data has been transmuxed
|
17315
17707
|
const result = this.push(decryptedData, null, chunkMeta);
|
@@ -17963,14 +18355,7 @@ class TransmuxerInterface {
|
|
17963
18355
|
this.observer = new EventEmitter();
|
17964
18356
|
this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
|
17965
18357
|
this.observer.on(Events.ERROR, forwardMessage);
|
17966
|
-
const
|
17967
|
-
isTypeSupported: () => false
|
17968
|
-
};
|
17969
|
-
const m2tsTypeSupported = {
|
17970
|
-
mpeg: MediaSource.isTypeSupported('audio/mpeg'),
|
17971
|
-
mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
|
17972
|
-
ac3: false
|
17973
|
-
};
|
18358
|
+
const m2tsTypeSupported = getM2TSSupportedAudioTypes(config.preferManagedMediaSource);
|
17974
18359
|
|
17975
18360
|
// navigator.vendor is not always available in Web Worker
|
17976
18361
|
// refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
|
@@ -18234,8 +18619,9 @@ const STALL_MINIMUM_DURATION_MS = 250;
|
|
18234
18619
|
const MAX_START_GAP_JUMP = 2.0;
|
18235
18620
|
const SKIP_BUFFER_HOLE_STEP_SECONDS = 0.1;
|
18236
18621
|
const SKIP_BUFFER_RANGE_START = 0.05;
|
18237
|
-
class GapController {
|
18622
|
+
class GapController extends Logger {
|
18238
18623
|
constructor(config, media, fragmentTracker, hls) {
|
18624
|
+
super('gap-controller', hls.logger);
|
18239
18625
|
this.config = void 0;
|
18240
18626
|
this.media = null;
|
18241
18627
|
this.fragmentTracker = void 0;
|
@@ -18245,6 +18631,7 @@ class GapController {
|
|
18245
18631
|
this.stalled = null;
|
18246
18632
|
this.moved = false;
|
18247
18633
|
this.seeking = false;
|
18634
|
+
this.ended = 0;
|
18248
18635
|
this.config = config;
|
18249
18636
|
this.media = media;
|
18250
18637
|
this.fragmentTracker = fragmentTracker;
|
@@ -18262,7 +18649,7 @@ class GapController {
|
|
18262
18649
|
*
|
18263
18650
|
* @param lastCurrentTime - Previously read playhead position
|
18264
18651
|
*/
|
18265
|
-
poll(lastCurrentTime, activeFrag) {
|
18652
|
+
poll(lastCurrentTime, activeFrag, levelDetails, state) {
|
18266
18653
|
const {
|
18267
18654
|
config,
|
18268
18655
|
media,
|
@@ -18281,6 +18668,7 @@ class GapController {
|
|
18281
18668
|
|
18282
18669
|
// The playhead is moving, no-op
|
18283
18670
|
if (currentTime !== lastCurrentTime) {
|
18671
|
+
this.ended = 0;
|
18284
18672
|
this.moved = true;
|
18285
18673
|
if (!seeking) {
|
18286
18674
|
this.nudgeRetry = 0;
|
@@ -18289,7 +18677,7 @@ class GapController {
|
|
18289
18677
|
// The playhead is now moving, but was previously stalled
|
18290
18678
|
if (this.stallReported) {
|
18291
18679
|
const _stalledDuration = self.performance.now() - stalled;
|
18292
|
-
|
18680
|
+
this.warn(`playback not stuck anymore @${currentTime}, after ${Math.round(_stalledDuration)}ms`);
|
18293
18681
|
this.stallReported = false;
|
18294
18682
|
}
|
18295
18683
|
this.stalled = null;
|
@@ -18325,7 +18713,6 @@ class GapController {
|
|
18325
18713
|
// Skip start gaps if we haven't played, but the last poll detected the start of a stall
|
18326
18714
|
// The addition poll gives the browser a chance to jump the gap for us
|
18327
18715
|
if (!this.moved && this.stalled !== null) {
|
18328
|
-
var _level$details;
|
18329
18716
|
// There is no playable buffer (seeked, waiting for buffer)
|
18330
18717
|
const isBuffered = bufferInfo.len > 0;
|
18331
18718
|
if (!isBuffered && !nextStart) {
|
@@ -18337,9 +18724,8 @@ class GapController {
|
|
18337
18724
|
// When joining a live stream with audio tracks, account for live playlist window sliding by allowing
|
18338
18725
|
// a larger jump over start gaps caused by the audio-stream-controller buffering a start fragment
|
18339
18726
|
// that begins over 1 target duration after the video start position.
|
18340
|
-
const
|
18341
|
-
const
|
18342
|
-
const maxStartGapJump = isLive ? level.details.targetduration * 2 : MAX_START_GAP_JUMP;
|
18727
|
+
const isLive = !!(levelDetails != null && levelDetails.live);
|
18728
|
+
const maxStartGapJump = isLive ? levelDetails.targetduration * 2 : MAX_START_GAP_JUMP;
|
18343
18729
|
const partialOrGap = this.fragmentTracker.getPartialFragment(currentTime);
|
18344
18730
|
if (startJump > 0 && (startJump <= maxStartGapJump || partialOrGap)) {
|
18345
18731
|
if (!media.paused) {
|
@@ -18357,6 +18743,17 @@ class GapController {
|
|
18357
18743
|
}
|
18358
18744
|
const stalledDuration = tnow - stalled;
|
18359
18745
|
if (!seeking && stalledDuration >= STALL_MINIMUM_DURATION_MS) {
|
18746
|
+
// Dispatch MEDIA_ENDED when media.ended/ended event is not signalled at end of stream
|
18747
|
+
if (state === State.ENDED && !(levelDetails && levelDetails.live) && Math.abs(currentTime - ((levelDetails == null ? void 0 : levelDetails.edge) || 0)) < 1) {
|
18748
|
+
if (stalledDuration < 1000 || this.ended) {
|
18749
|
+
return;
|
18750
|
+
}
|
18751
|
+
this.ended = currentTime;
|
18752
|
+
this.hls.trigger(Events.MEDIA_ENDED, {
|
18753
|
+
stalled: true
|
18754
|
+
});
|
18755
|
+
return;
|
18756
|
+
}
|
18360
18757
|
// Report stalling after trying to fix
|
18361
18758
|
this._reportStall(bufferInfo);
|
18362
18759
|
if (!this.media) {
|
@@ -18400,7 +18797,7 @@ class GapController {
|
|
18400
18797
|
// needs to cross some sort of threshold covering all source-buffers content
|
18401
18798
|
// to start playing properly.
|
18402
18799
|
if ((bufferInfo.len > config.maxBufferHole || bufferInfo.nextStart && bufferInfo.nextStart - currentTime < config.maxBufferHole) && stalledDurationMs > config.highBufferWatchdogPeriod * 1000) {
|
18403
|
-
|
18800
|
+
this.warn('Trying to nudge playhead over buffer-hole');
|
18404
18801
|
// Try to nudge currentTime over a buffer hole if we've been stalling for the configured amount of seconds
|
18405
18802
|
// We only try to jump the hole if it's under the configured size
|
18406
18803
|
// Reset stalled so to rearm watchdog timer
|
@@ -18424,7 +18821,7 @@ class GapController {
|
|
18424
18821
|
// Report stalled error once
|
18425
18822
|
this.stallReported = true;
|
18426
18823
|
const error = new Error(`Playback stalling at @${media.currentTime} due to low buffer (${JSON.stringify(bufferInfo)})`);
|
18427
|
-
|
18824
|
+
this.warn(error.message);
|
18428
18825
|
hls.trigger(Events.ERROR, {
|
18429
18826
|
type: ErrorTypes.MEDIA_ERROR,
|
18430
18827
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
@@ -18492,7 +18889,7 @@ class GapController {
|
|
18492
18889
|
}
|
18493
18890
|
}
|
18494
18891
|
const targetTime = Math.max(startTime + SKIP_BUFFER_RANGE_START, currentTime + SKIP_BUFFER_HOLE_STEP_SECONDS);
|
18495
|
-
|
18892
|
+
this.warn(`skipping hole, adjusting currentTime from ${currentTime} to ${targetTime}`);
|
18496
18893
|
this.moved = true;
|
18497
18894
|
this.stalled = null;
|
18498
18895
|
media.currentTime = targetTime;
|
@@ -18533,7 +18930,7 @@ class GapController {
|
|
18533
18930
|
const targetTime = currentTime + (nudgeRetry + 1) * config.nudgeOffset;
|
18534
18931
|
// playback stalled in buffered area ... let's nudge currentTime to try to overcome this
|
18535
18932
|
const error = new Error(`Nudging 'currentTime' from ${currentTime} to ${targetTime}`);
|
18536
|
-
|
18933
|
+
this.warn(error.message);
|
18537
18934
|
media.currentTime = targetTime;
|
18538
18935
|
hls.trigger(Events.ERROR, {
|
18539
18936
|
type: ErrorTypes.MEDIA_ERROR,
|
@@ -18543,7 +18940,7 @@ class GapController {
|
|
18543
18940
|
});
|
18544
18941
|
} else {
|
18545
18942
|
const error = new Error(`Playhead still not moving while enough data buffered @${currentTime} after ${config.nudgeMaxRetry} nudges`);
|
18546
|
-
|
18943
|
+
this.error(error.message);
|
18547
18944
|
hls.trigger(Events.ERROR, {
|
18548
18945
|
type: ErrorTypes.MEDIA_ERROR,
|
18549
18946
|
details: ErrorDetails.BUFFER_STALLED_ERROR,
|
@@ -18558,7 +18955,7 @@ const TICK_INTERVAL = 100; // how often to tick in ms
|
|
18558
18955
|
|
18559
18956
|
class StreamController extends BaseStreamController {
|
18560
18957
|
constructor(hls, fragmentTracker, keyLoader) {
|
18561
|
-
super(hls, fragmentTracker, keyLoader, '
|
18958
|
+
super(hls, fragmentTracker, keyLoader, 'stream-controller', PlaylistLevelType.MAIN);
|
18562
18959
|
this.audioCodecSwap = false;
|
18563
18960
|
this.gapController = null;
|
18564
18961
|
this.level = -1;
|
@@ -18566,27 +18963,43 @@ class StreamController extends BaseStreamController {
|
|
18566
18963
|
this.altAudio = false;
|
18567
18964
|
this.audioOnly = false;
|
18568
18965
|
this.fragPlaying = null;
|
18569
|
-
this.onvplaying = null;
|
18570
|
-
this.onvseeked = null;
|
18571
18966
|
this.fragLastKbps = 0;
|
18572
18967
|
this.couldBacktrack = false;
|
18573
18968
|
this.backtrackFragment = null;
|
18574
18969
|
this.audioCodecSwitch = false;
|
18575
18970
|
this.videoBuffer = null;
|
18576
|
-
this.
|
18971
|
+
this.onMediaPlaying = () => {
|
18972
|
+
// tick to speed up FRAG_CHANGED triggering
|
18973
|
+
this.tick();
|
18974
|
+
};
|
18975
|
+
this.onMediaSeeked = () => {
|
18976
|
+
const media = this.media;
|
18977
|
+
const currentTime = media ? media.currentTime : null;
|
18978
|
+
if (isFiniteNumber(currentTime)) {
|
18979
|
+
this.log(`Media seeked to ${currentTime.toFixed(3)}`);
|
18980
|
+
}
|
18981
|
+
|
18982
|
+
// If seeked was issued before buffer was appended do not tick immediately
|
18983
|
+
const bufferInfo = this.getMainFwdBufferInfo();
|
18984
|
+
if (bufferInfo === null || bufferInfo.len === 0) {
|
18985
|
+
this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
|
18986
|
+
return;
|
18987
|
+
}
|
18988
|
+
|
18989
|
+
// tick to speed up FRAG_CHANGED triggering
|
18990
|
+
this.tick();
|
18991
|
+
};
|
18992
|
+
this.registerListeners();
|
18577
18993
|
}
|
18578
|
-
|
18994
|
+
registerListeners() {
|
18995
|
+
super.registerListeners();
|
18579
18996
|
const {
|
18580
18997
|
hls
|
18581
18998
|
} = this;
|
18582
|
-
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
18583
|
-
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
18584
|
-
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
18585
18999
|
hls.on(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
18586
19000
|
hls.on(Events.LEVEL_LOADING, this.onLevelLoading, this);
|
18587
19001
|
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
18588
19002
|
hls.on(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
|
18589
|
-
hls.on(Events.ERROR, this.onError, this);
|
18590
19003
|
hls.on(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
18591
19004
|
hls.on(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
18592
19005
|
hls.on(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -18594,17 +19007,14 @@ class StreamController extends BaseStreamController {
|
|
18594
19007
|
hls.on(Events.LEVELS_UPDATED, this.onLevelsUpdated, this);
|
18595
19008
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
18596
19009
|
}
|
18597
|
-
|
19010
|
+
unregisterListeners() {
|
19011
|
+
super.unregisterListeners();
|
18598
19012
|
const {
|
18599
19013
|
hls
|
18600
19014
|
} = this;
|
18601
|
-
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
18602
|
-
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
18603
|
-
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
18604
19015
|
hls.off(Events.MANIFEST_PARSED, this.onManifestParsed, this);
|
18605
19016
|
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
|
18606
19017
|
hls.off(Events.FRAG_LOAD_EMERGENCY_ABORTED, this.onFragLoadEmergencyAborted, this);
|
18607
|
-
hls.off(Events.ERROR, this.onError, this);
|
18608
19018
|
hls.off(Events.AUDIO_TRACK_SWITCHING, this.onAudioTrackSwitching, this);
|
18609
19019
|
hls.off(Events.AUDIO_TRACK_SWITCHED, this.onAudioTrackSwitched, this);
|
18610
19020
|
hls.off(Events.BUFFER_CREATED, this.onBufferCreated, this);
|
@@ -18613,7 +19023,9 @@ class StreamController extends BaseStreamController {
|
|
18613
19023
|
hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
18614
19024
|
}
|
18615
19025
|
onHandlerDestroying() {
|
18616
|
-
|
19026
|
+
// @ts-ignore
|
19027
|
+
this.onMediaPlaying = this.onMediaSeeked = null;
|
19028
|
+
this.unregisterListeners();
|
18617
19029
|
super.onHandlerDestroying();
|
18618
19030
|
}
|
18619
19031
|
startLoad(startPosition) {
|
@@ -18711,6 +19123,9 @@ class StreamController extends BaseStreamController {
|
|
18711
19123
|
this.checkFragmentChanged();
|
18712
19124
|
}
|
18713
19125
|
doTickIdle() {
|
19126
|
+
if (!this.buffering) {
|
19127
|
+
return;
|
19128
|
+
}
|
18714
19129
|
const {
|
18715
19130
|
hls,
|
18716
19131
|
levelLastLoaded,
|
@@ -18938,20 +19353,17 @@ class StreamController extends BaseStreamController {
|
|
18938
19353
|
onMediaAttached(event, data) {
|
18939
19354
|
super.onMediaAttached(event, data);
|
18940
19355
|
const media = data.media;
|
18941
|
-
|
18942
|
-
|
18943
|
-
media.addEventListener('playing', this.onvplaying);
|
18944
|
-
media.addEventListener('seeked', this.onvseeked);
|
19356
|
+
media.addEventListener('playing', this.onMediaPlaying);
|
19357
|
+
media.addEventListener('seeked', this.onMediaSeeked);
|
18945
19358
|
this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
|
18946
19359
|
}
|
18947
19360
|
onMediaDetaching() {
|
18948
19361
|
const {
|
18949
19362
|
media
|
18950
19363
|
} = this;
|
18951
|
-
if (media
|
18952
|
-
media.removeEventListener('playing', this.
|
18953
|
-
media.removeEventListener('seeked', this.
|
18954
|
-
this.onvplaying = this.onvseeked = null;
|
19364
|
+
if (media) {
|
19365
|
+
media.removeEventListener('playing', this.onMediaPlaying);
|
19366
|
+
media.removeEventListener('seeked', this.onMediaSeeked);
|
18955
19367
|
this.videoBuffer = null;
|
18956
19368
|
}
|
18957
19369
|
this.fragPlaying = null;
|
@@ -18961,27 +19373,6 @@ class StreamController extends BaseStreamController {
|
|
18961
19373
|
}
|
18962
19374
|
super.onMediaDetaching();
|
18963
19375
|
}
|
18964
|
-
onMediaPlaying() {
|
18965
|
-
// tick to speed up FRAG_CHANGED triggering
|
18966
|
-
this.tick();
|
18967
|
-
}
|
18968
|
-
onMediaSeeked() {
|
18969
|
-
const media = this.media;
|
18970
|
-
const currentTime = media ? media.currentTime : null;
|
18971
|
-
if (isFiniteNumber(currentTime)) {
|
18972
|
-
this.log(`Media seeked to ${currentTime.toFixed(3)}`);
|
18973
|
-
}
|
18974
|
-
|
18975
|
-
// If seeked was issued before buffer was appended do not tick immediately
|
18976
|
-
const bufferInfo = this.getMainFwdBufferInfo();
|
18977
|
-
if (bufferInfo === null || bufferInfo.len === 0) {
|
18978
|
-
this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
|
18979
|
-
return;
|
18980
|
-
}
|
18981
|
-
|
18982
|
-
// tick to speed up FRAG_CHANGED triggering
|
18983
|
-
this.tick();
|
18984
|
-
}
|
18985
19376
|
onManifestLoading() {
|
18986
19377
|
// reset buffer on manifest loading
|
18987
19378
|
this.log('Trigger BUFFER_RESET');
|
@@ -19273,8 +19664,10 @@ class StreamController extends BaseStreamController {
|
|
19273
19664
|
}
|
19274
19665
|
if (this.loadedmetadata || !BufferHelper.getBuffered(media).length) {
|
19275
19666
|
// Resolve gaps using the main buffer, whose ranges are the intersections of the A/V sourcebuffers
|
19276
|
-
const
|
19277
|
-
|
19667
|
+
const state = this.state;
|
19668
|
+
const activeFrag = state !== State.IDLE ? this.fragCurrent : null;
|
19669
|
+
const levelDetails = this.getLevelDetails();
|
19670
|
+
gapController.poll(this.lastCurrentTime, activeFrag, levelDetails, state);
|
19278
19671
|
}
|
19279
19672
|
this.lastCurrentTime = media.currentTime;
|
19280
19673
|
}
|
@@ -19607,6 +20000,17 @@ class StreamController extends BaseStreamController {
|
|
19607
20000
|
getMainFwdBufferInfo() {
|
19608
20001
|
return this.getFwdBufferInfo(this.mediaBuffer ? this.mediaBuffer : this.media, PlaylistLevelType.MAIN);
|
19609
20002
|
}
|
20003
|
+
get maxBufferLength() {
|
20004
|
+
const {
|
20005
|
+
levels,
|
20006
|
+
level
|
20007
|
+
} = this;
|
20008
|
+
const levelInfo = levels == null ? void 0 : levels[level];
|
20009
|
+
if (!levelInfo) {
|
20010
|
+
return this.config.maxBufferLength;
|
20011
|
+
}
|
20012
|
+
return this.getMaxBufferLength(levelInfo.maxBitrate);
|
20013
|
+
}
|
19610
20014
|
backtrack(frag) {
|
19611
20015
|
this.couldBacktrack = true;
|
19612
20016
|
// Causes findFragments to backtrack through fragments to find the keyframe
|
@@ -19712,7 +20116,7 @@ class Hls {
|
|
19712
20116
|
* Get the video-dev/hls.js package version.
|
19713
20117
|
*/
|
19714
20118
|
static get version() {
|
19715
|
-
return "1.5.
|
20119
|
+
return "1.5.8-0.canary.10046";
|
19716
20120
|
}
|
19717
20121
|
|
19718
20122
|
/**
|
@@ -19775,9 +20179,12 @@ class Hls {
|
|
19775
20179
|
* The configuration object provided on player instantiation.
|
19776
20180
|
*/
|
19777
20181
|
this.userConfig = void 0;
|
20182
|
+
/**
|
20183
|
+
* The logger functions used by this player instance, configured on player instantiation.
|
20184
|
+
*/
|
20185
|
+
this.logger = void 0;
|
19778
20186
|
this.coreComponents = void 0;
|
19779
20187
|
this.networkControllers = void 0;
|
19780
|
-
this.started = false;
|
19781
20188
|
this._emitter = new EventEmitter();
|
19782
20189
|
this._autoLevelCapping = -1;
|
19783
20190
|
this._maxHdcpLevel = null;
|
@@ -19794,11 +20201,11 @@ class Hls {
|
|
19794
20201
|
this._media = null;
|
19795
20202
|
this.url = null;
|
19796
20203
|
this.triggeringException = void 0;
|
19797
|
-
enableLogs(userConfig.debug || false, 'Hls instance');
|
19798
|
-
const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig);
|
20204
|
+
const logger = this.logger = enableLogs(userConfig.debug || false, 'Hls instance');
|
20205
|
+
const config = this.config = mergeConfig(Hls.DefaultConfig, userConfig, logger);
|
19799
20206
|
this.userConfig = userConfig;
|
19800
20207
|
if (config.progressive) {
|
19801
|
-
enableStreamingMode(config);
|
20208
|
+
enableStreamingMode(config, logger);
|
19802
20209
|
}
|
19803
20210
|
|
19804
20211
|
// core controllers and network loaders
|
@@ -19811,7 +20218,9 @@ class Hls {
|
|
19811
20218
|
} = config;
|
19812
20219
|
const errorController = new ConfigErrorController(this);
|
19813
20220
|
const abrController = this.abrController = new ConfigAbrController(this);
|
19814
|
-
|
20221
|
+
// FragmentTracker must be defined before StreamController because the order of event handling is important
|
20222
|
+
const fragmentTracker = new FragmentTracker(this);
|
20223
|
+
const bufferController = this.bufferController = new ConfigBufferController(this, fragmentTracker);
|
19815
20224
|
const capLevelController = this.capLevelController = new ConfigCapLevelController(this);
|
19816
20225
|
const fpsController = new ConfigFpsController(this);
|
19817
20226
|
const playListLoader = new PlaylistLoader(this);
|
@@ -19820,8 +20229,6 @@ class Hls {
|
|
19820
20229
|
// ConentSteeringController is defined before LevelController to receive Multivariant Playlist events first
|
19821
20230
|
const contentSteering = ConfigContentSteeringController ? new ConfigContentSteeringController(this) : null;
|
19822
20231
|
const levelController = this.levelController = new LevelController(this, contentSteering);
|
19823
|
-
// FragmentTracker must be defined before StreamController because the order of event handling is important
|
19824
|
-
const fragmentTracker = new FragmentTracker(this);
|
19825
20232
|
const keyLoader = new KeyLoader(this.config);
|
19826
20233
|
const streamController = this.streamController = new StreamController(this, fragmentTracker, keyLoader);
|
19827
20234
|
|
@@ -19897,7 +20304,7 @@ class Hls {
|
|
19897
20304
|
try {
|
19898
20305
|
return this.emit(event, event, eventObject);
|
19899
20306
|
} catch (error) {
|
19900
|
-
logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
|
20307
|
+
this.logger.error('An internal error happened while handling event ' + event + '. Error message: "' + error.message + '". Here is a stacktrace:', error);
|
19901
20308
|
// Prevent recursion in error event handlers that throw #5497
|
19902
20309
|
if (!this.triggeringException) {
|
19903
20310
|
this.triggeringException = true;
|
@@ -19923,7 +20330,7 @@ class Hls {
|
|
19923
20330
|
* Dispose of the instance
|
19924
20331
|
*/
|
19925
20332
|
destroy() {
|
19926
|
-
logger.log('destroy');
|
20333
|
+
this.logger.log('destroy');
|
19927
20334
|
this.trigger(Events.DESTROYING, undefined);
|
19928
20335
|
this.detachMedia();
|
19929
20336
|
this.removeAllListeners();
|
@@ -19944,7 +20351,7 @@ class Hls {
|
|
19944
20351
|
* Attaches Hls.js to a media element
|
19945
20352
|
*/
|
19946
20353
|
attachMedia(media) {
|
19947
|
-
logger.log('attachMedia');
|
20354
|
+
this.logger.log('attachMedia');
|
19948
20355
|
this._media = media;
|
19949
20356
|
this.trigger(Events.MEDIA_ATTACHING, {
|
19950
20357
|
media: media
|
@@ -19955,7 +20362,7 @@ class Hls {
|
|
19955
20362
|
* Detach Hls.js from the media
|
19956
20363
|
*/
|
19957
20364
|
detachMedia() {
|
19958
|
-
logger.log('detachMedia');
|
20365
|
+
this.logger.log('detachMedia');
|
19959
20366
|
this.trigger(Events.MEDIA_DETACHING, undefined);
|
19960
20367
|
this._media = null;
|
19961
20368
|
}
|
@@ -19972,7 +20379,7 @@ class Hls {
|
|
19972
20379
|
});
|
19973
20380
|
this._autoLevelCapping = -1;
|
19974
20381
|
this._maxHdcpLevel = null;
|
19975
|
-
logger.log(`loadSource:${loadingSource}`);
|
20382
|
+
this.logger.log(`loadSource:${loadingSource}`);
|
19976
20383
|
if (media && loadedSource && (loadedSource !== loadingSource || this.bufferController.hasSourceTypes())) {
|
19977
20384
|
this.detachMedia();
|
19978
20385
|
this.attachMedia(media);
|
@@ -19991,8 +20398,7 @@ class Hls {
|
|
19991
20398
|
* Defaults to -1 (None: starts from earliest point)
|
19992
20399
|
*/
|
19993
20400
|
startLoad(startPosition = -1) {
|
19994
|
-
logger.log(`startLoad(${startPosition})`);
|
19995
|
-
this.started = true;
|
20401
|
+
this.logger.log(`startLoad(${startPosition})`);
|
19996
20402
|
this.networkControllers.forEach(controller => {
|
19997
20403
|
controller.startLoad(startPosition);
|
19998
20404
|
});
|
@@ -20002,34 +20408,31 @@ class Hls {
|
|
20002
20408
|
* Stop loading of any stream data.
|
20003
20409
|
*/
|
20004
20410
|
stopLoad() {
|
20005
|
-
logger.log('stopLoad');
|
20006
|
-
this.started = false;
|
20411
|
+
this.logger.log('stopLoad');
|
20007
20412
|
this.networkControllers.forEach(controller => {
|
20008
20413
|
controller.stopLoad();
|
20009
20414
|
});
|
20010
20415
|
}
|
20011
20416
|
|
20012
20417
|
/**
|
20013
|
-
* Resumes stream controller segment loading
|
20418
|
+
* Resumes stream controller segment loading after `pauseBuffering` has been called.
|
20014
20419
|
*/
|
20015
20420
|
resumeBuffering() {
|
20016
|
-
|
20017
|
-
|
20018
|
-
|
20019
|
-
|
20020
|
-
|
20021
|
-
});
|
20022
|
-
}
|
20421
|
+
this.networkControllers.forEach(controller => {
|
20422
|
+
if (controller.resumeBuffering) {
|
20423
|
+
controller.resumeBuffering();
|
20424
|
+
}
|
20425
|
+
});
|
20023
20426
|
}
|
20024
20427
|
|
20025
20428
|
/**
|
20026
|
-
*
|
20429
|
+
* Prevents stream controller from loading new segments until `resumeBuffering` is called.
|
20027
20430
|
* This allows for media buffering to be paused without interupting playlist loading.
|
20028
20431
|
*/
|
20029
20432
|
pauseBuffering() {
|
20030
20433
|
this.networkControllers.forEach(controller => {
|
20031
|
-
if (
|
20032
|
-
controller.
|
20434
|
+
if (controller.pauseBuffering) {
|
20435
|
+
controller.pauseBuffering();
|
20033
20436
|
}
|
20034
20437
|
});
|
20035
20438
|
}
|
@@ -20038,7 +20441,7 @@ class Hls {
|
|
20038
20441
|
* Swap through possible audio codecs in the stream (for example to switch from stereo to 5.1)
|
20039
20442
|
*/
|
20040
20443
|
swapAudioCodec() {
|
20041
|
-
logger.log('swapAudioCodec');
|
20444
|
+
this.logger.log('swapAudioCodec');
|
20042
20445
|
this.streamController.swapAudioCodec();
|
20043
20446
|
}
|
20044
20447
|
|
@@ -20049,7 +20452,7 @@ class Hls {
|
|
20049
20452
|
* Automatic recovery of media-errors by this process is configurable.
|
20050
20453
|
*/
|
20051
20454
|
recoverMediaError() {
|
20052
|
-
logger.log('recoverMediaError');
|
20455
|
+
this.logger.log('recoverMediaError');
|
20053
20456
|
const media = this._media;
|
20054
20457
|
this.detachMedia();
|
20055
20458
|
if (media) {
|
@@ -20079,7 +20482,7 @@ class Hls {
|
|
20079
20482
|
* 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.
|
20080
20483
|
*/
|
20081
20484
|
set currentLevel(newLevel) {
|
20082
|
-
logger.log(`set currentLevel:${newLevel}`);
|
20485
|
+
this.logger.log(`set currentLevel:${newLevel}`);
|
20083
20486
|
this.levelController.manualLevel = newLevel;
|
20084
20487
|
this.streamController.immediateLevelSwitch();
|
20085
20488
|
}
|
@@ -20098,7 +20501,7 @@ class Hls {
|
|
20098
20501
|
* @param newLevel - Pass -1 for automatic level selection
|
20099
20502
|
*/
|
20100
20503
|
set nextLevel(newLevel) {
|
20101
|
-
logger.log(`set nextLevel:${newLevel}`);
|
20504
|
+
this.logger.log(`set nextLevel:${newLevel}`);
|
20102
20505
|
this.levelController.manualLevel = newLevel;
|
20103
20506
|
this.streamController.nextLevelSwitch();
|
20104
20507
|
}
|
@@ -20117,7 +20520,7 @@ class Hls {
|
|
20117
20520
|
* @param newLevel - Pass -1 for automatic level selection
|
20118
20521
|
*/
|
20119
20522
|
set loadLevel(newLevel) {
|
20120
|
-
logger.log(`set loadLevel:${newLevel}`);
|
20523
|
+
this.logger.log(`set loadLevel:${newLevel}`);
|
20121
20524
|
this.levelController.manualLevel = newLevel;
|
20122
20525
|
}
|
20123
20526
|
|
@@ -20148,7 +20551,7 @@ class Hls {
|
|
20148
20551
|
* Sets "first-level", see getter.
|
20149
20552
|
*/
|
20150
20553
|
set firstLevel(newLevel) {
|
20151
|
-
logger.log(`set firstLevel:${newLevel}`);
|
20554
|
+
this.logger.log(`set firstLevel:${newLevel}`);
|
20152
20555
|
this.levelController.firstLevel = newLevel;
|
20153
20556
|
}
|
20154
20557
|
|
@@ -20173,7 +20576,7 @@ class Hls {
|
|
20173
20576
|
* (determined from download of first segment)
|
20174
20577
|
*/
|
20175
20578
|
set startLevel(newLevel) {
|
20176
|
-
logger.log(`set startLevel:${newLevel}`);
|
20579
|
+
this.logger.log(`set startLevel:${newLevel}`);
|
20177
20580
|
// if not in automatic start level detection, ensure startLevel is greater than minAutoLevel
|
20178
20581
|
if (newLevel !== -1) {
|
20179
20582
|
newLevel = Math.max(newLevel, this.minAutoLevel);
|
@@ -20248,7 +20651,7 @@ class Hls {
|
|
20248
20651
|
*/
|
20249
20652
|
set autoLevelCapping(newLevel) {
|
20250
20653
|
if (this._autoLevelCapping !== newLevel) {
|
20251
|
-
logger.log(`set autoLevelCapping:${newLevel}`);
|
20654
|
+
this.logger.log(`set autoLevelCapping:${newLevel}`);
|
20252
20655
|
this._autoLevelCapping = newLevel;
|
20253
20656
|
this.levelController.checkMaxAutoUpdated();
|
20254
20657
|
}
|
@@ -20353,6 +20756,9 @@ class Hls {
|
|
20353
20756
|
get mainForwardBufferInfo() {
|
20354
20757
|
return this.streamController.getMainFwdBufferInfo();
|
20355
20758
|
}
|
20759
|
+
get maxBufferLength() {
|
20760
|
+
return this.streamController.maxBufferLength;
|
20761
|
+
}
|
20356
20762
|
|
20357
20763
|
/**
|
20358
20764
|
* Find and select the best matching audio track, making a level switch when a Group change is necessary.
|
@@ -20527,5 +20933,5 @@ var KeySystemFormats = empty.KeySystemFormats;
|
|
20527
20933
|
var KeySystems = empty.KeySystems;
|
20528
20934
|
var SubtitleStreamController = empty.SubtitleStreamController;
|
20529
20935
|
var TimelineController = empty.TimelineController;
|
20530
|
-
export { AbrController, AttrList,
|
20936
|
+
export { AbrController, AttrList, HevcVideoParser as AudioStreamController, HevcVideoParser as AudioTrackController, BasePlaylistController, BaseSegment, BaseStreamController, BufferController, HevcVideoParser as CMCDController, CapLevelController, ChunkMetadata, ContentSteeringController, DateRange, HevcVideoParser as EMEController, ErrorActionFlags, ErrorController, ErrorDetails, ErrorTypes, Events, FPSController, Fragment, Hls, HlsSkip, HlsUrlParameters, KeySystemFormats, KeySystems, Level, LevelDetails, LevelKey, LoadStats, MetadataSchema, NetworkErrorAction, Part, PlaylistLevelType, SubtitleStreamController, HevcVideoParser as SubtitleTrackController, TimelineController, Hls as default, getMediaSource, isMSESupported, isSupported };
|
20531
20937
|
//# sourceMappingURL=hls.light.mjs.map
|