hls.js 1.5.17 → 1.5.19
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 +342 -268
- package/dist/hls.js.d.ts +10 -6
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +112 -91
- 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 +111 -90
- 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 +292 -222
- 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 +1 -1
- package/src/controller/audio-stream-controller.ts +4 -2
- package/src/controller/base-stream-controller.ts +9 -0
- package/src/controller/buffer-controller.ts +1 -0
- package/src/controller/eme-controller.ts +184 -124
- package/src/controller/stream-controller.ts +4 -1
- package/src/hls.ts +24 -17
- package/src/loader/key-loader.ts +6 -1
- package/src/loader/level-key.ts +6 -30
- package/src/remux/mp4-remuxer.ts +11 -7
- package/src/types/component-api.ts +2 -0
- package/src/utils/level-helper.ts +37 -38
- package/src/utils/mediakeys-helper.ts +34 -1
- package/src/utils/xhr-loader.ts +52 -49
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.19"}`);
|
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
|
}
|
@@ -9168,6 +9172,7 @@ class BaseStreamController extends TaskLoop {
|
|
9168
9172
|
this.startFragRequested = false;
|
9169
9173
|
this.decrypter = void 0;
|
9170
9174
|
this.initPTS = [];
|
9175
|
+
this.buffering = true;
|
9171
9176
|
this.onvseeking = null;
|
9172
9177
|
this.onvended = null;
|
9173
9178
|
this.logPrefix = '';
|
@@ -9207,6 +9212,12 @@ class BaseStreamController extends TaskLoop {
|
|
9207
9212
|
this.clearNextTick();
|
9208
9213
|
this.state = State.STOPPED;
|
9209
9214
|
}
|
9215
|
+
pauseBuffering() {
|
9216
|
+
this.buffering = false;
|
9217
|
+
}
|
9218
|
+
resumeBuffering() {
|
9219
|
+
this.buffering = true;
|
9220
|
+
}
|
9210
9221
|
_streamEnded(bufferInfo, levelDetails) {
|
9211
9222
|
// If playlist is live, there is another buffered range after the current range, nothing buffered, media is detached,
|
9212
9223
|
// of nothing loading/loaded return false
|
@@ -13624,19 +13635,23 @@ class MP4Remuxer {
|
|
13624
13635
|
this.videoTrackConfig = undefined;
|
13625
13636
|
}
|
13626
13637
|
getVideoStartPts(videoSamples) {
|
13638
|
+
// Get the minimum PTS value relative to the first sample's PTS, normalized for 33-bit wrapping
|
13627
13639
|
let rolloverDetected = false;
|
13640
|
+
const firstPts = videoSamples[0].pts;
|
13628
13641
|
const startPTS = videoSamples.reduce((minPTS, sample) => {
|
13629
|
-
|
13642
|
+
let pts = sample.pts;
|
13643
|
+
let delta = pts - minPTS;
|
13630
13644
|
if (delta < -4294967296) {
|
13631
13645
|
// 2^32, see PTSNormalize for reasoning, but we're hitting a rollover here, and we don't want that to impact the timeOffset calculation
|
13632
13646
|
rolloverDetected = true;
|
13633
|
-
|
13634
|
-
|
13647
|
+
pts = normalizePts(pts, firstPts);
|
13648
|
+
delta = pts - minPTS;
|
13649
|
+
}
|
13650
|
+
if (delta > 0) {
|
13635
13651
|
return minPTS;
|
13636
|
-
} else {
|
13637
|
-
return sample.pts;
|
13638
13652
|
}
|
13639
|
-
|
13653
|
+
return pts;
|
13654
|
+
}, firstPts);
|
13640
13655
|
if (rolloverDetected) {
|
13641
13656
|
logger.debug('PTS rollover detected');
|
13642
13657
|
}
|
@@ -15934,12 +15949,13 @@ class AudioStreamController extends BaseStreamController {
|
|
15934
15949
|
} = this;
|
15935
15950
|
const config = hls.config;
|
15936
15951
|
|
15937
|
-
// 1. if
|
15952
|
+
// 1. if buffering is suspended
|
15953
|
+
// 2. if video not attached AND
|
15938
15954
|
// start fragment already requested OR start frag prefetch not enabled
|
15939
|
-
//
|
15955
|
+
// 3. if tracks or track not loaded and selected
|
15940
15956
|
// then exit loop
|
15941
15957
|
// => if media not attached but start frag prefetch is enabled and start frag not requested yet, we will not exit loop
|
15942
|
-
if (!media && (this.startFragRequested || !config.startFragPrefetch) || !(levels != null && levels[trackId])) {
|
15958
|
+
if (!this.buffering || !media && (this.startFragRequested || !config.startFragPrefetch) || !(levels != null && levels[trackId])) {
|
15943
15959
|
return;
|
15944
15960
|
}
|
15945
15961
|
const levelInfo = levels[trackId];
|
@@ -17993,6 +18009,7 @@ class BufferController {
|
|
17993
18009
|
this.resetBuffer(type);
|
17994
18010
|
});
|
17995
18011
|
this._initSourceBuffer();
|
18012
|
+
this.hls.resumeBuffering();
|
17996
18013
|
}
|
17997
18014
|
resetBuffer(type) {
|
17998
18015
|
const sb = this.sourceBuffer[type];
|
@@ -21999,12 +22016,148 @@ class EMEController {
|
|
21999
22016
|
this.mediaKeySessions = [];
|
22000
22017
|
this.keyIdToKeySessionPromise = {};
|
22001
22018
|
this.setMediaKeysQueue = EMEController.CDMCleanupPromise ? [EMEController.CDMCleanupPromise] : [];
|
22002
|
-
this.onMediaEncrypted = this._onMediaEncrypted.bind(this);
|
22003
|
-
this.onWaitingForKey = this._onWaitingForKey.bind(this);
|
22004
22019
|
this.debug = logger.debug.bind(logger, LOGGER_PREFIX);
|
22005
22020
|
this.log = logger.log.bind(logger, LOGGER_PREFIX);
|
22006
22021
|
this.warn = logger.warn.bind(logger, LOGGER_PREFIX);
|
22007
22022
|
this.error = logger.error.bind(logger, LOGGER_PREFIX);
|
22023
|
+
this.onMediaEncrypted = event => {
|
22024
|
+
const {
|
22025
|
+
initDataType,
|
22026
|
+
initData
|
22027
|
+
} = event;
|
22028
|
+
const logMessage = `"${event.type}" event: init data type: "${initDataType}"`;
|
22029
|
+
this.debug(logMessage);
|
22030
|
+
|
22031
|
+
// Ignore event when initData is null
|
22032
|
+
if (initData === null) {
|
22033
|
+
return;
|
22034
|
+
}
|
22035
|
+
if (!this.keyFormatPromise) {
|
22036
|
+
let keySystems = Object.keys(this.keySystemAccessPromises);
|
22037
|
+
if (!keySystems.length) {
|
22038
|
+
keySystems = getKeySystemsForConfig(this.config);
|
22039
|
+
}
|
22040
|
+
const keyFormats = keySystems.map(keySystemDomainToKeySystemFormat).filter(k => !!k);
|
22041
|
+
this.keyFormatPromise = this.getKeyFormatPromise(keyFormats);
|
22042
|
+
}
|
22043
|
+
this.keyFormatPromise.then(keySystemFormat => {
|
22044
|
+
const keySystem = keySystemFormatToKeySystemDomain(keySystemFormat);
|
22045
|
+
let keyId;
|
22046
|
+
let keySystemDomain;
|
22047
|
+
if (initDataType === 'sinf') {
|
22048
|
+
if (keySystem !== KeySystems.FAIRPLAY) {
|
22049
|
+
this.warn(`Ignoring unexpected "${event.type}" event with init data type: "${initDataType}" for selected key-system ${keySystem}`);
|
22050
|
+
return;
|
22051
|
+
}
|
22052
|
+
// Match sinf keyId to playlist skd://keyId=
|
22053
|
+
const json = bin2str(new Uint8Array(initData));
|
22054
|
+
try {
|
22055
|
+
const sinf = base64Decode(JSON.parse(json).sinf);
|
22056
|
+
const tenc = parseSinf(sinf);
|
22057
|
+
if (!tenc) {
|
22058
|
+
throw new Error(`'schm' box missing or not cbcs/cenc with schi > tenc`);
|
22059
|
+
}
|
22060
|
+
keyId = tenc.subarray(8, 24);
|
22061
|
+
keySystemDomain = KeySystems.FAIRPLAY;
|
22062
|
+
} catch (error) {
|
22063
|
+
this.warn(`${logMessage} Failed to parse sinf: ${error}`);
|
22064
|
+
return;
|
22065
|
+
}
|
22066
|
+
} else {
|
22067
|
+
if (keySystem !== KeySystems.WIDEVINE && keySystem !== KeySystems.PLAYREADY) {
|
22068
|
+
this.warn(`Ignoring unexpected "${event.type}" event with init data type: "${initDataType}" for selected key-system ${keySystem}`);
|
22069
|
+
return;
|
22070
|
+
}
|
22071
|
+
// Support Widevine/PlayReady clear-lead key-session creation (otherwise depend on playlist keys)
|
22072
|
+
const psshResults = parseMultiPssh(initData);
|
22073
|
+
const psshInfos = psshResults.filter(pssh => !!pssh.systemId && keySystemIdToKeySystemDomain(pssh.systemId) === keySystem);
|
22074
|
+
if (psshInfos.length > 1) {
|
22075
|
+
this.warn(`${logMessage} Using first of ${psshInfos.length} pssh found for selected key-system ${keySystem}`);
|
22076
|
+
}
|
22077
|
+
const psshInfo = psshInfos[0];
|
22078
|
+
if (!psshInfo) {
|
22079
|
+
if (psshResults.length === 0 || psshResults.some(pssh => !pssh.systemId)) {
|
22080
|
+
this.warn(`${logMessage} contains incomplete or invalid pssh data`);
|
22081
|
+
} else {
|
22082
|
+
this.log(`ignoring ${logMessage} for ${psshResults.map(pssh => keySystemIdToKeySystemDomain(pssh.systemId)).join(',')} pssh data in favor of playlist keys`);
|
22083
|
+
}
|
22084
|
+
return;
|
22085
|
+
}
|
22086
|
+
keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
|
22087
|
+
if (psshInfo.version === 0 && psshInfo.data) {
|
22088
|
+
if (keySystemDomain === KeySystems.WIDEVINE) {
|
22089
|
+
const offset = psshInfo.data.length - 22;
|
22090
|
+
keyId = psshInfo.data.subarray(offset, offset + 16);
|
22091
|
+
} else if (keySystemDomain === KeySystems.PLAYREADY) {
|
22092
|
+
keyId = parsePlayReadyWRM(psshInfo.data);
|
22093
|
+
}
|
22094
|
+
}
|
22095
|
+
}
|
22096
|
+
if (!keySystemDomain || !keyId) {
|
22097
|
+
this.log(`Unable to handle ${logMessage} with key-system ${keySystem}`);
|
22098
|
+
return;
|
22099
|
+
}
|
22100
|
+
const keyIdHex = Hex.hexDump(keyId);
|
22101
|
+
const {
|
22102
|
+
keyIdToKeySessionPromise,
|
22103
|
+
mediaKeySessions
|
22104
|
+
} = this;
|
22105
|
+
let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
|
22106
|
+
for (let i = 0; i < mediaKeySessions.length; i++) {
|
22107
|
+
// Match playlist key
|
22108
|
+
const keyContext = mediaKeySessions[i];
|
22109
|
+
const decryptdata = keyContext.decryptdata;
|
22110
|
+
if (!decryptdata.keyId) {
|
22111
|
+
continue;
|
22112
|
+
}
|
22113
|
+
const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
|
22114
|
+
if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
|
22115
|
+
keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
|
22116
|
+
if (decryptdata.pssh) {
|
22117
|
+
break;
|
22118
|
+
}
|
22119
|
+
delete keyIdToKeySessionPromise[oldKeyIdHex];
|
22120
|
+
decryptdata.pssh = new Uint8Array(initData);
|
22121
|
+
decryptdata.keyId = keyId;
|
22122
|
+
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
|
22123
|
+
return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
|
22124
|
+
});
|
22125
|
+
keySessionContextPromise.catch(error => this.handleError(error));
|
22126
|
+
break;
|
22127
|
+
}
|
22128
|
+
}
|
22129
|
+
if (!keySessionContextPromise) {
|
22130
|
+
if (keySystemDomain !== keySystem) {
|
22131
|
+
this.log(`Ignoring "${logMessage}" with ${keySystemDomain} init data for selected key-system ${keySystem}`);
|
22132
|
+
return;
|
22133
|
+
}
|
22134
|
+
// "Clear-lead" (misc key not encountered in playlist)
|
22135
|
+
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
|
22136
|
+
keySystem,
|
22137
|
+
mediaKeys
|
22138
|
+
}) => {
|
22139
|
+
var _keySystemToKeySystem;
|
22140
|
+
this.throwIfDestroyed();
|
22141
|
+
const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
|
22142
|
+
decryptdata.pssh = new Uint8Array(initData);
|
22143
|
+
decryptdata.keyId = keyId;
|
22144
|
+
return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
|
22145
|
+
this.throwIfDestroyed();
|
22146
|
+
const keySessionContext = this.createMediaKeySessionContext({
|
22147
|
+
decryptdata,
|
22148
|
+
keySystem,
|
22149
|
+
mediaKeys
|
22150
|
+
});
|
22151
|
+
return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
|
22152
|
+
});
|
22153
|
+
});
|
22154
|
+
keySessionContextPromise.catch(error => this.handleError(error));
|
22155
|
+
}
|
22156
|
+
});
|
22157
|
+
};
|
22158
|
+
this.onWaitingForKey = event => {
|
22159
|
+
this.log(`"${event.type}" event`);
|
22160
|
+
};
|
22008
22161
|
this.hls = hls;
|
22009
22162
|
this.config = hls.config;
|
22010
22163
|
this.registerListeners();
|
@@ -22018,9 +22171,9 @@ class EMEController {
|
|
22018
22171
|
config.licenseXhrSetup = config.licenseResponseCallback = undefined;
|
22019
22172
|
config.drmSystems = config.drmSystemOptions = {};
|
22020
22173
|
// @ts-ignore
|
22021
|
-
this.hls = this.
|
22174
|
+
this.hls = this.config = this.keyIdToKeySessionPromise = null;
|
22022
22175
|
// @ts-ignore
|
22023
|
-
this.
|
22176
|
+
this.onMediaEncrypted = this.onWaitingForKey = null;
|
22024
22177
|
}
|
22025
22178
|
registerListeners() {
|
22026
22179
|
this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
@@ -22048,7 +22201,13 @@ class EMEController {
|
|
22048
22201
|
if (keySystem === KeySystems.WIDEVINE && widevineLicenseUrl) {
|
22049
22202
|
return widevineLicenseUrl;
|
22050
22203
|
}
|
22051
|
-
|
22204
|
+
}
|
22205
|
+
getLicenseServerUrlOrThrow(keySystem) {
|
22206
|
+
const url = this.getLicenseServerUrl(keySystem);
|
22207
|
+
if (url === undefined) {
|
22208
|
+
throw new Error(`no license server URL configured for key-system "${keySystem}"`);
|
22209
|
+
}
|
22210
|
+
return url;
|
22052
22211
|
}
|
22053
22212
|
getServerCertificateUrl(keySystem) {
|
22054
22213
|
const {
|
@@ -22284,111 +22443,6 @@ class EMEController {
|
|
22284
22443
|
}
|
22285
22444
|
return this.attemptKeySystemAccess(keySystemsToAttempt);
|
22286
22445
|
}
|
22287
|
-
_onMediaEncrypted(event) {
|
22288
|
-
const {
|
22289
|
-
initDataType,
|
22290
|
-
initData
|
22291
|
-
} = event;
|
22292
|
-
const logMessage = `"${event.type}" event: init data type: "${initDataType}"`;
|
22293
|
-
this.debug(logMessage);
|
22294
|
-
|
22295
|
-
// Ignore event when initData is null
|
22296
|
-
if (initData === null) {
|
22297
|
-
return;
|
22298
|
-
}
|
22299
|
-
let keyId;
|
22300
|
-
let keySystemDomain;
|
22301
|
-
if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) {
|
22302
|
-
// Match sinf keyId to playlist skd://keyId=
|
22303
|
-
const json = bin2str(new Uint8Array(initData));
|
22304
|
-
try {
|
22305
|
-
const sinf = base64Decode(JSON.parse(json).sinf);
|
22306
|
-
const tenc = parseSinf(new Uint8Array(sinf));
|
22307
|
-
if (!tenc) {
|
22308
|
-
throw new Error(`'schm' box missing or not cbcs/cenc with schi > tenc`);
|
22309
|
-
}
|
22310
|
-
keyId = tenc.subarray(8, 24);
|
22311
|
-
keySystemDomain = KeySystems.FAIRPLAY;
|
22312
|
-
} catch (error) {
|
22313
|
-
this.warn(`${logMessage} Failed to parse sinf: ${error}`);
|
22314
|
-
return;
|
22315
|
-
}
|
22316
|
-
} else {
|
22317
|
-
// Support Widevine clear-lead key-session creation (otherwise depend on playlist keys)
|
22318
|
-
const psshResults = parseMultiPssh(initData);
|
22319
|
-
const psshInfo = psshResults.filter(pssh => pssh.systemId === KeySystemIds.WIDEVINE)[0];
|
22320
|
-
if (!psshInfo) {
|
22321
|
-
if (psshResults.length === 0 || psshResults.some(pssh => !pssh.systemId)) {
|
22322
|
-
this.warn(`${logMessage} contains incomplete or invalid pssh data`);
|
22323
|
-
} else {
|
22324
|
-
this.log(`ignoring ${logMessage} for ${psshResults.map(pssh => keySystemIdToKeySystemDomain(pssh.systemId)).join(',')} pssh data in favor of playlist keys`);
|
22325
|
-
}
|
22326
|
-
return;
|
22327
|
-
}
|
22328
|
-
keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
|
22329
|
-
if (psshInfo.version === 0 && psshInfo.data) {
|
22330
|
-
const offset = psshInfo.data.length - 22;
|
22331
|
-
keyId = psshInfo.data.subarray(offset, offset + 16);
|
22332
|
-
}
|
22333
|
-
}
|
22334
|
-
if (!keySystemDomain || !keyId) {
|
22335
|
-
return;
|
22336
|
-
}
|
22337
|
-
const keyIdHex = Hex.hexDump(keyId);
|
22338
|
-
const {
|
22339
|
-
keyIdToKeySessionPromise,
|
22340
|
-
mediaKeySessions
|
22341
|
-
} = this;
|
22342
|
-
let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
|
22343
|
-
for (let i = 0; i < mediaKeySessions.length; i++) {
|
22344
|
-
// Match playlist key
|
22345
|
-
const keyContext = mediaKeySessions[i];
|
22346
|
-
const decryptdata = keyContext.decryptdata;
|
22347
|
-
if (!decryptdata.keyId) {
|
22348
|
-
continue;
|
22349
|
-
}
|
22350
|
-
const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
|
22351
|
-
if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
|
22352
|
-
keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
|
22353
|
-
if (decryptdata.pssh) {
|
22354
|
-
break;
|
22355
|
-
}
|
22356
|
-
delete keyIdToKeySessionPromise[oldKeyIdHex];
|
22357
|
-
decryptdata.pssh = new Uint8Array(initData);
|
22358
|
-
decryptdata.keyId = keyId;
|
22359
|
-
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
|
22360
|
-
return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
|
22361
|
-
});
|
22362
|
-
break;
|
22363
|
-
}
|
22364
|
-
}
|
22365
|
-
if (!keySessionContextPromise) {
|
22366
|
-
// Clear-lead key (not encountered in playlist)
|
22367
|
-
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
|
22368
|
-
keySystem,
|
22369
|
-
mediaKeys
|
22370
|
-
}) => {
|
22371
|
-
var _keySystemToKeySystem;
|
22372
|
-
this.throwIfDestroyed();
|
22373
|
-
const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
|
22374
|
-
decryptdata.pssh = new Uint8Array(initData);
|
22375
|
-
decryptdata.keyId = keyId;
|
22376
|
-
return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
|
22377
|
-
this.throwIfDestroyed();
|
22378
|
-
const keySessionContext = this.createMediaKeySessionContext({
|
22379
|
-
decryptdata,
|
22380
|
-
keySystem,
|
22381
|
-
mediaKeys
|
22382
|
-
});
|
22383
|
-
return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
|
22384
|
-
});
|
22385
|
-
});
|
22386
|
-
}
|
22387
|
-
keySessionContextPromise.catch(error => this.handleError(error));
|
22388
|
-
}
|
22389
|
-
_onWaitingForKey(event) {
|
22390
|
-
this.log(`"${event.type}" event`);
|
22391
|
-
}
|
22392
22446
|
attemptSetMediaKeys(keySystem, mediaKeys) {
|
22393
22447
|
const queue = this.setMediaKeysQueue.slice();
|
22394
22448
|
this.log(`Setting media-keys for "${keySystem}"`);
|
@@ -22681,7 +22735,7 @@ class EMEController {
|
|
22681
22735
|
requestLicense(keySessionContext, licenseChallenge) {
|
22682
22736
|
const keyLoadPolicy = this.config.keyLoadPolicy.default;
|
22683
22737
|
return new Promise((resolve, reject) => {
|
22684
|
-
const url = this.
|
22738
|
+
const url = this.getLicenseServerUrlOrThrow(keySessionContext.keySystem);
|
22685
22739
|
this.log(`Sending license request to URL: ${url}`);
|
22686
22740
|
const xhr = new XMLHttpRequest();
|
22687
22741
|
xhr.responseType = 'arraybuffer';
|
@@ -22751,6 +22805,8 @@ class EMEController {
|
|
22751
22805
|
|
22752
22806
|
// keep reference of media
|
22753
22807
|
this.media = media;
|
22808
|
+
media.removeEventListener('encrypted', this.onMediaEncrypted);
|
22809
|
+
media.removeEventListener('waitingforkey', this.onWaitingForKey);
|
22754
22810
|
media.addEventListener('encrypted', this.onMediaEncrypted);
|
22755
22811
|
media.addEventListener('waitingforkey', this.onWaitingForKey);
|
22756
22812
|
}
|
@@ -24704,47 +24760,51 @@ class XhrLoader {
|
|
24704
24760
|
xhr.onprogress = null;
|
24705
24761
|
const status = xhr.status;
|
24706
24762
|
// http status between 200 to 299 are all successful
|
24707
|
-
const
|
24708
|
-
if (status >= 200 && status < 300
|
24709
|
-
|
24710
|
-
|
24711
|
-
|
24712
|
-
|
24713
|
-
|
24714
|
-
|
24715
|
-
|
24716
|
-
|
24717
|
-
|
24718
|
-
|
24719
|
-
onProgress
|
24720
|
-
|
24721
|
-
|
24763
|
+
const useResponseText = xhr.responseType === 'text' ? xhr.responseText : null;
|
24764
|
+
if (status >= 200 && status < 300) {
|
24765
|
+
const data = useResponseText != null ? useResponseText : xhr.response;
|
24766
|
+
if (data != null) {
|
24767
|
+
stats.loading.end = Math.max(self.performance.now(), stats.loading.first);
|
24768
|
+
const len = xhr.responseType === 'arraybuffer' ? data.byteLength : data.length;
|
24769
|
+
stats.loaded = stats.total = len;
|
24770
|
+
stats.bwEstimate = stats.total * 8000 / (stats.loading.end - stats.loading.first);
|
24771
|
+
if (!this.callbacks) {
|
24772
|
+
return;
|
24773
|
+
}
|
24774
|
+
const onProgress = this.callbacks.onProgress;
|
24775
|
+
if (onProgress) {
|
24776
|
+
onProgress(stats, context, data, xhr);
|
24777
|
+
}
|
24778
|
+
if (!this.callbacks) {
|
24779
|
+
return;
|
24780
|
+
}
|
24781
|
+
const _response = {
|
24782
|
+
url: xhr.responseURL,
|
24783
|
+
data: data,
|
24784
|
+
code: status
|
24785
|
+
};
|
24786
|
+
this.callbacks.onSuccess(_response, stats, context, xhr);
|
24722
24787
|
return;
|
24723
24788
|
}
|
24724
|
-
|
24725
|
-
|
24726
|
-
|
24727
|
-
|
24728
|
-
|
24729
|
-
|
24789
|
+
}
|
24790
|
+
|
24791
|
+
// Handle bad status or nullish response
|
24792
|
+
const retryConfig = config.loadPolicy.errorRetry;
|
24793
|
+
const retryCount = stats.retry;
|
24794
|
+
// if max nb of retries reached or if http status between 400 and 499 (such error cannot be recovered, retrying is useless), return error
|
24795
|
+
const response = {
|
24796
|
+
url: context.url,
|
24797
|
+
data: undefined,
|
24798
|
+
code: status
|
24799
|
+
};
|
24800
|
+
if (shouldRetry(retryConfig, retryCount, false, response)) {
|
24801
|
+
this.retry(retryConfig);
|
24730
24802
|
} else {
|
24731
|
-
|
24732
|
-
|
24733
|
-
|
24734
|
-
|
24735
|
-
|
24736
|
-
data: undefined,
|
24737
|
-
code: status
|
24738
|
-
};
|
24739
|
-
if (shouldRetry(retryConfig, retryCount, false, response)) {
|
24740
|
-
this.retry(retryConfig);
|
24741
|
-
} else {
|
24742
|
-
logger.error(`${status} while loading ${context.url}`);
|
24743
|
-
this.callbacks.onError({
|
24744
|
-
code: status,
|
24745
|
-
text: xhr.statusText
|
24746
|
-
}, context, xhr, stats);
|
24747
|
-
}
|
24803
|
+
logger.error(`${status} while loading ${context.url}`);
|
24804
|
+
this.callbacks.onError({
|
24805
|
+
code: status,
|
24806
|
+
text: xhr.statusText
|
24807
|
+
}, context, xhr, stats);
|
24748
24808
|
}
|
24749
24809
|
}
|
24750
24810
|
}
|
@@ -26096,7 +26156,7 @@ class KeyLoader {
|
|
26096
26156
|
}
|
26097
26157
|
}
|
26098
26158
|
load(frag) {
|
26099
|
-
if (!frag.decryptdata && frag.encrypted && this.emeController) {
|
26159
|
+
if (!frag.decryptdata && frag.encrypted && this.emeController && this.config.emeEnabled) {
|
26100
26160
|
// Multiple keys, but none selected, resolve in eme-controller
|
26101
26161
|
return this.emeController.selectKeySystemFormat(frag).then(keySystemFormat => {
|
26102
26162
|
return this.loadInternal(frag, keySystemFormat);
|
@@ -26797,7 +26857,7 @@ class StreamController extends BaseStreamController {
|
|
26797
26857
|
if (this.altAudio && this.audioOnly) {
|
26798
26858
|
return;
|
26799
26859
|
}
|
26800
|
-
const level = hls.nextLoadLevel;
|
26860
|
+
const level = this.buffering ? hls.nextLoadLevel : hls.loadLevel;
|
26801
26861
|
if (!(levels != null && levels[level])) {
|
26802
26862
|
return;
|
26803
26863
|
}
|
@@ -26819,6 +26879,9 @@ class StreamController extends BaseStreamController {
|
|
26819
26879
|
this.state = State.ENDED;
|
26820
26880
|
return;
|
26821
26881
|
}
|
26882
|
+
if (!this.buffering) {
|
26883
|
+
return;
|
26884
|
+
}
|
26822
26885
|
|
26823
26886
|
// set next load level : this will trigger a playlist load if needed
|
26824
26887
|
if (hls.loadLevel !== level && hls.manualLevel === -1) {
|
@@ -27781,7 +27844,7 @@ class Hls {
|
|
27781
27844
|
* Get the video-dev/hls.js package version.
|
27782
27845
|
*/
|
27783
27846
|
static get version() {
|
27784
|
-
return "1.5.
|
27847
|
+
return "1.5.19";
|
27785
27848
|
}
|
27786
27849
|
|
27787
27850
|
/**
|
@@ -28062,9 +28125,13 @@ class Hls {
|
|
28062
28125
|
startLoad(startPosition = -1) {
|
28063
28126
|
logger.log(`startLoad(${startPosition})`);
|
28064
28127
|
this.started = true;
|
28065
|
-
this.
|
28066
|
-
|
28067
|
-
|
28128
|
+
this.resumeBuffering();
|
28129
|
+
for (let i = 0; i < this.networkControllers.length; i++) {
|
28130
|
+
this.networkControllers[i].startLoad(startPosition);
|
28131
|
+
if (!this.started || !this.networkControllers) {
|
28132
|
+
break;
|
28133
|
+
}
|
28134
|
+
}
|
28068
28135
|
}
|
28069
28136
|
|
28070
28137
|
/**
|
@@ -28073,32 +28140,35 @@ class Hls {
|
|
28073
28140
|
stopLoad() {
|
28074
28141
|
logger.log('stopLoad');
|
28075
28142
|
this.started = false;
|
28076
|
-
this.networkControllers.
|
28077
|
-
|
28078
|
-
|
28143
|
+
for (let i = 0; i < this.networkControllers.length; i++) {
|
28144
|
+
this.networkControllers[i].stopLoad();
|
28145
|
+
if (this.started || !this.networkControllers) {
|
28146
|
+
break;
|
28147
|
+
}
|
28148
|
+
}
|
28079
28149
|
}
|
28080
28150
|
|
28081
28151
|
/**
|
28082
|
-
* Resumes stream controller segment loading
|
28152
|
+
* Resumes stream controller segment loading after `pauseBuffering` has been called.
|
28083
28153
|
*/
|
28084
28154
|
resumeBuffering() {
|
28085
|
-
|
28086
|
-
|
28087
|
-
|
28088
|
-
|
28089
|
-
|
28090
|
-
|
28091
|
-
}
|
28155
|
+
logger.log(`resume buffering`);
|
28156
|
+
this.networkControllers.forEach(controller => {
|
28157
|
+
if (controller.resumeBuffering) {
|
28158
|
+
controller.resumeBuffering();
|
28159
|
+
}
|
28160
|
+
});
|
28092
28161
|
}
|
28093
28162
|
|
28094
28163
|
/**
|
28095
|
-
*
|
28164
|
+
* Prevents stream controller from loading new segments until `resumeBuffering` is called.
|
28096
28165
|
* This allows for media buffering to be paused without interupting playlist loading.
|
28097
28166
|
*/
|
28098
28167
|
pauseBuffering() {
|
28168
|
+
logger.log(`pause buffering`);
|
28099
28169
|
this.networkControllers.forEach(controller => {
|
28100
|
-
if (
|
28101
|
-
controller.
|
28170
|
+
if (controller.pauseBuffering) {
|
28171
|
+
controller.pauseBuffering();
|
28102
28172
|
}
|
28103
28173
|
});
|
28104
28174
|
}
|