hls.js 1.5.14-0.canary.10567 → 1.5.14-0.canary.10570
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hls.d.mts +1 -0
- package/dist/hls.d.ts +1 -0
- package/dist/hls.js +92 -126
- package/dist/hls.js.d.ts +1 -0
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +35 -95
- 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 +37 -95
- 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 +90 -120
- 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 +2 -2
- package/src/controller/eme-controller.ts +40 -16
- package/src/demux/audio/aacdemuxer.ts +2 -1
- package/src/demux/audio/adts.ts +31 -102
- package/src/demux/tsdemuxer.ts +1 -7
- package/src/loader/level-key.ts +8 -36
- package/src/remux/mp4-generator.ts +37 -34
- package/src/utils/mediakeys-helper.ts +33 -0
package/dist/hls.mjs
CHANGED
@@ -421,7 +421,7 @@ function enableLogs(debugConfig, context, id) {
|
|
421
421
|
// Some browsers don't allow to use bind on console object anyway
|
422
422
|
// fallback to default if needed
|
423
423
|
try {
|
424
|
-
newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.5.14-0.canary.
|
424
|
+
newLogger.log(`Debug logs enabled for "${context}" in hls.js version ${"1.5.14-0.canary.10570"}`);
|
425
425
|
} catch (e) {
|
426
426
|
/* log fn threw an exception. All logger methods are no-ops. */
|
427
427
|
return createLogger();
|
@@ -1365,6 +1365,27 @@ function createMediaKeySystemConfigurations(initDataTypes, audioCodecs, videoCod
|
|
1365
1365
|
};
|
1366
1366
|
return [baseConfig];
|
1367
1367
|
}
|
1368
|
+
function parsePlayReadyWRM(keyBytes) {
|
1369
|
+
const keyBytesUtf16 = new Uint16Array(keyBytes.buffer, keyBytes.byteOffset, keyBytes.byteLength / 2);
|
1370
|
+
const keyByteStr = String.fromCharCode.apply(null, Array.from(keyBytesUtf16));
|
1371
|
+
|
1372
|
+
// Parse Playready WRMHeader XML
|
1373
|
+
const xmlKeyBytes = keyByteStr.substring(keyByteStr.indexOf('<'), keyByteStr.length);
|
1374
|
+
const parser = new DOMParser();
|
1375
|
+
const xmlDoc = parser.parseFromString(xmlKeyBytes, 'text/xml');
|
1376
|
+
const keyData = xmlDoc.getElementsByTagName('KID')[0];
|
1377
|
+
if (keyData) {
|
1378
|
+
const keyId = keyData.childNodes[0] ? keyData.childNodes[0].nodeValue : keyData.getAttribute('VALUE');
|
1379
|
+
if (keyId) {
|
1380
|
+
const keyIdArray = base64Decode(keyId).subarray(0, 16);
|
1381
|
+
// KID value in PRO is a base64-encoded little endian GUID interpretation of UUID
|
1382
|
+
// KID value in ‘tenc’ is a big endian UUID GUID interpretation of UUID
|
1383
|
+
changeEndianness(keyIdArray);
|
1384
|
+
return keyIdArray;
|
1385
|
+
}
|
1386
|
+
}
|
1387
|
+
return null;
|
1388
|
+
}
|
1368
1389
|
|
1369
1390
|
function sliceUint8(array, start, end) {
|
1370
1391
|
// @ts-expect-error This polyfills IE11 usage of Uint8Array slice.
|
@@ -2703,6 +2724,8 @@ class LevelKey {
|
|
2703
2724
|
if (keyBytes) {
|
2704
2725
|
switch (this.keyFormat) {
|
2705
2726
|
case KeySystemFormats.WIDEVINE:
|
2727
|
+
// Setting `pssh` on this LevelKey/DecryptData allows HLS.js to generate a session using
|
2728
|
+
// the playlist-key before the "encrypted" event. (Comment out to only use "encrypted" path.)
|
2706
2729
|
this.pssh = keyBytes;
|
2707
2730
|
// In case of widevine keyID is embedded in PSSH box. Read Key ID.
|
2708
2731
|
if (keyBytes.length >= 22) {
|
@@ -2712,25 +2735,11 @@ class LevelKey {
|
|
2712
2735
|
case KeySystemFormats.PLAYREADY:
|
2713
2736
|
{
|
2714
2737
|
const PlayReadyKeySystemUUID = new Uint8Array([0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40, 0x42, 0x86, 0xab, 0x92, 0xe6, 0x5b, 0xe0, 0x88, 0x5f, 0x95]);
|
2738
|
+
|
2739
|
+
// Setting `pssh` on this LevelKey/DecryptData allows HLS.js to generate a session using
|
2740
|
+
// the playlist-key before the "encrypted" event. (Comment out to only use "encrypted" path.)
|
2715
2741
|
this.pssh = mp4pssh(PlayReadyKeySystemUUID, null, keyBytes);
|
2716
|
-
|
2717
|
-
const keyByteStr = String.fromCharCode.apply(null, Array.from(keyBytesUtf16));
|
2718
|
-
|
2719
|
-
// Parse Playready WRMHeader XML
|
2720
|
-
const xmlKeyBytes = keyByteStr.substring(keyByteStr.indexOf('<'), keyByteStr.length);
|
2721
|
-
const parser = new DOMParser();
|
2722
|
-
const xmlDoc = parser.parseFromString(xmlKeyBytes, 'text/xml');
|
2723
|
-
const keyData = xmlDoc.getElementsByTagName('KID')[0];
|
2724
|
-
if (keyData) {
|
2725
|
-
const keyId = keyData.childNodes[0] ? keyData.childNodes[0].nodeValue : keyData.getAttribute('VALUE');
|
2726
|
-
if (keyId) {
|
2727
|
-
const keyIdArray = base64Decode(keyId).subarray(0, 16);
|
2728
|
-
// KID value in PRO is a base64-encoded little endian GUID interpretation of UUID
|
2729
|
-
// KID value in ‘tenc’ is a big endian UUID GUID interpretation of UUID
|
2730
|
-
changeEndianness(keyIdArray);
|
2731
|
-
this.keyId = keyIdArray;
|
2732
|
-
}
|
2733
|
-
}
|
2742
|
+
this.keyId = parsePlayReadyWRM(keyBytes);
|
2734
2743
|
break;
|
2735
2744
|
}
|
2736
2745
|
default:
|
@@ -11070,7 +11079,7 @@ function concatUint8Arrays(chunks, dataLength) {
|
|
11070
11079
|
return result;
|
11071
11080
|
}
|
11072
11081
|
|
11073
|
-
const version = "1.5.14-0.canary.
|
11082
|
+
const version = "1.5.14-0.canary.10570";
|
11074
11083
|
|
11075
11084
|
// ensure the worker ends up in the bundle
|
11076
11085
|
// If the worker should not be included this gets aliased to empty.js
|
@@ -11388,23 +11397,11 @@ const initPTSFn = (timestamp, timeOffset, initPTS) => {
|
|
11388
11397
|
return timeOffset * 90000 + init90kHz;
|
11389
11398
|
};
|
11390
11399
|
|
11391
|
-
|
11392
|
-
* ADTS parser helper
|
11393
|
-
* @link https://wiki.multimedia.cx/index.php?title=ADTS
|
11394
|
-
*/
|
11395
|
-
function getAudioConfig(observer, data, offset, audioCodec) {
|
11396
|
-
let adtsObjectType;
|
11397
|
-
let originalAdtsObjectType;
|
11398
|
-
let adtsExtensionSamplingIndex;
|
11399
|
-
let adtsChannelConfig;
|
11400
|
-
let config;
|
11401
|
-
const userAgent = navigator.userAgent.toLowerCase();
|
11402
|
-
const manifestCodec = audioCodec;
|
11400
|
+
function getAudioConfig(observer, data, offset, manifestCodec) {
|
11403
11401
|
const adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
|
11404
|
-
|
11405
|
-
|
11406
|
-
|
11407
|
-
if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
|
11402
|
+
const byte2 = data[offset + 2];
|
11403
|
+
const adtsSamplingIndex = byte2 >> 2 & 0xf;
|
11404
|
+
if (adtsSamplingIndex > 12) {
|
11408
11405
|
const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
|
11409
11406
|
observer.emit(Events.ERROR, Events.ERROR, {
|
11410
11407
|
type: ErrorTypes.MEDIA_ERROR,
|
@@ -11415,53 +11412,12 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
11415
11412
|
});
|
11416
11413
|
return;
|
11417
11414
|
}
|
11418
|
-
|
11419
|
-
|
11420
|
-
|
11421
|
-
|
11422
|
-
// Firefox and Pale Moon: freq less than 24kHz = AAC SBR (HE-AAC)
|
11423
|
-
if (/firefox|palemoon/i.test(userAgent)) {
|
11424
|
-
if (adtsSamplingIndex >= 6) {
|
11425
|
-
adtsObjectType = 5;
|
11426
|
-
config = new Array(4);
|
11427
|
-
// HE-AAC uses SBR (Spectral Band Replication) , high frequencies are constructed from low frequencies
|
11428
|
-
// there is a factor 2 between frame sample rate and output sample rate
|
11429
|
-
// multiply frequency by 2 (see table below, equivalent to substract 3)
|
11430
|
-
adtsExtensionSamplingIndex = adtsSamplingIndex - 3;
|
11431
|
-
} else {
|
11432
|
-
adtsObjectType = 2;
|
11433
|
-
config = new Array(2);
|
11434
|
-
adtsExtensionSamplingIndex = adtsSamplingIndex;
|
11435
|
-
}
|
11436
|
-
// Android : always use AAC
|
11437
|
-
} else if (userAgent.indexOf('android') !== -1) {
|
11438
|
-
adtsObjectType = 2;
|
11439
|
-
config = new Array(2);
|
11440
|
-
adtsExtensionSamplingIndex = adtsSamplingIndex;
|
11441
|
-
} else {
|
11442
|
-
/* for other browsers (Chrome/Vivaldi/Opera ...)
|
11443
|
-
always force audio type to be HE-AAC SBR, as some browsers do not support audio codec switch properly (like Chrome ...)
|
11444
|
-
*/
|
11445
|
-
adtsObjectType = 5;
|
11446
|
-
config = new Array(4);
|
11447
|
-
// if (manifest codec is HE-AAC or HE-AACv2) OR (manifest codec not specified AND frequency less than 24kHz)
|
11448
|
-
if (audioCodec && (audioCodec.indexOf('mp4a.40.29') !== -1 || audioCodec.indexOf('mp4a.40.5') !== -1) || !audioCodec && adtsSamplingIndex >= 6) {
|
11449
|
-
// HE-AAC uses SBR (Spectral Band Replication) , high frequencies are constructed from low frequencies
|
11450
|
-
// there is a factor 2 between frame sample rate and output sample rate
|
11451
|
-
// multiply frequency by 2 (see table below, equivalent to substract 3)
|
11452
|
-
adtsExtensionSamplingIndex = adtsSamplingIndex - 3;
|
11453
|
-
} else {
|
11454
|
-
// if (manifest codec is AAC) AND (frequency less than 24kHz AND nb channel is 1) OR (manifest codec not specified and mono audio)
|
11455
|
-
// Chrome fails to play back with low frequency AAC LC mono when initialized with HE-AAC. This is not a problem with stereo.
|
11456
|
-
if (audioCodec && audioCodec.indexOf('mp4a.40.2') !== -1 && (adtsSamplingIndex >= 6 && adtsChannelConfig === 1 || /vivaldi/i.test(userAgent)) || !audioCodec && adtsChannelConfig === 1) {
|
11457
|
-
adtsObjectType = 2;
|
11458
|
-
config = new Array(2);
|
11459
|
-
}
|
11460
|
-
adtsExtensionSamplingIndex = adtsSamplingIndex;
|
11461
|
-
}
|
11462
|
-
}
|
11415
|
+
// MPEG-4 Audio Object Type (profile_ObjectType+1)
|
11416
|
+
const adtsObjectType = (byte2 >> 6 & 0x3) + 1;
|
11417
|
+
const channelCount = data[offset + 3] >> 6 & 0x3 | (byte2 & 1) << 2;
|
11418
|
+
const codec = 'mp4a.40.' + adtsObjectType;
|
11463
11419
|
/* refer to http://wiki.multimedia.cx/index.php?title=MPEG-4_Audio#Audio_Specific_Config
|
11464
|
-
ISO 14496-3
|
11420
|
+
ISO/IEC 14496-3 - Table 1.13 — Syntax of AudioSpecificConfig()
|
11465
11421
|
Audio Profile / Audio Object Type
|
11466
11422
|
0: Null
|
11467
11423
|
1: AAC Main
|
@@ -11494,27 +11450,22 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
11494
11450
|
2: 2 channels: front-left, front-right
|
11495
11451
|
*/
|
11496
11452
|
// audioObjectType = profile => profile, the MPEG-4 Audio Object Type minus 1
|
11497
|
-
|
11498
|
-
|
11499
|
-
|
11500
|
-
|
11501
|
-
|
11502
|
-
|
11503
|
-
|
11504
|
-
|
11505
|
-
|
11506
|
-
|
11507
|
-
// adtsObjectType (force to 2, chrome is checking that object type is less than 5 ???
|
11508
|
-
// https://chromium.googlesource.com/chromium/src.git/+/master/media/formats/mp4/aac.cc
|
11509
|
-
config[2] |= 2 << 2;
|
11510
|
-
config[3] = 0;
|
11511
|
-
}
|
11453
|
+
const samplerate = adtsSamplingRates[adtsSamplingIndex];
|
11454
|
+
let aacSampleIndex = adtsSamplingIndex;
|
11455
|
+
if (adtsObjectType === 5 || adtsObjectType === 29) {
|
11456
|
+
// HE-AAC uses SBR (Spectral Band Replication) , high frequencies are constructed from low frequencies
|
11457
|
+
// there is a factor 2 between frame sample rate and output sample rate
|
11458
|
+
// multiply frequency by 2 (see table above, equivalent to substract 3)
|
11459
|
+
aacSampleIndex -= 3;
|
11460
|
+
}
|
11461
|
+
const config = [adtsObjectType << 3 | (aacSampleIndex & 0x0e) >> 1, (aacSampleIndex & 0x01) << 7 | channelCount << 3];
|
11462
|
+
logger.log(`manifest codec:${manifestCodec}, parsed codec:${codec}, channels:${channelCount}, rate:${samplerate} (ADTS object type:${adtsObjectType} sampling index:${adtsSamplingIndex})`);
|
11512
11463
|
return {
|
11513
11464
|
config,
|
11514
|
-
samplerate
|
11515
|
-
channelCount
|
11516
|
-
codec
|
11517
|
-
parsedCodec:
|
11465
|
+
samplerate,
|
11466
|
+
channelCount,
|
11467
|
+
codec,
|
11468
|
+
parsedCodec: codec,
|
11518
11469
|
manifestCodec
|
11519
11470
|
};
|
11520
11471
|
}
|
@@ -11564,13 +11515,7 @@ function initTrackConfig(track, observer, data, offset, audioCodec) {
|
|
11564
11515
|
if (!config) {
|
11565
11516
|
return;
|
11566
11517
|
}
|
11567
|
-
track
|
11568
|
-
track.samplerate = config.samplerate;
|
11569
|
-
track.channelCount = config.channelCount;
|
11570
|
-
track.codec = config.codec;
|
11571
|
-
track.manifestCodec = config.manifestCodec;
|
11572
|
-
track.parsedCodec = config.parsedCodec;
|
11573
|
-
logger.log(`parsed codec:${track.parsedCodec}, codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
|
11518
|
+
_extends(track, config);
|
11574
11519
|
}
|
11575
11520
|
}
|
11576
11521
|
function getFrameDuration(samplerate) {
|
@@ -14736,7 +14681,7 @@ class MP4 {
|
|
14736
14681
|
vSpacing >> 16 & 0xff, vSpacing >> 8 & 0xff, vSpacing & 0xff])));
|
14737
14682
|
}
|
14738
14683
|
static esds(track) {
|
14739
|
-
const
|
14684
|
+
const config = track.config;
|
14740
14685
|
return new Uint8Array([0x00,
|
14741
14686
|
// version 0
|
14742
14687
|
0x00, 0x00, 0x00,
|
@@ -14744,16 +14689,18 @@ class MP4 {
|
|
14744
14689
|
|
14745
14690
|
0x03,
|
14746
14691
|
// descriptor_type
|
14747
|
-
|
14692
|
+
0x19,
|
14748
14693
|
// length
|
14694
|
+
|
14749
14695
|
0x00, 0x01,
|
14750
14696
|
// es_id
|
14697
|
+
|
14751
14698
|
0x00,
|
14752
14699
|
// stream_priority
|
14753
14700
|
|
14754
14701
|
0x04,
|
14755
14702
|
// descriptor_type
|
14756
|
-
|
14703
|
+
0x11,
|
14757
14704
|
// length
|
14758
14705
|
0x40,
|
14759
14706
|
// codec : mpeg4_audio
|
@@ -14766,8 +14713,12 @@ class MP4 {
|
|
14766
14713
|
0x00, 0x00, 0x00, 0x00,
|
14767
14714
|
// avgBitrate
|
14768
14715
|
|
14769
|
-
0x05
|
14770
|
-
|
14716
|
+
0x05,
|
14717
|
+
// descriptor_type
|
14718
|
+
0x02,
|
14719
|
+
// length
|
14720
|
+
...config, 0x06, 0x01, 0x02 // GASpecificConfig)); // length + audio config descriptor
|
14721
|
+
]);
|
14771
14722
|
}
|
14772
14723
|
static audioStsd(track) {
|
14773
14724
|
const samplerate = track.samplerate;
|
@@ -23626,7 +23577,7 @@ class EMEController extends Logger {
|
|
23626
23577
|
}
|
23627
23578
|
let keyId;
|
23628
23579
|
let keySystemDomain;
|
23629
|
-
if (initDataType === 'sinf' && this.
|
23580
|
+
if (initDataType === 'sinf' && this.getLicenseServerUrl(KeySystems.FAIRPLAY)) {
|
23630
23581
|
// Match sinf keyId to playlist skd://keyId=
|
23631
23582
|
const json = bin2str(new Uint8Array(initData));
|
23632
23583
|
try {
|
@@ -23641,10 +23592,19 @@ class EMEController extends Logger {
|
|
23641
23592
|
this.warn(`${logMessage} Failed to parse sinf: ${error}`);
|
23642
23593
|
return;
|
23643
23594
|
}
|
23644
|
-
} else {
|
23595
|
+
} else if (this.getLicenseServerUrl(KeySystems.WIDEVINE)) {
|
23645
23596
|
// Support Widevine clear-lead key-session creation (otherwise depend on playlist keys)
|
23646
23597
|
const psshResults = parseMultiPssh(initData);
|
23647
|
-
|
23598
|
+
|
23599
|
+
// TODO: If using keySystemAccessPromises we might want to wait until one is resolved
|
23600
|
+
let keySystems = Object.keys(this.keySystemAccessPromises);
|
23601
|
+
if (!keySystems.length) {
|
23602
|
+
keySystems = getKeySystemsForConfig(this.config);
|
23603
|
+
}
|
23604
|
+
const psshInfo = psshResults.filter(pssh => {
|
23605
|
+
const keySystem = pssh.systemId ? keySystemIdToKeySystemDomain(pssh.systemId) : null;
|
23606
|
+
return keySystem ? keySystems.indexOf(keySystem) > -1 : false;
|
23607
|
+
})[0];
|
23648
23608
|
if (!psshInfo) {
|
23649
23609
|
if (psshResults.length === 0 || psshResults.some(pssh => !pssh.systemId)) {
|
23650
23610
|
this.warn(`${logMessage} contains incomplete or invalid pssh data`);
|
@@ -23655,8 +23615,12 @@ class EMEController extends Logger {
|
|
23655
23615
|
}
|
23656
23616
|
keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
|
23657
23617
|
if (psshInfo.version === 0 && psshInfo.data) {
|
23658
|
-
|
23659
|
-
|
23618
|
+
if (keySystemDomain === KeySystems.WIDEVINE) {
|
23619
|
+
const offset = psshInfo.data.length - 22;
|
23620
|
+
keyId = psshInfo.data.subarray(offset, offset + 16);
|
23621
|
+
} else if (keySystemDomain === KeySystems.PLAYREADY) {
|
23622
|
+
keyId = parsePlayReadyWRM(psshInfo.data);
|
23623
|
+
}
|
23660
23624
|
}
|
23661
23625
|
}
|
23662
23626
|
if (!keySystemDomain || !keyId) {
|
@@ -23760,7 +23724,13 @@ class EMEController extends Logger {
|
|
23760
23724
|
if (keySystem === KeySystems.WIDEVINE && widevineLicenseUrl) {
|
23761
23725
|
return widevineLicenseUrl;
|
23762
23726
|
}
|
23763
|
-
|
23727
|
+
}
|
23728
|
+
getLicenseServerUrlOrThrow(keySystem) {
|
23729
|
+
const url = this.getLicenseServerUrl(keySystem);
|
23730
|
+
if (url === undefined) {
|
23731
|
+
throw new Error(`no license server URL configured for key-system "${keySystem}"`);
|
23732
|
+
}
|
23733
|
+
return url;
|
23764
23734
|
}
|
23765
23735
|
getServerCertificateUrl(keySystem) {
|
23766
23736
|
const {
|
@@ -24288,7 +24258,7 @@ class EMEController extends Logger {
|
|
24288
24258
|
requestLicense(keySessionContext, licenseChallenge) {
|
24289
24259
|
const keyLoadPolicy = this.config.keyLoadPolicy.default;
|
24290
24260
|
return new Promise((resolve, reject) => {
|
24291
|
-
const url = this.
|
24261
|
+
const url = this.getLicenseServerUrlOrThrow(keySessionContext.keySystem);
|
24292
24262
|
this.log(`Sending license request to URL: ${url}`);
|
24293
24263
|
const xhr = new XMLHttpRequest();
|
24294
24264
|
xhr.responseType = 'arraybuffer';
|