hls.js 1.5.18 → 1.5.20
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.js +303 -250
- package/dist/hls.js.d.ts +3 -4
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +24 -29
- 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 +23 -28
- 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 +252 -206
- package/dist/hls.mjs.map +1 -1
- package/dist/hls.worker.js +1 -1
- package/package.json +1 -1
- package/src/controller/audio-stream-controller.ts +21 -29
- package/src/controller/eme-controller.ts +184 -124
- package/src/loader/key-loader.ts +6 -1
- package/src/loader/level-key.ts +6 -30
- package/src/utils/level-helper.ts +37 -38
- package/src/utils/mediakeys-helper.ts +34 -1
- package/src/utils/rendition-helper.ts +6 -10
package/dist/hls.mjs
CHANGED
@@ -411,7 +411,7 @@ function enableLogs(debugConfig, id) {
|
|
411
411
|
// Some browsers don't allow to use bind on console object anyway
|
412
412
|
// fallback to default if needed
|
413
413
|
try {
|
414
|
-
exportedLogger.log(`Debug logs enabled for "${id}" in hls.js version ${"1.5.
|
414
|
+
exportedLogger.log(`Debug logs enabled for "${id}" in hls.js version ${"1.5.20"}`);
|
415
415
|
} catch (e) {
|
416
416
|
exportedLogger = fakeLogger;
|
417
417
|
}
|
@@ -1157,6 +1157,27 @@ function createMediaKeySystemConfigurations(initDataTypes, audioCodecs, videoCod
|
|
1157
1157
|
};
|
1158
1158
|
return [baseConfig];
|
1159
1159
|
}
|
1160
|
+
function parsePlayReadyWRM(keyBytes) {
|
1161
|
+
const keyBytesUtf16 = new Uint16Array(keyBytes.buffer, keyBytes.byteOffset, keyBytes.byteLength / 2);
|
1162
|
+
const keyByteStr = String.fromCharCode.apply(null, Array.from(keyBytesUtf16));
|
1163
|
+
|
1164
|
+
// Parse Playready WRMHeader XML
|
1165
|
+
const xmlKeyBytes = keyByteStr.substring(keyByteStr.indexOf('<'), keyByteStr.length);
|
1166
|
+
const parser = new DOMParser();
|
1167
|
+
const xmlDoc = parser.parseFromString(xmlKeyBytes, 'text/xml');
|
1168
|
+
const keyData = xmlDoc.getElementsByTagName('KID')[0];
|
1169
|
+
if (keyData) {
|
1170
|
+
const keyId = keyData.childNodes[0] ? keyData.childNodes[0].nodeValue : keyData.getAttribute('VALUE');
|
1171
|
+
if (keyId) {
|
1172
|
+
const keyIdArray = base64Decode(keyId).subarray(0, 16);
|
1173
|
+
// KID value in PRO is a base64-encoded little endian GUID interpretation of UUID
|
1174
|
+
// KID value in ‘tenc’ is a big endian UUID GUID interpretation of UUID
|
1175
|
+
changeEndianness(keyIdArray);
|
1176
|
+
return keyIdArray;
|
1177
|
+
}
|
1178
|
+
}
|
1179
|
+
return null;
|
1180
|
+
}
|
1160
1181
|
|
1161
1182
|
function sliceUint8(array, start, end) {
|
1162
1183
|
// @ts-expect-error This polyfills IE11 usage of Uint8Array slice.
|
@@ -2773,6 +2794,8 @@ class LevelKey {
|
|
2773
2794
|
if (keyBytes) {
|
2774
2795
|
switch (this.keyFormat) {
|
2775
2796
|
case KeySystemFormats.WIDEVINE:
|
2797
|
+
// Setting `pssh` on this LevelKey/DecryptData allows HLS.js to generate a session using
|
2798
|
+
// the playlist-key before the "encrypted" event. (Comment out to only use "encrypted" path.)
|
2776
2799
|
this.pssh = keyBytes;
|
2777
2800
|
// In case of widevine keyID is embedded in PSSH box. Read Key ID.
|
2778
2801
|
if (keyBytes.length >= 22) {
|
@@ -2782,25 +2805,11 @@ class LevelKey {
|
|
2782
2805
|
case KeySystemFormats.PLAYREADY:
|
2783
2806
|
{
|
2784
2807
|
const PlayReadyKeySystemUUID = new Uint8Array([0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40, 0x42, 0x86, 0xab, 0x92, 0xe6, 0x5b, 0xe0, 0x88, 0x5f, 0x95]);
|
2808
|
+
|
2809
|
+
// Setting `pssh` on this LevelKey/DecryptData allows HLS.js to generate a session using
|
2810
|
+
// the playlist-key before the "encrypted" event. (Comment out to only use "encrypted" path.)
|
2785
2811
|
this.pssh = mp4pssh(PlayReadyKeySystemUUID, null, keyBytes);
|
2786
|
-
|
2787
|
-
const keyByteStr = String.fromCharCode.apply(null, Array.from(keyBytesUtf16));
|
2788
|
-
|
2789
|
-
// Parse Playready WRMHeader XML
|
2790
|
-
const xmlKeyBytes = keyByteStr.substring(keyByteStr.indexOf('<'), keyByteStr.length);
|
2791
|
-
const parser = new DOMParser();
|
2792
|
-
const xmlDoc = parser.parseFromString(xmlKeyBytes, 'text/xml');
|
2793
|
-
const keyData = xmlDoc.getElementsByTagName('KID')[0];
|
2794
|
-
if (keyData) {
|
2795
|
-
const keyId = keyData.childNodes[0] ? keyData.childNodes[0].nodeValue : keyData.getAttribute('VALUE');
|
2796
|
-
if (keyId) {
|
2797
|
-
const keyIdArray = base64Decode(keyId).subarray(0, 16);
|
2798
|
-
// KID value in PRO is a base64-encoded little endian GUID interpretation of UUID
|
2799
|
-
// KID value in ‘tenc’ is a big endian UUID GUID interpretation of UUID
|
2800
|
-
changeEndianness(keyIdArray);
|
2801
|
-
this.keyId = keyIdArray;
|
2802
|
-
}
|
2803
|
-
}
|
2812
|
+
this.keyId = parsePlayReadyWRM(keyBytes);
|
2804
2813
|
break;
|
2805
2814
|
}
|
2806
2815
|
default:
|
@@ -5250,15 +5259,16 @@ function mergeDetails(oldDetails, newDetails) {
|
|
5250
5259
|
delete oldDetails.fragmentHint.endPTS;
|
5251
5260
|
}
|
5252
5261
|
// check if old/new playlists have fragments in common
|
5253
|
-
// loop through overlapping SN and update startPTS
|
5254
|
-
let ccOffset = 0;
|
5262
|
+
// loop through overlapping SN and update startPTS, cc, and duration if any found
|
5255
5263
|
let PTSFrag;
|
5256
|
-
mapFragmentIntersection(oldDetails, newDetails, (oldFrag, newFrag) => {
|
5257
|
-
if (
|
5258
|
-
|
5259
|
-
|
5260
|
-
|
5261
|
-
|
5264
|
+
mapFragmentIntersection(oldDetails, newDetails, (oldFrag, newFrag, newFragIndex, newFragments) => {
|
5265
|
+
if (newDetails.skippedSegments) {
|
5266
|
+
if (newFrag.cc !== oldFrag.cc) {
|
5267
|
+
const ccOffset = oldFrag.cc - newFrag.cc;
|
5268
|
+
for (let i = newFragIndex; i < newFragments.length; i++) {
|
5269
|
+
newFragments[i].cc += ccOffset;
|
5270
|
+
}
|
5271
|
+
}
|
5262
5272
|
}
|
5263
5273
|
if (isFiniteNumber(oldFrag.startPTS) && isFiniteNumber(oldFrag.endPTS)) {
|
5264
5274
|
newFrag.start = newFrag.startPTS = oldFrag.startPTS;
|
@@ -5283,8 +5293,9 @@ function mergeDetails(oldDetails, newDetails) {
|
|
5283
5293
|
currentInitSegment = oldFrag.initSegment;
|
5284
5294
|
}
|
5285
5295
|
});
|
5296
|
+
const newFragments = newDetails.fragments;
|
5286
5297
|
if (currentInitSegment) {
|
5287
|
-
const fragmentsToCheck = newDetails.fragmentHint ?
|
5298
|
+
const fragmentsToCheck = newDetails.fragmentHint ? newFragments.concat(newDetails.fragmentHint) : newFragments;
|
5288
5299
|
fragmentsToCheck.forEach(frag => {
|
5289
5300
|
var _currentInitSegment;
|
5290
5301
|
if (frag && (!frag.initSegment || frag.initSegment.relurl === ((_currentInitSegment = currentInitSegment) == null ? void 0 : _currentInitSegment.relurl))) {
|
@@ -5293,27 +5304,20 @@ function mergeDetails(oldDetails, newDetails) {
|
|
5293
5304
|
});
|
5294
5305
|
}
|
5295
5306
|
if (newDetails.skippedSegments) {
|
5296
|
-
newDetails.deltaUpdateFailed =
|
5307
|
+
newDetails.deltaUpdateFailed = newFragments.some(frag => !frag);
|
5297
5308
|
if (newDetails.deltaUpdateFailed) {
|
5298
5309
|
logger.warn('[level-helper] Previous playlist missing segments skipped in delta playlist');
|
5299
5310
|
for (let i = newDetails.skippedSegments; i--;) {
|
5300
|
-
|
5311
|
+
newFragments.shift();
|
5312
|
+
}
|
5313
|
+
newDetails.startSN = newFragments[0].sn;
|
5314
|
+
} else {
|
5315
|
+
if (newDetails.canSkipDateRanges) {
|
5316
|
+
newDetails.dateRanges = mergeDateRanges(oldDetails.dateRanges, newDetails.dateRanges, newDetails.recentlyRemovedDateranges);
|
5301
5317
|
}
|
5302
|
-
newDetails.startSN = newDetails.fragments[0].sn;
|
5303
|
-
newDetails.startCC = newDetails.fragments[0].cc;
|
5304
|
-
} else if (newDetails.canSkipDateRanges) {
|
5305
|
-
newDetails.dateRanges = mergeDateRanges(oldDetails.dateRanges, newDetails.dateRanges, newDetails.recentlyRemovedDateranges);
|
5306
|
-
}
|
5307
|
-
}
|
5308
|
-
const newFragments = newDetails.fragments;
|
5309
|
-
if (ccOffset) {
|
5310
|
-
logger.warn('discontinuity sliding from playlist, take drift into account');
|
5311
|
-
for (let i = 0; i < newFragments.length; i++) {
|
5312
|
-
newFragments[i].cc += ccOffset;
|
5313
5318
|
}
|
5314
|
-
}
|
5315
|
-
if (newDetails.skippedSegments) {
|
5316
5319
|
newDetails.startCC = newDetails.fragments[0].cc;
|
5320
|
+
newDetails.endCC = newFragments[newFragments.length - 1].cc;
|
5317
5321
|
}
|
5318
5322
|
|
5319
5323
|
// Merge parts
|
@@ -5397,7 +5401,7 @@ function mapFragmentIntersection(oldDetails, newDetails, intersectionFn) {
|
|
5397
5401
|
newFrag = newDetails.fragments[i] = oldFrag;
|
5398
5402
|
}
|
5399
5403
|
if (oldFrag && newFrag) {
|
5400
|
-
intersectionFn(oldFrag, newFrag);
|
5404
|
+
intersectionFn(oldFrag, newFrag, i, newFrags);
|
5401
5405
|
}
|
5402
5406
|
}
|
5403
5407
|
}
|
@@ -6819,11 +6823,10 @@ function matchesOption(option, track, matchPredicate) {
|
|
6819
6823
|
name,
|
6820
6824
|
lang,
|
6821
6825
|
assocLang,
|
6822
|
-
characteristics,
|
6823
6826
|
default: isDefault
|
6824
6827
|
} = option;
|
6825
6828
|
const forced = option.forced;
|
6826
|
-
return (groupId === undefined || track.groupId === groupId) && (name === undefined || track.name === name) && (lang === undefined || track.lang === lang) && (lang === undefined || track.assocLang === assocLang) && (isDefault === undefined || track.default === isDefault) && (forced === undefined || track.forced === forced) && (characteristics
|
6829
|
+
return (groupId === undefined || track.groupId === groupId) && (name === undefined || track.name === name) && (lang === undefined || track.lang === lang) && (lang === undefined || track.assocLang === assocLang) && (isDefault === undefined || track.default === isDefault) && (forced === undefined || track.forced === forced) && (!('characteristics' in option) || characteristicsMatch(option.characteristics || '', track.characteristics)) && (matchPredicate === undefined || matchPredicate(option, track));
|
6827
6830
|
}
|
6828
6831
|
function characteristicsMatch(characteristicsA, characteristicsB = '') {
|
6829
6832
|
const arrA = characteristicsA.split(',');
|
@@ -15697,30 +15700,6 @@ class TransmuxerInterface {
|
|
15697
15700
|
}
|
15698
15701
|
}
|
15699
15702
|
|
15700
|
-
function subtitleOptionsIdentical(trackList1, trackList2) {
|
15701
|
-
if (trackList1.length !== trackList2.length) {
|
15702
|
-
return false;
|
15703
|
-
}
|
15704
|
-
for (let i = 0; i < trackList1.length; i++) {
|
15705
|
-
if (!mediaAttributesIdentical(trackList1[i].attrs, trackList2[i].attrs)) {
|
15706
|
-
return false;
|
15707
|
-
}
|
15708
|
-
}
|
15709
|
-
return true;
|
15710
|
-
}
|
15711
|
-
function mediaAttributesIdentical(attrs1, attrs2, customAttributes) {
|
15712
|
-
// Media options with the same rendition ID must be bit identical
|
15713
|
-
const stableRenditionId = attrs1['STABLE-RENDITION-ID'];
|
15714
|
-
if (stableRenditionId && !customAttributes) {
|
15715
|
-
return stableRenditionId === attrs2['STABLE-RENDITION-ID'];
|
15716
|
-
}
|
15717
|
-
// When rendition ID is not present, compare attributes
|
15718
|
-
return !(customAttributes || ['LANGUAGE', 'NAME', 'CHARACTERISTICS', 'AUTOSELECT', 'DEFAULT', 'FORCED', 'ASSOC-LANGUAGE']).some(subtitleAttribute => attrs1[subtitleAttribute] !== attrs2[subtitleAttribute]);
|
15719
|
-
}
|
15720
|
-
function subtitleTrackMatchesTextTrack(subtitleTrack, textTrack) {
|
15721
|
-
return textTrack.label.toLowerCase() === subtitleTrack.name.toLowerCase() && (!textTrack.language || textTrack.language.toLowerCase() === (subtitleTrack.lang || '').toLowerCase());
|
15722
|
-
}
|
15723
|
-
|
15724
15703
|
const TICK_INTERVAL$2 = 100; // how often to tick in ms
|
15725
15704
|
|
15726
15705
|
class AudioStreamController extends BaseStreamController {
|
@@ -15969,11 +15948,7 @@ class AudioStreamController extends BaseStreamController {
|
|
15969
15948
|
if (bufferInfo === null) {
|
15970
15949
|
return;
|
15971
15950
|
}
|
15972
|
-
|
15973
|
-
bufferedTrack,
|
15974
|
-
switchingTrack
|
15975
|
-
} = this;
|
15976
|
-
if (!switchingTrack && this._streamEnded(bufferInfo, trackDetails)) {
|
15951
|
+
if (!this.switchingTrack && this._streamEnded(bufferInfo, trackDetails)) {
|
15977
15952
|
hls.trigger(Events.BUFFER_EOS, {
|
15978
15953
|
type: 'audio'
|
15979
15954
|
});
|
@@ -15985,13 +15960,10 @@ class AudioStreamController extends BaseStreamController {
|
|
15985
15960
|
const maxBufLen = this.getMaxBufferLength(mainBufferInfo == null ? void 0 : mainBufferInfo.len);
|
15986
15961
|
const fragments = trackDetails.fragments;
|
15987
15962
|
const start = fragments[0].start;
|
15988
|
-
|
15989
|
-
|
15990
|
-
|
15991
|
-
|
15992
|
-
if (bufferedTrack && !mediaAttributesIdentical(switchingTrack.attrs, bufferedTrack.attrs)) {
|
15993
|
-
targetBufferTime = pos;
|
15994
|
-
}
|
15963
|
+
const loadPosition = this.getLoadPosition();
|
15964
|
+
const targetBufferTime = this.flushing ? loadPosition : bufferInfo.end;
|
15965
|
+
if (this.switchingTrack && media) {
|
15966
|
+
const pos = loadPosition;
|
15995
15967
|
// if currentTime (pos) is less than alt audio playlist start time, it means that alt audio is ahead of currentTime
|
15996
15968
|
if (trackDetails.PTSKnown && pos < start) {
|
15997
15969
|
// if everything is buffered from pos to start or if audio buffer upfront, let's seek to start
|
@@ -16003,7 +15975,7 @@ class AudioStreamController extends BaseStreamController {
|
|
16003
15975
|
}
|
16004
15976
|
|
16005
15977
|
// if buffer length is less than maxBufLen, or near the end, find a fragment to load
|
16006
|
-
if (bufferLen >= maxBufLen && !switchingTrack && targetBufferTime < fragments[fragments.length - 1].start) {
|
15978
|
+
if (bufferLen >= maxBufLen && !this.switchingTrack && targetBufferTime < fragments[fragments.length - 1].start) {
|
16007
15979
|
return;
|
16008
15980
|
}
|
16009
15981
|
let frag = this.getNextFragment(targetBufferTime, trackDetails);
|
@@ -16484,16 +16456,27 @@ class AudioStreamController extends BaseStreamController {
|
|
16484
16456
|
}
|
16485
16457
|
}
|
16486
16458
|
flushAudioIfNeeded(switchingTrack) {
|
16487
|
-
|
16488
|
-
|
16489
|
-
|
16490
|
-
|
16491
|
-
|
16492
|
-
|
16493
|
-
|
16494
|
-
|
16495
|
-
|
16496
|
-
|
16459
|
+
if (this.media && this.bufferedTrack) {
|
16460
|
+
const {
|
16461
|
+
name,
|
16462
|
+
lang,
|
16463
|
+
assocLang,
|
16464
|
+
characteristics,
|
16465
|
+
audioCodec,
|
16466
|
+
channels
|
16467
|
+
} = this.bufferedTrack;
|
16468
|
+
if (!matchesOption({
|
16469
|
+
name,
|
16470
|
+
lang,
|
16471
|
+
assocLang,
|
16472
|
+
characteristics,
|
16473
|
+
audioCodec,
|
16474
|
+
channels
|
16475
|
+
}, switchingTrack, audioMatchPredicate)) {
|
16476
|
+
this.log('Switching audio track : flushing all audio');
|
16477
|
+
super.flushMainBuffer(0, Number.POSITIVE_INFINITY, 'audio');
|
16478
|
+
this.bufferedTrack = null;
|
16479
|
+
}
|
16497
16480
|
}
|
16498
16481
|
}
|
16499
16482
|
completeAudioSwitch(switchingTrack) {
|
@@ -16507,6 +16490,30 @@ class AudioStreamController extends BaseStreamController {
|
|
16507
16490
|
}
|
16508
16491
|
}
|
16509
16492
|
|
16493
|
+
function subtitleOptionsIdentical(trackList1, trackList2) {
|
16494
|
+
if (trackList1.length !== trackList2.length) {
|
16495
|
+
return false;
|
16496
|
+
}
|
16497
|
+
for (let i = 0; i < trackList1.length; i++) {
|
16498
|
+
if (!mediaAttributesIdentical(trackList1[i].attrs, trackList2[i].attrs)) {
|
16499
|
+
return false;
|
16500
|
+
}
|
16501
|
+
}
|
16502
|
+
return true;
|
16503
|
+
}
|
16504
|
+
function mediaAttributesIdentical(attrs1, attrs2, customAttributes) {
|
16505
|
+
// Media options with the same rendition ID must be bit identical
|
16506
|
+
const stableRenditionId = attrs1['STABLE-RENDITION-ID'];
|
16507
|
+
if (stableRenditionId && !customAttributes) {
|
16508
|
+
return stableRenditionId === attrs2['STABLE-RENDITION-ID'];
|
16509
|
+
}
|
16510
|
+
// When rendition ID is not present, compare attributes
|
16511
|
+
return !(customAttributes || ['LANGUAGE', 'NAME', 'CHARACTERISTICS', 'AUTOSELECT', 'DEFAULT', 'FORCED', 'ASSOC-LANGUAGE']).some(subtitleAttribute => attrs1[subtitleAttribute] !== attrs2[subtitleAttribute]);
|
16512
|
+
}
|
16513
|
+
function subtitleTrackMatchesTextTrack(subtitleTrack, textTrack) {
|
16514
|
+
return textTrack.label.toLowerCase() === subtitleTrack.name.toLowerCase() && (!textTrack.language || textTrack.language.toLowerCase() === (subtitleTrack.lang || '').toLowerCase());
|
16515
|
+
}
|
16516
|
+
|
16510
16517
|
class AudioTrackController extends BasePlaylistController {
|
16511
16518
|
constructor(hls) {
|
16512
16519
|
super(hls, '[audio-track-controller]');
|
@@ -22012,12 +22019,148 @@ class EMEController {
|
|
22012
22019
|
this.mediaKeySessions = [];
|
22013
22020
|
this.keyIdToKeySessionPromise = {};
|
22014
22021
|
this.setMediaKeysQueue = EMEController.CDMCleanupPromise ? [EMEController.CDMCleanupPromise] : [];
|
22015
|
-
this.onMediaEncrypted = this._onMediaEncrypted.bind(this);
|
22016
|
-
this.onWaitingForKey = this._onWaitingForKey.bind(this);
|
22017
22022
|
this.debug = logger.debug.bind(logger, LOGGER_PREFIX);
|
22018
22023
|
this.log = logger.log.bind(logger, LOGGER_PREFIX);
|
22019
22024
|
this.warn = logger.warn.bind(logger, LOGGER_PREFIX);
|
22020
22025
|
this.error = logger.error.bind(logger, LOGGER_PREFIX);
|
22026
|
+
this.onMediaEncrypted = event => {
|
22027
|
+
const {
|
22028
|
+
initDataType,
|
22029
|
+
initData
|
22030
|
+
} = event;
|
22031
|
+
const logMessage = `"${event.type}" event: init data type: "${initDataType}"`;
|
22032
|
+
this.debug(logMessage);
|
22033
|
+
|
22034
|
+
// Ignore event when initData is null
|
22035
|
+
if (initData === null) {
|
22036
|
+
return;
|
22037
|
+
}
|
22038
|
+
if (!this.keyFormatPromise) {
|
22039
|
+
let keySystems = Object.keys(this.keySystemAccessPromises);
|
22040
|
+
if (!keySystems.length) {
|
22041
|
+
keySystems = getKeySystemsForConfig(this.config);
|
22042
|
+
}
|
22043
|
+
const keyFormats = keySystems.map(keySystemDomainToKeySystemFormat).filter(k => !!k);
|
22044
|
+
this.keyFormatPromise = this.getKeyFormatPromise(keyFormats);
|
22045
|
+
}
|
22046
|
+
this.keyFormatPromise.then(keySystemFormat => {
|
22047
|
+
const keySystem = keySystemFormatToKeySystemDomain(keySystemFormat);
|
22048
|
+
let keyId;
|
22049
|
+
let keySystemDomain;
|
22050
|
+
if (initDataType === 'sinf') {
|
22051
|
+
if (keySystem !== KeySystems.FAIRPLAY) {
|
22052
|
+
this.warn(`Ignoring unexpected "${event.type}" event with init data type: "${initDataType}" for selected key-system ${keySystem}`);
|
22053
|
+
return;
|
22054
|
+
}
|
22055
|
+
// Match sinf keyId to playlist skd://keyId=
|
22056
|
+
const json = bin2str(new Uint8Array(initData));
|
22057
|
+
try {
|
22058
|
+
const sinf = base64Decode(JSON.parse(json).sinf);
|
22059
|
+
const tenc = parseSinf(sinf);
|
22060
|
+
if (!tenc) {
|
22061
|
+
throw new Error(`'schm' box missing or not cbcs/cenc with schi > tenc`);
|
22062
|
+
}
|
22063
|
+
keyId = tenc.subarray(8, 24);
|
22064
|
+
keySystemDomain = KeySystems.FAIRPLAY;
|
22065
|
+
} catch (error) {
|
22066
|
+
this.warn(`${logMessage} Failed to parse sinf: ${error}`);
|
22067
|
+
return;
|
22068
|
+
}
|
22069
|
+
} else {
|
22070
|
+
if (keySystem !== KeySystems.WIDEVINE && keySystem !== KeySystems.PLAYREADY) {
|
22071
|
+
this.warn(`Ignoring unexpected "${event.type}" event with init data type: "${initDataType}" for selected key-system ${keySystem}`);
|
22072
|
+
return;
|
22073
|
+
}
|
22074
|
+
// Support Widevine/PlayReady clear-lead key-session creation (otherwise depend on playlist keys)
|
22075
|
+
const psshResults = parseMultiPssh(initData);
|
22076
|
+
const psshInfos = psshResults.filter(pssh => !!pssh.systemId && keySystemIdToKeySystemDomain(pssh.systemId) === keySystem);
|
22077
|
+
if (psshInfos.length > 1) {
|
22078
|
+
this.warn(`${logMessage} Using first of ${psshInfos.length} pssh found for selected key-system ${keySystem}`);
|
22079
|
+
}
|
22080
|
+
const psshInfo = psshInfos[0];
|
22081
|
+
if (!psshInfo) {
|
22082
|
+
if (psshResults.length === 0 || psshResults.some(pssh => !pssh.systemId)) {
|
22083
|
+
this.warn(`${logMessage} contains incomplete or invalid pssh data`);
|
22084
|
+
} else {
|
22085
|
+
this.log(`ignoring ${logMessage} for ${psshResults.map(pssh => keySystemIdToKeySystemDomain(pssh.systemId)).join(',')} pssh data in favor of playlist keys`);
|
22086
|
+
}
|
22087
|
+
return;
|
22088
|
+
}
|
22089
|
+
keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
|
22090
|
+
if (psshInfo.version === 0 && psshInfo.data) {
|
22091
|
+
if (keySystemDomain === KeySystems.WIDEVINE) {
|
22092
|
+
const offset = psshInfo.data.length - 22;
|
22093
|
+
keyId = psshInfo.data.subarray(offset, offset + 16);
|
22094
|
+
} else if (keySystemDomain === KeySystems.PLAYREADY) {
|
22095
|
+
keyId = parsePlayReadyWRM(psshInfo.data);
|
22096
|
+
}
|
22097
|
+
}
|
22098
|
+
}
|
22099
|
+
if (!keySystemDomain || !keyId) {
|
22100
|
+
this.log(`Unable to handle ${logMessage} with key-system ${keySystem}`);
|
22101
|
+
return;
|
22102
|
+
}
|
22103
|
+
const keyIdHex = Hex.hexDump(keyId);
|
22104
|
+
const {
|
22105
|
+
keyIdToKeySessionPromise,
|
22106
|
+
mediaKeySessions
|
22107
|
+
} = this;
|
22108
|
+
let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
|
22109
|
+
for (let i = 0; i < mediaKeySessions.length; i++) {
|
22110
|
+
// Match playlist key
|
22111
|
+
const keyContext = mediaKeySessions[i];
|
22112
|
+
const decryptdata = keyContext.decryptdata;
|
22113
|
+
if (!decryptdata.keyId) {
|
22114
|
+
continue;
|
22115
|
+
}
|
22116
|
+
const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
|
22117
|
+
if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
|
22118
|
+
keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
|
22119
|
+
if (decryptdata.pssh) {
|
22120
|
+
break;
|
22121
|
+
}
|
22122
|
+
delete keyIdToKeySessionPromise[oldKeyIdHex];
|
22123
|
+
decryptdata.pssh = new Uint8Array(initData);
|
22124
|
+
decryptdata.keyId = keyId;
|
22125
|
+
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
|
22126
|
+
return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
|
22127
|
+
});
|
22128
|
+
keySessionContextPromise.catch(error => this.handleError(error));
|
22129
|
+
break;
|
22130
|
+
}
|
22131
|
+
}
|
22132
|
+
if (!keySessionContextPromise) {
|
22133
|
+
if (keySystemDomain !== keySystem) {
|
22134
|
+
this.log(`Ignoring "${logMessage}" with ${keySystemDomain} init data for selected key-system ${keySystem}`);
|
22135
|
+
return;
|
22136
|
+
}
|
22137
|
+
// "Clear-lead" (misc key not encountered in playlist)
|
22138
|
+
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
|
22139
|
+
keySystem,
|
22140
|
+
mediaKeys
|
22141
|
+
}) => {
|
22142
|
+
var _keySystemToKeySystem;
|
22143
|
+
this.throwIfDestroyed();
|
22144
|
+
const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
|
22145
|
+
decryptdata.pssh = new Uint8Array(initData);
|
22146
|
+
decryptdata.keyId = keyId;
|
22147
|
+
return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
|
22148
|
+
this.throwIfDestroyed();
|
22149
|
+
const keySessionContext = this.createMediaKeySessionContext({
|
22150
|
+
decryptdata,
|
22151
|
+
keySystem,
|
22152
|
+
mediaKeys
|
22153
|
+
});
|
22154
|
+
return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
|
22155
|
+
});
|
22156
|
+
});
|
22157
|
+
keySessionContextPromise.catch(error => this.handleError(error));
|
22158
|
+
}
|
22159
|
+
});
|
22160
|
+
};
|
22161
|
+
this.onWaitingForKey = event => {
|
22162
|
+
this.log(`"${event.type}" event`);
|
22163
|
+
};
|
22021
22164
|
this.hls = hls;
|
22022
22165
|
this.config = hls.config;
|
22023
22166
|
this.registerListeners();
|
@@ -22031,9 +22174,9 @@ class EMEController {
|
|
22031
22174
|
config.licenseXhrSetup = config.licenseResponseCallback = undefined;
|
22032
22175
|
config.drmSystems = config.drmSystemOptions = {};
|
22033
22176
|
// @ts-ignore
|
22034
|
-
this.hls = this.
|
22177
|
+
this.hls = this.config = this.keyIdToKeySessionPromise = null;
|
22035
22178
|
// @ts-ignore
|
22036
|
-
this.
|
22179
|
+
this.onMediaEncrypted = this.onWaitingForKey = null;
|
22037
22180
|
}
|
22038
22181
|
registerListeners() {
|
22039
22182
|
this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
@@ -22061,7 +22204,13 @@ class EMEController {
|
|
22061
22204
|
if (keySystem === KeySystems.WIDEVINE && widevineLicenseUrl) {
|
22062
22205
|
return widevineLicenseUrl;
|
22063
22206
|
}
|
22064
|
-
|
22207
|
+
}
|
22208
|
+
getLicenseServerUrlOrThrow(keySystem) {
|
22209
|
+
const url = this.getLicenseServerUrl(keySystem);
|
22210
|
+
if (url === undefined) {
|
22211
|
+
throw new Error(`no license server URL configured for key-system "${keySystem}"`);
|
22212
|
+
}
|
22213
|
+
return url;
|
22065
22214
|
}
|
22066
22215
|
getServerCertificateUrl(keySystem) {
|
22067
22216
|
const {
|
@@ -22297,111 +22446,6 @@ class EMEController {
|
|
22297
22446
|
}
|
22298
22447
|
return this.attemptKeySystemAccess(keySystemsToAttempt);
|
22299
22448
|
}
|
22300
|
-
_onMediaEncrypted(event) {
|
22301
|
-
const {
|
22302
|
-
initDataType,
|
22303
|
-
initData
|
22304
|
-
} = event;
|
22305
|
-
const logMessage = `"${event.type}" event: init data type: "${initDataType}"`;
|
22306
|
-
this.debug(logMessage);
|
22307
|
-
|
22308
|
-
// Ignore event when initData is null
|
22309
|
-
if (initData === null) {
|
22310
|
-
return;
|
22311
|
-
}
|
22312
|
-
let keyId;
|
22313
|
-
let keySystemDomain;
|
22314
|
-
if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) {
|
22315
|
-
// Match sinf keyId to playlist skd://keyId=
|
22316
|
-
const json = bin2str(new Uint8Array(initData));
|
22317
|
-
try {
|
22318
|
-
const sinf = base64Decode(JSON.parse(json).sinf);
|
22319
|
-
const tenc = parseSinf(new Uint8Array(sinf));
|
22320
|
-
if (!tenc) {
|
22321
|
-
throw new Error(`'schm' box missing or not cbcs/cenc with schi > tenc`);
|
22322
|
-
}
|
22323
|
-
keyId = tenc.subarray(8, 24);
|
22324
|
-
keySystemDomain = KeySystems.FAIRPLAY;
|
22325
|
-
} catch (error) {
|
22326
|
-
this.warn(`${logMessage} Failed to parse sinf: ${error}`);
|
22327
|
-
return;
|
22328
|
-
}
|
22329
|
-
} else {
|
22330
|
-
// Support Widevine clear-lead key-session creation (otherwise depend on playlist keys)
|
22331
|
-
const psshResults = parseMultiPssh(initData);
|
22332
|
-
const psshInfo = psshResults.filter(pssh => pssh.systemId === KeySystemIds.WIDEVINE)[0];
|
22333
|
-
if (!psshInfo) {
|
22334
|
-
if (psshResults.length === 0 || psshResults.some(pssh => !pssh.systemId)) {
|
22335
|
-
this.warn(`${logMessage} contains incomplete or invalid pssh data`);
|
22336
|
-
} else {
|
22337
|
-
this.log(`ignoring ${logMessage} for ${psshResults.map(pssh => keySystemIdToKeySystemDomain(pssh.systemId)).join(',')} pssh data in favor of playlist keys`);
|
22338
|
-
}
|
22339
|
-
return;
|
22340
|
-
}
|
22341
|
-
keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
|
22342
|
-
if (psshInfo.version === 0 && psshInfo.data) {
|
22343
|
-
const offset = psshInfo.data.length - 22;
|
22344
|
-
keyId = psshInfo.data.subarray(offset, offset + 16);
|
22345
|
-
}
|
22346
|
-
}
|
22347
|
-
if (!keySystemDomain || !keyId) {
|
22348
|
-
return;
|
22349
|
-
}
|
22350
|
-
const keyIdHex = Hex.hexDump(keyId);
|
22351
|
-
const {
|
22352
|
-
keyIdToKeySessionPromise,
|
22353
|
-
mediaKeySessions
|
22354
|
-
} = this;
|
22355
|
-
let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
|
22356
|
-
for (let i = 0; i < mediaKeySessions.length; i++) {
|
22357
|
-
// Match playlist key
|
22358
|
-
const keyContext = mediaKeySessions[i];
|
22359
|
-
const decryptdata = keyContext.decryptdata;
|
22360
|
-
if (!decryptdata.keyId) {
|
22361
|
-
continue;
|
22362
|
-
}
|
22363
|
-
const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
|
22364
|
-
if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
|
22365
|
-
keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
|
22366
|
-
if (decryptdata.pssh) {
|
22367
|
-
break;
|
22368
|
-
}
|
22369
|
-
delete keyIdToKeySessionPromise[oldKeyIdHex];
|
22370
|
-
decryptdata.pssh = new Uint8Array(initData);
|
22371
|
-
decryptdata.keyId = keyId;
|
22372
|
-
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
|
22373
|
-
return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
|
22374
|
-
});
|
22375
|
-
break;
|
22376
|
-
}
|
22377
|
-
}
|
22378
|
-
if (!keySessionContextPromise) {
|
22379
|
-
// Clear-lead key (not encountered in playlist)
|
22380
|
-
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
|
22381
|
-
keySystem,
|
22382
|
-
mediaKeys
|
22383
|
-
}) => {
|
22384
|
-
var _keySystemToKeySystem;
|
22385
|
-
this.throwIfDestroyed();
|
22386
|
-
const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
|
22387
|
-
decryptdata.pssh = new Uint8Array(initData);
|
22388
|
-
decryptdata.keyId = keyId;
|
22389
|
-
return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
|
22390
|
-
this.throwIfDestroyed();
|
22391
|
-
const keySessionContext = this.createMediaKeySessionContext({
|
22392
|
-
decryptdata,
|
22393
|
-
keySystem,
|
22394
|
-
mediaKeys
|
22395
|
-
});
|
22396
|
-
return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
|
22397
|
-
});
|
22398
|
-
});
|
22399
|
-
}
|
22400
|
-
keySessionContextPromise.catch(error => this.handleError(error));
|
22401
|
-
}
|
22402
|
-
_onWaitingForKey(event) {
|
22403
|
-
this.log(`"${event.type}" event`);
|
22404
|
-
}
|
22405
22449
|
attemptSetMediaKeys(keySystem, mediaKeys) {
|
22406
22450
|
const queue = this.setMediaKeysQueue.slice();
|
22407
22451
|
this.log(`Setting media-keys for "${keySystem}"`);
|
@@ -22694,7 +22738,7 @@ class EMEController {
|
|
22694
22738
|
requestLicense(keySessionContext, licenseChallenge) {
|
22695
22739
|
const keyLoadPolicy = this.config.keyLoadPolicy.default;
|
22696
22740
|
return new Promise((resolve, reject) => {
|
22697
|
-
const url = this.
|
22741
|
+
const url = this.getLicenseServerUrlOrThrow(keySessionContext.keySystem);
|
22698
22742
|
this.log(`Sending license request to URL: ${url}`);
|
22699
22743
|
const xhr = new XMLHttpRequest();
|
22700
22744
|
xhr.responseType = 'arraybuffer';
|
@@ -22764,6 +22808,8 @@ class EMEController {
|
|
22764
22808
|
|
22765
22809
|
// keep reference of media
|
22766
22810
|
this.media = media;
|
22811
|
+
media.removeEventListener('encrypted', this.onMediaEncrypted);
|
22812
|
+
media.removeEventListener('waitingforkey', this.onWaitingForKey);
|
22767
22813
|
media.addEventListener('encrypted', this.onMediaEncrypted);
|
22768
22814
|
media.addEventListener('waitingforkey', this.onWaitingForKey);
|
22769
22815
|
}
|
@@ -26113,7 +26159,7 @@ class KeyLoader {
|
|
26113
26159
|
}
|
26114
26160
|
}
|
26115
26161
|
load(frag) {
|
26116
|
-
if (!frag.decryptdata && frag.encrypted && this.emeController) {
|
26162
|
+
if (!frag.decryptdata && frag.encrypted && this.emeController && this.config.emeEnabled) {
|
26117
26163
|
// Multiple keys, but none selected, resolve in eme-controller
|
26118
26164
|
return this.emeController.selectKeySystemFormat(frag).then(keySystemFormat => {
|
26119
26165
|
return this.loadInternal(frag, keySystemFormat);
|
@@ -27801,7 +27847,7 @@ class Hls {
|
|
27801
27847
|
* Get the video-dev/hls.js package version.
|
27802
27848
|
*/
|
27803
27849
|
static get version() {
|
27804
|
-
return "1.5.
|
27850
|
+
return "1.5.20";
|
27805
27851
|
}
|
27806
27852
|
|
27807
27853
|
/**
|