hls.js 1.5.2-0.canary.9923 → 1.5.2
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-demo.js +0 -5
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +379 -431
- package/dist/hls.js.d.ts +14 -14
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +217 -275
- 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 +219 -278
- 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 +350 -401
- 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 +9 -9
- package/src/controller/abr-controller.ts +2 -2
- package/src/controller/base-stream-controller.ts +16 -16
- package/src/controller/buffer-controller.ts +0 -6
- package/src/controller/eme-controller.ts +12 -6
- package/src/controller/latency-controller.ts +8 -7
- package/src/controller/level-controller.ts +18 -7
- package/src/controller/stream-controller.ts +14 -11
- package/src/controller/subtitle-stream-controller.ts +1 -6
- package/src/controller/subtitle-track-controller.ts +2 -4
- package/src/controller/timeline-controller.ts +26 -20
- package/src/crypt/aes-crypto.ts +2 -21
- package/src/crypt/decrypter.ts +18 -32
- package/src/crypt/fast-aes-key.ts +5 -24
- package/src/demux/audio/adts.ts +4 -9
- package/src/demux/sample-aes.ts +0 -2
- package/src/demux/transmuxer-interface.ts +12 -4
- package/src/demux/transmuxer.ts +3 -16
- package/src/demux/tsdemuxer.ts +17 -12
- package/src/loader/fragment-loader.ts +2 -9
- package/src/loader/key-loader.ts +0 -2
- package/src/loader/level-key.ts +9 -10
- package/src/remux/mp4-remuxer.ts +3 -4
- package/src/types/demuxer.ts +0 -1
- package/src/utils/codecs.ts +4 -33
- package/src/crypt/decrypter-aes-mode.ts +0 -4
- package/src/utils/encryption-methods-util.ts +0 -21
package/dist/hls.light.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.2
|
414
|
+
exportedLogger.log(`Debug logs enabled for "${id}" in hls.js version ${"1.5.2"}`);
|
415
415
|
} catch (e) {
|
416
416
|
exportedLogger = fakeLogger;
|
417
417
|
}
|
@@ -991,26 +991,6 @@ class LevelDetails {
|
|
991
991
|
}
|
992
992
|
}
|
993
993
|
|
994
|
-
var DecrypterAesMode = {
|
995
|
-
cbc: 0,
|
996
|
-
ctr: 1
|
997
|
-
};
|
998
|
-
|
999
|
-
function isFullSegmentEncryption(method) {
|
1000
|
-
return method === 'AES-128' || method === 'AES-256' || method === 'AES-256-CTR';
|
1001
|
-
}
|
1002
|
-
function getAesModeFromFullSegmentMethod(method) {
|
1003
|
-
switch (method) {
|
1004
|
-
case 'AES-128':
|
1005
|
-
case 'AES-256':
|
1006
|
-
return DecrypterAesMode.cbc;
|
1007
|
-
case 'AES-256-CTR':
|
1008
|
-
return DecrypterAesMode.ctr;
|
1009
|
-
default:
|
1010
|
-
throw new Error(`invalid full segment method ${method}`);
|
1011
|
-
}
|
1012
|
-
}
|
1013
|
-
|
1014
994
|
// This file is inserted as a shim for modules which we do not want to include into the distro.
|
1015
995
|
// This replacement is done in the "alias" plugin of the rollup config.
|
1016
996
|
var empty = undefined;
|
@@ -2439,12 +2419,12 @@ class LevelKey {
|
|
2439
2419
|
this.keyFormatVersions = formatversions;
|
2440
2420
|
this.iv = iv;
|
2441
2421
|
this.encrypted = method ? method !== 'NONE' : false;
|
2442
|
-
this.isCommonEncryption = this.encrypted &&
|
2422
|
+
this.isCommonEncryption = this.encrypted && method !== 'AES-128';
|
2443
2423
|
}
|
2444
2424
|
isSupported() {
|
2445
2425
|
// If it's Segment encryption or No encryption, just select that key system
|
2446
2426
|
if (this.method) {
|
2447
|
-
if (
|
2427
|
+
if (this.method === 'AES-128' || this.method === 'NONE') {
|
2448
2428
|
return true;
|
2449
2429
|
}
|
2450
2430
|
if (this.keyFormat === 'identity') {
|
@@ -2458,13 +2438,14 @@ class LevelKey {
|
|
2458
2438
|
if (!this.encrypted || !this.uri) {
|
2459
2439
|
return null;
|
2460
2440
|
}
|
2461
|
-
if (
|
2441
|
+
if (this.method === 'AES-128' && this.uri && !this.iv) {
|
2462
2442
|
if (typeof sn !== 'number') {
|
2463
2443
|
// We are fetching decryption data for a initialization segment
|
2464
|
-
// If the segment was encrypted with AES-128
|
2444
|
+
// If the segment was encrypted with AES-128
|
2465
2445
|
// It must have an IV defined. We cannot substitute the Segment Number in.
|
2466
|
-
|
2467
|
-
|
2446
|
+
if (this.method === 'AES-128' && !this.iv) {
|
2447
|
+
logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
|
2448
|
+
}
|
2468
2449
|
// Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
|
2469
2450
|
sn = 0;
|
2470
2451
|
}
|
@@ -2611,28 +2592,23 @@ function getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource =
|
|
2611
2592
|
if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) {
|
2612
2593
|
return CODEC_COMPATIBLE_NAMES[lowerCaseCodec];
|
2613
2594
|
}
|
2595
|
+
|
2596
|
+
// Idealy fLaC and Opus would be first (spec-compliant) but
|
2597
|
+
// some browsers will report that fLaC is supported then fail.
|
2598
|
+
// see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
|
2614
2599
|
const codecsToCheck = {
|
2615
|
-
// Idealy fLaC and Opus would be first (spec-compliant) but
|
2616
|
-
// some browsers will report that fLaC is supported then fail.
|
2617
|
-
// see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
|
2618
2600
|
flac: ['flac', 'fLaC', 'FLAC'],
|
2619
|
-
opus: ['opus', 'Opus']
|
2620
|
-
// Replace audio codec info if browser does not support mp4a.40.34,
|
2621
|
-
// and demuxer can fallback to 'audio/mpeg' or 'audio/mp4;codecs="mp3"'
|
2622
|
-
'mp4a.40.34': ['mp3']
|
2601
|
+
opus: ['opus', 'Opus']
|
2623
2602
|
}[lowerCaseCodec];
|
2624
2603
|
for (let i = 0; i < codecsToCheck.length; i++) {
|
2625
|
-
var _getMediaSource;
|
2626
2604
|
if (isCodecMediaSourceSupported(codecsToCheck[i], 'audio', preferManagedMediaSource)) {
|
2627
2605
|
CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
|
2628
2606
|
return codecsToCheck[i];
|
2629
|
-
} else if (codecsToCheck[i] === 'mp3' && (_getMediaSource = getMediaSource(preferManagedMediaSource)) != null && _getMediaSource.isTypeSupported('audio/mpeg')) {
|
2630
|
-
return '';
|
2631
2607
|
}
|
2632
2608
|
}
|
2633
2609
|
return lowerCaseCodec;
|
2634
2610
|
}
|
2635
|
-
const AUDIO_CODEC_REGEXP = /flac|opus
|
2611
|
+
const AUDIO_CODEC_REGEXP = /flac|opus/i;
|
2636
2612
|
function getCodecCompatibleName(codec, preferManagedMediaSource = true) {
|
2637
2613
|
return codec.replace(AUDIO_CODEC_REGEXP, m => getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource));
|
2638
2614
|
}
|
@@ -2655,16 +2631,6 @@ function convertAVC1ToAVCOTI(codec) {
|
|
2655
2631
|
}
|
2656
2632
|
return codec;
|
2657
2633
|
}
|
2658
|
-
function getM2TSSupportedAudioTypes(preferManagedMediaSource) {
|
2659
|
-
const MediaSource = getMediaSource(preferManagedMediaSource) || {
|
2660
|
-
isTypeSupported: () => false
|
2661
|
-
};
|
2662
|
-
return {
|
2663
|
-
mpeg: MediaSource.isTypeSupported('audio/mpeg'),
|
2664
|
-
mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
|
2665
|
-
ac3: false
|
2666
|
-
};
|
2667
|
-
}
|
2668
2634
|
|
2669
2635
|
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;
|
2670
2636
|
const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g;
|
@@ -4242,47 +4208,7 @@ class LatencyController {
|
|
4242
4208
|
this.currentTime = 0;
|
4243
4209
|
this.stallCount = 0;
|
4244
4210
|
this._latency = null;
|
4245
|
-
this.
|
4246
|
-
const {
|
4247
|
-
media,
|
4248
|
-
levelDetails
|
4249
|
-
} = this;
|
4250
|
-
if (!media || !levelDetails) {
|
4251
|
-
return;
|
4252
|
-
}
|
4253
|
-
this.currentTime = media.currentTime;
|
4254
|
-
const latency = this.computeLatency();
|
4255
|
-
if (latency === null) {
|
4256
|
-
return;
|
4257
|
-
}
|
4258
|
-
this._latency = latency;
|
4259
|
-
|
4260
|
-
// Adapt playbackRate to meet target latency in low-latency mode
|
4261
|
-
const {
|
4262
|
-
lowLatencyMode,
|
4263
|
-
maxLiveSyncPlaybackRate
|
4264
|
-
} = this.config;
|
4265
|
-
if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
|
4266
|
-
return;
|
4267
|
-
}
|
4268
|
-
const targetLatency = this.targetLatency;
|
4269
|
-
if (targetLatency === null) {
|
4270
|
-
return;
|
4271
|
-
}
|
4272
|
-
const distanceFromTarget = latency - targetLatency;
|
4273
|
-
// Only adjust playbackRate when within one target duration of targetLatency
|
4274
|
-
// and more than one second from under-buffering.
|
4275
|
-
// Playback further than one target duration from target can be considered DVR playback.
|
4276
|
-
const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
|
4277
|
-
const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
|
4278
|
-
if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
|
4279
|
-
const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
|
4280
|
-
const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
|
4281
|
-
media.playbackRate = Math.min(max, Math.max(1, rate));
|
4282
|
-
} else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
|
4283
|
-
media.playbackRate = 1;
|
4284
|
-
}
|
4285
|
-
};
|
4211
|
+
this.timeupdateHandler = () => this.timeupdate();
|
4286
4212
|
this.hls = hls;
|
4287
4213
|
this.config = hls.config;
|
4288
4214
|
this.registerListeners();
|
@@ -4374,7 +4300,7 @@ class LatencyController {
|
|
4374
4300
|
this.onMediaDetaching();
|
4375
4301
|
this.levelDetails = null;
|
4376
4302
|
// @ts-ignore
|
4377
|
-
this.hls = this.
|
4303
|
+
this.hls = this.timeupdateHandler = null;
|
4378
4304
|
}
|
4379
4305
|
registerListeners() {
|
4380
4306
|
this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
@@ -4392,11 +4318,11 @@ class LatencyController {
|
|
4392
4318
|
}
|
4393
4319
|
onMediaAttached(event, data) {
|
4394
4320
|
this.media = data.media;
|
4395
|
-
this.media.addEventListener('timeupdate', this.
|
4321
|
+
this.media.addEventListener('timeupdate', this.timeupdateHandler);
|
4396
4322
|
}
|
4397
4323
|
onMediaDetaching() {
|
4398
4324
|
if (this.media) {
|
4399
|
-
this.media.removeEventListener('timeupdate', this.
|
4325
|
+
this.media.removeEventListener('timeupdate', this.timeupdateHandler);
|
4400
4326
|
this.media = null;
|
4401
4327
|
}
|
4402
4328
|
}
|
@@ -4410,10 +4336,10 @@ class LatencyController {
|
|
4410
4336
|
}) {
|
4411
4337
|
this.levelDetails = details;
|
4412
4338
|
if (details.advanced) {
|
4413
|
-
this.
|
4339
|
+
this.timeupdate();
|
4414
4340
|
}
|
4415
4341
|
if (!details.live && this.media) {
|
4416
|
-
this.media.removeEventListener('timeupdate', this.
|
4342
|
+
this.media.removeEventListener('timeupdate', this.timeupdateHandler);
|
4417
4343
|
}
|
4418
4344
|
}
|
4419
4345
|
onError(event, data) {
|
@@ -4426,6 +4352,47 @@ class LatencyController {
|
|
4426
4352
|
logger.warn('[playback-rate-controller]: Stall detected, adjusting target latency');
|
4427
4353
|
}
|
4428
4354
|
}
|
4355
|
+
timeupdate() {
|
4356
|
+
const {
|
4357
|
+
media,
|
4358
|
+
levelDetails
|
4359
|
+
} = this;
|
4360
|
+
if (!media || !levelDetails) {
|
4361
|
+
return;
|
4362
|
+
}
|
4363
|
+
this.currentTime = media.currentTime;
|
4364
|
+
const latency = this.computeLatency();
|
4365
|
+
if (latency === null) {
|
4366
|
+
return;
|
4367
|
+
}
|
4368
|
+
this._latency = latency;
|
4369
|
+
|
4370
|
+
// Adapt playbackRate to meet target latency in low-latency mode
|
4371
|
+
const {
|
4372
|
+
lowLatencyMode,
|
4373
|
+
maxLiveSyncPlaybackRate
|
4374
|
+
} = this.config;
|
4375
|
+
if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
|
4376
|
+
return;
|
4377
|
+
}
|
4378
|
+
const targetLatency = this.targetLatency;
|
4379
|
+
if (targetLatency === null) {
|
4380
|
+
return;
|
4381
|
+
}
|
4382
|
+
const distanceFromTarget = latency - targetLatency;
|
4383
|
+
// Only adjust playbackRate when within one target duration of targetLatency
|
4384
|
+
// and more than one second from under-buffering.
|
4385
|
+
// Playback further than one target duration from target can be considered DVR playback.
|
4386
|
+
const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
|
4387
|
+
const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
|
4388
|
+
if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
|
4389
|
+
const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
|
4390
|
+
const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
|
4391
|
+
media.playbackRate = Math.min(max, Math.max(1, rate));
|
4392
|
+
} else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
|
4393
|
+
media.playbackRate = 1;
|
4394
|
+
}
|
4395
|
+
}
|
4429
4396
|
estimateLiveEdge() {
|
4430
4397
|
const {
|
4431
4398
|
levelDetails
|
@@ -6213,7 +6180,7 @@ class AbrController {
|
|
6213
6180
|
const bwEstimate = this.getBwEstimate();
|
6214
6181
|
const levels = hls.levels;
|
6215
6182
|
const level = levels[frag.level];
|
6216
|
-
const expectedLen = stats.total || Math.max(stats.loaded, Math.round(duration * level.
|
6183
|
+
const expectedLen = stats.total || Math.max(stats.loaded, Math.round(duration * level.averageBitrate / 8));
|
6217
6184
|
let timeStreaming = loadedFirstByte ? timeLoading - ttfb : timeLoading;
|
6218
6185
|
if (timeStreaming < 1 && loadedFirstByte) {
|
6219
6186
|
timeStreaming = Math.min(timeLoading, stats.loaded * 8 / bwEstimate);
|
@@ -6256,7 +6223,7 @@ class AbrController {
|
|
6256
6223
|
// If there has been no loading progress, sample TTFB
|
6257
6224
|
this.bwEstimator.sampleTTFB(timeLoading);
|
6258
6225
|
}
|
6259
|
-
const nextLoadLevelBitrate = levels[nextLoadLevel].
|
6226
|
+
const nextLoadLevelBitrate = levels[nextLoadLevel].maxBitrate;
|
6260
6227
|
if (this.getBwEstimate() * this.hls.config.abrBandWidthUpFactor > nextLoadLevelBitrate) {
|
6261
6228
|
this.resetEstimator(nextLoadLevelBitrate);
|
6262
6229
|
}
|
@@ -7053,12 +7020,6 @@ class BufferController {
|
|
7053
7020
|
this.lastMpegAudioChunk = null;
|
7054
7021
|
// @ts-ignore
|
7055
7022
|
this.hls = null;
|
7056
|
-
// @ts-ignore
|
7057
|
-
this._onMediaSourceOpen = this._onMediaSourceClose = null;
|
7058
|
-
// @ts-ignore
|
7059
|
-
this._onMediaSourceEnded = null;
|
7060
|
-
// @ts-ignore
|
7061
|
-
this._onStartStreaming = this._onEndStreaming = null;
|
7062
7023
|
}
|
7063
7024
|
registerListeners() {
|
7064
7025
|
const {
|
@@ -9611,6 +9572,7 @@ function enableStreamingMode(config) {
|
|
9611
9572
|
}
|
9612
9573
|
}
|
9613
9574
|
|
9575
|
+
let chromeOrFirefox;
|
9614
9576
|
class LevelController extends BasePlaylistController {
|
9615
9577
|
constructor(hls, contentSteeringController) {
|
9616
9578
|
super(hls, '[level-controller]');
|
@@ -9684,15 +9646,23 @@ class LevelController extends BasePlaylistController {
|
|
9684
9646
|
let videoCodecFound = false;
|
9685
9647
|
let audioCodecFound = false;
|
9686
9648
|
data.levels.forEach(levelParsed => {
|
9687
|
-
var _videoCodec;
|
9649
|
+
var _audioCodec, _videoCodec;
|
9688
9650
|
const attributes = levelParsed.attrs;
|
9651
|
+
|
9652
|
+
// erase audio codec info if browser does not support mp4a.40.34.
|
9653
|
+
// demuxer will autodetect codec and fallback to mpeg/audio
|
9689
9654
|
let {
|
9690
9655
|
audioCodec,
|
9691
9656
|
videoCodec
|
9692
9657
|
} = levelParsed;
|
9658
|
+
if (((_audioCodec = audioCodec) == null ? void 0 : _audioCodec.indexOf('mp4a.40.34')) !== -1) {
|
9659
|
+
chromeOrFirefox || (chromeOrFirefox = /chrome|firefox/i.test(navigator.userAgent));
|
9660
|
+
if (chromeOrFirefox) {
|
9661
|
+
levelParsed.audioCodec = audioCodec = undefined;
|
9662
|
+
}
|
9663
|
+
}
|
9693
9664
|
if (audioCodec) {
|
9694
|
-
|
9695
|
-
levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource) || undefined;
|
9665
|
+
levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource);
|
9696
9666
|
}
|
9697
9667
|
if (((_videoCodec = videoCodec) == null ? void 0 : _videoCodec.indexOf('avc1')) === 0) {
|
9698
9668
|
videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
|
@@ -9817,8 +9787,8 @@ class LevelController extends BasePlaylistController {
|
|
9817
9787
|
return valueB - valueA;
|
9818
9788
|
}
|
9819
9789
|
}
|
9820
|
-
if (a.
|
9821
|
-
return a.
|
9790
|
+
if (a.averageBitrate !== b.averageBitrate) {
|
9791
|
+
return a.averageBitrate - b.averageBitrate;
|
9822
9792
|
}
|
9823
9793
|
return 0;
|
9824
9794
|
});
|
@@ -10820,8 +10790,8 @@ function createLoaderContext(frag, part = null) {
|
|
10820
10790
|
var _frag$decryptdata;
|
10821
10791
|
let byteRangeStart = start;
|
10822
10792
|
let byteRangeEnd = end;
|
10823
|
-
if (frag.sn === 'initSegment' &&
|
10824
|
-
// MAP segment encrypted with method 'AES-128'
|
10793
|
+
if (frag.sn === 'initSegment' && ((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method) === 'AES-128') {
|
10794
|
+
// MAP segment encrypted with method 'AES-128', when served with HTTP Range,
|
10825
10795
|
// has the unencrypted size specified in the range.
|
10826
10796
|
// Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
|
10827
10797
|
const fragmentLen = end - start;
|
@@ -10854,9 +10824,6 @@ function createGapLoadError(frag, part) {
|
|
10854
10824
|
(part ? part : frag).stats.aborted = true;
|
10855
10825
|
return new LoadError(errorData);
|
10856
10826
|
}
|
10857
|
-
function isMethodFullSegmentAesCbc(method) {
|
10858
|
-
return method === 'AES-128' || method === 'AES-256';
|
10859
|
-
}
|
10860
10827
|
class LoadError extends Error {
|
10861
10828
|
constructor(data) {
|
10862
10829
|
super(data.error.message);
|
@@ -11002,8 +10969,6 @@ class KeyLoader {
|
|
11002
10969
|
}
|
11003
10970
|
return this.loadKeyEME(keyInfo, frag);
|
11004
10971
|
case 'AES-128':
|
11005
|
-
case 'AES-256':
|
11006
|
-
case 'AES-256-CTR':
|
11007
10972
|
return this.loadKeyHTTP(keyInfo, frag);
|
11008
10973
|
default:
|
11009
10974
|
return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`)));
|
@@ -11408,61 +11373,33 @@ function alignMediaPlaylistByPDT(details, refDetails) {
|
|
11408
11373
|
}
|
11409
11374
|
|
11410
11375
|
class AESCrypto {
|
11411
|
-
constructor(subtle, iv
|
11376
|
+
constructor(subtle, iv) {
|
11412
11377
|
this.subtle = void 0;
|
11413
11378
|
this.aesIV = void 0;
|
11414
|
-
this.aesMode = void 0;
|
11415
11379
|
this.subtle = subtle;
|
11416
11380
|
this.aesIV = iv;
|
11417
|
-
this.aesMode = aesMode;
|
11418
11381
|
}
|
11419
11382
|
decrypt(data, key) {
|
11420
|
-
|
11421
|
-
|
11422
|
-
|
11423
|
-
|
11424
|
-
iv: this.aesIV
|
11425
|
-
}, key, data);
|
11426
|
-
case DecrypterAesMode.ctr:
|
11427
|
-
return this.subtle.decrypt({
|
11428
|
-
name: 'AES-CTR',
|
11429
|
-
counter: this.aesIV,
|
11430
|
-
length: 64
|
11431
|
-
},
|
11432
|
-
//64 : NIST SP800-38A standard suggests that the counter should occupy half of the counter block
|
11433
|
-
key, data);
|
11434
|
-
default:
|
11435
|
-
throw new Error(`[AESCrypto] invalid aes mode ${this.aesMode}`);
|
11436
|
-
}
|
11383
|
+
return this.subtle.decrypt({
|
11384
|
+
name: 'AES-CBC',
|
11385
|
+
iv: this.aesIV
|
11386
|
+
}, key, data);
|
11437
11387
|
}
|
11438
11388
|
}
|
11439
11389
|
|
11440
11390
|
class FastAESKey {
|
11441
|
-
constructor(subtle, key
|
11391
|
+
constructor(subtle, key) {
|
11442
11392
|
this.subtle = void 0;
|
11443
11393
|
this.key = void 0;
|
11444
|
-
this.aesMode = void 0;
|
11445
11394
|
this.subtle = subtle;
|
11446
11395
|
this.key = key;
|
11447
|
-
this.aesMode = aesMode;
|
11448
11396
|
}
|
11449
11397
|
expandKey() {
|
11450
|
-
const subtleAlgoName = getSubtleAlgoName(this.aesMode);
|
11451
11398
|
return this.subtle.importKey('raw', this.key, {
|
11452
|
-
name:
|
11399
|
+
name: 'AES-CBC'
|
11453
11400
|
}, false, ['encrypt', 'decrypt']);
|
11454
11401
|
}
|
11455
11402
|
}
|
11456
|
-
function getSubtleAlgoName(aesMode) {
|
11457
|
-
switch (aesMode) {
|
11458
|
-
case DecrypterAesMode.cbc:
|
11459
|
-
return 'AES-CBC';
|
11460
|
-
case DecrypterAesMode.ctr:
|
11461
|
-
return 'AES-CTR';
|
11462
|
-
default:
|
11463
|
-
throw new Error(`[FastAESKey] invalid aes mode ${aesMode}`);
|
11464
|
-
}
|
11465
|
-
}
|
11466
11403
|
|
11467
11404
|
// PKCS7
|
11468
11405
|
function removePadding(array) {
|
@@ -11712,8 +11649,7 @@ class Decrypter {
|
|
11712
11649
|
this.currentIV = null;
|
11713
11650
|
this.currentResult = null;
|
11714
11651
|
this.useSoftware = void 0;
|
11715
|
-
this.
|
11716
|
-
this.enableSoftwareAES = config.enableSoftwareAES;
|
11652
|
+
this.useSoftware = config.enableSoftwareAES;
|
11717
11653
|
this.removePKCS7Padding = removePKCS7Padding;
|
11718
11654
|
// built in decryptor expects PKCS7 padding
|
11719
11655
|
if (removePKCS7Padding) {
|
@@ -11726,7 +11662,9 @@ class Decrypter {
|
|
11726
11662
|
/* no-op */
|
11727
11663
|
}
|
11728
11664
|
}
|
11729
|
-
|
11665
|
+
if (this.subtle === null) {
|
11666
|
+
this.useSoftware = true;
|
11667
|
+
}
|
11730
11668
|
}
|
11731
11669
|
destroy() {
|
11732
11670
|
this.subtle = null;
|
@@ -11764,10 +11702,10 @@ class Decrypter {
|
|
11764
11702
|
this.softwareDecrypter = null;
|
11765
11703
|
}
|
11766
11704
|
}
|
11767
|
-
decrypt(data, key, iv
|
11705
|
+
decrypt(data, key, iv) {
|
11768
11706
|
if (this.useSoftware) {
|
11769
11707
|
return new Promise((resolve, reject) => {
|
11770
|
-
this.softwareDecrypt(new Uint8Array(data), key, iv
|
11708
|
+
this.softwareDecrypt(new Uint8Array(data), key, iv);
|
11771
11709
|
const decryptResult = this.flush();
|
11772
11710
|
if (decryptResult) {
|
11773
11711
|
resolve(decryptResult.buffer);
|
@@ -11776,21 +11714,17 @@ class Decrypter {
|
|
11776
11714
|
}
|
11777
11715
|
});
|
11778
11716
|
}
|
11779
|
-
return this.webCryptoDecrypt(new Uint8Array(data), key, iv
|
11717
|
+
return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
|
11780
11718
|
}
|
11781
11719
|
|
11782
11720
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
11783
11721
|
// data is handled in the flush() call
|
11784
|
-
softwareDecrypt(data, key, iv
|
11722
|
+
softwareDecrypt(data, key, iv) {
|
11785
11723
|
const {
|
11786
11724
|
currentIV,
|
11787
11725
|
currentResult,
|
11788
11726
|
remainderData
|
11789
11727
|
} = this;
|
11790
|
-
if (aesMode !== DecrypterAesMode.cbc || key.byteLength !== 16) {
|
11791
|
-
logger.warn('SoftwareDecrypt: can only handle AES-128-CBC');
|
11792
|
-
return null;
|
11793
|
-
}
|
11794
11728
|
this.logOnce('JS AES decrypt');
|
11795
11729
|
// The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
|
11796
11730
|
// This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached
|
@@ -11823,11 +11757,11 @@ class Decrypter {
|
|
11823
11757
|
}
|
11824
11758
|
return result;
|
11825
11759
|
}
|
11826
|
-
webCryptoDecrypt(data, key, iv
|
11760
|
+
webCryptoDecrypt(data, key, iv) {
|
11827
11761
|
const subtle = this.subtle;
|
11828
11762
|
if (this.key !== key || !this.fastAesKey) {
|
11829
11763
|
this.key = key;
|
11830
|
-
this.fastAesKey = new FastAESKey(subtle, key
|
11764
|
+
this.fastAesKey = new FastAESKey(subtle, key);
|
11831
11765
|
}
|
11832
11766
|
return this.fastAesKey.expandKey().then(aesKey => {
|
11833
11767
|
// decrypt using web crypto
|
@@ -11835,25 +11769,22 @@ class Decrypter {
|
|
11835
11769
|
return Promise.reject(new Error('web crypto not initialized'));
|
11836
11770
|
}
|
11837
11771
|
this.logOnce('WebCrypto AES decrypt');
|
11838
|
-
const crypto = new AESCrypto(subtle, new Uint8Array(iv)
|
11772
|
+
const crypto = new AESCrypto(subtle, new Uint8Array(iv));
|
11839
11773
|
return crypto.decrypt(data.buffer, aesKey);
|
11840
11774
|
}).catch(err => {
|
11841
11775
|
logger.warn(`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`);
|
11842
|
-
return this.onWebCryptoError(data, key, iv
|
11776
|
+
return this.onWebCryptoError(data, key, iv);
|
11843
11777
|
});
|
11844
11778
|
}
|
11845
|
-
onWebCryptoError(data, key, iv
|
11846
|
-
|
11847
|
-
|
11848
|
-
|
11849
|
-
|
11850
|
-
|
11851
|
-
|
11852
|
-
if (decryptResult) {
|
11853
|
-
return decryptResult.buffer;
|
11854
|
-
}
|
11779
|
+
onWebCryptoError(data, key, iv) {
|
11780
|
+
this.useSoftware = true;
|
11781
|
+
this.logEnabled = true;
|
11782
|
+
this.softwareDecrypt(data, key, iv);
|
11783
|
+
const decryptResult = this.flush();
|
11784
|
+
if (decryptResult) {
|
11785
|
+
return decryptResult.buffer;
|
11855
11786
|
}
|
11856
|
-
throw new Error('WebCrypto
|
11787
|
+
throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data');
|
11857
11788
|
}
|
11858
11789
|
getValidChunk(data) {
|
11859
11790
|
let currentChunk = data;
|
@@ -11929,59 +11860,11 @@ class BaseStreamController extends TaskLoop {
|
|
11929
11860
|
this.startFragRequested = false;
|
11930
11861
|
this.decrypter = void 0;
|
11931
11862
|
this.initPTS = [];
|
11863
|
+
this.onvseeking = null;
|
11864
|
+
this.onvended = null;
|
11932
11865
|
this.logPrefix = '';
|
11933
11866
|
this.log = void 0;
|
11934
11867
|
this.warn = void 0;
|
11935
|
-
this.onMediaSeeking = () => {
|
11936
|
-
const {
|
11937
|
-
config,
|
11938
|
-
fragCurrent,
|
11939
|
-
media,
|
11940
|
-
mediaBuffer,
|
11941
|
-
state
|
11942
|
-
} = this;
|
11943
|
-
const currentTime = media ? media.currentTime : 0;
|
11944
|
-
const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
|
11945
|
-
this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
|
11946
|
-
if (this.state === State.ENDED) {
|
11947
|
-
this.resetLoadingState();
|
11948
|
-
} else if (fragCurrent) {
|
11949
|
-
// Seeking while frag load is in progress
|
11950
|
-
const tolerance = config.maxFragLookUpTolerance;
|
11951
|
-
const fragStartOffset = fragCurrent.start - tolerance;
|
11952
|
-
const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
|
11953
|
-
// if seeking out of buffered range or into new one
|
11954
|
-
if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
|
11955
|
-
const pastFragment = currentTime > fragEndOffset;
|
11956
|
-
// if the seek position is outside the current fragment range
|
11957
|
-
if (currentTime < fragStartOffset || pastFragment) {
|
11958
|
-
if (pastFragment && fragCurrent.loader) {
|
11959
|
-
this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
|
11960
|
-
fragCurrent.abortRequests();
|
11961
|
-
this.resetLoadingState();
|
11962
|
-
}
|
11963
|
-
this.fragPrevious = null;
|
11964
|
-
}
|
11965
|
-
}
|
11966
|
-
}
|
11967
|
-
if (media) {
|
11968
|
-
// Remove gap fragments
|
11969
|
-
this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
|
11970
|
-
this.lastCurrentTime = currentTime;
|
11971
|
-
}
|
11972
|
-
|
11973
|
-
// in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
|
11974
|
-
if (!this.loadedmetadata && !bufferInfo.len) {
|
11975
|
-
this.nextLoadPosition = this.startPosition = currentTime;
|
11976
|
-
}
|
11977
|
-
|
11978
|
-
// Async tick to speed up processing
|
11979
|
-
this.tickImmediate();
|
11980
|
-
};
|
11981
|
-
this.onMediaEnded = () => {
|
11982
|
-
// reset startPosition and lastCurrentTime to restart playback @ stream beginning
|
11983
|
-
this.startPosition = this.lastCurrentTime = 0;
|
11984
|
-
};
|
11985
11868
|
this.playlistType = playlistType;
|
11986
11869
|
this.logPrefix = logPrefix;
|
11987
11870
|
this.log = logger.log.bind(logger, `${logPrefix}:`);
|
@@ -12046,8 +11929,10 @@ class BaseStreamController extends TaskLoop {
|
|
12046
11929
|
}
|
12047
11930
|
onMediaAttached(event, data) {
|
12048
11931
|
const media = this.media = this.mediaBuffer = data.media;
|
12049
|
-
|
12050
|
-
|
11932
|
+
this.onvseeking = this.onMediaSeeking.bind(this);
|
11933
|
+
this.onvended = this.onMediaEnded.bind(this);
|
11934
|
+
media.addEventListener('seeking', this.onvseeking);
|
11935
|
+
media.addEventListener('ended', this.onvended);
|
12051
11936
|
const config = this.config;
|
12052
11937
|
if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
|
12053
11938
|
this.startLoad(config.startPosition);
|
@@ -12061,9 +11946,10 @@ class BaseStreamController extends TaskLoop {
|
|
12061
11946
|
}
|
12062
11947
|
|
12063
11948
|
// remove video listeners
|
12064
|
-
if (media) {
|
12065
|
-
media.removeEventListener('seeking', this.
|
12066
|
-
media.removeEventListener('ended', this.
|
11949
|
+
if (media && this.onvseeking && this.onvended) {
|
11950
|
+
media.removeEventListener('seeking', this.onvseeking);
|
11951
|
+
media.removeEventListener('ended', this.onvended);
|
11952
|
+
this.onvseeking = this.onvended = null;
|
12067
11953
|
}
|
12068
11954
|
if (this.keyLoader) {
|
12069
11955
|
this.keyLoader.detach();
|
@@ -12073,6 +11959,56 @@ class BaseStreamController extends TaskLoop {
|
|
12073
11959
|
this.fragmentTracker.removeAllFragments();
|
12074
11960
|
this.stopLoad();
|
12075
11961
|
}
|
11962
|
+
onMediaSeeking() {
|
11963
|
+
const {
|
11964
|
+
config,
|
11965
|
+
fragCurrent,
|
11966
|
+
media,
|
11967
|
+
mediaBuffer,
|
11968
|
+
state
|
11969
|
+
} = this;
|
11970
|
+
const currentTime = media ? media.currentTime : 0;
|
11971
|
+
const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
|
11972
|
+
this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
|
11973
|
+
if (this.state === State.ENDED) {
|
11974
|
+
this.resetLoadingState();
|
11975
|
+
} else if (fragCurrent) {
|
11976
|
+
// Seeking while frag load is in progress
|
11977
|
+
const tolerance = config.maxFragLookUpTolerance;
|
11978
|
+
const fragStartOffset = fragCurrent.start - tolerance;
|
11979
|
+
const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
|
11980
|
+
// if seeking out of buffered range or into new one
|
11981
|
+
if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
|
11982
|
+
const pastFragment = currentTime > fragEndOffset;
|
11983
|
+
// if the seek position is outside the current fragment range
|
11984
|
+
if (currentTime < fragStartOffset || pastFragment) {
|
11985
|
+
if (pastFragment && fragCurrent.loader) {
|
11986
|
+
this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
|
11987
|
+
fragCurrent.abortRequests();
|
11988
|
+
this.resetLoadingState();
|
11989
|
+
}
|
11990
|
+
this.fragPrevious = null;
|
11991
|
+
}
|
11992
|
+
}
|
11993
|
+
}
|
11994
|
+
if (media) {
|
11995
|
+
// Remove gap fragments
|
11996
|
+
this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
|
11997
|
+
this.lastCurrentTime = currentTime;
|
11998
|
+
}
|
11999
|
+
|
12000
|
+
// in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
|
12001
|
+
if (!this.loadedmetadata && !bufferInfo.len) {
|
12002
|
+
this.nextLoadPosition = this.startPosition = currentTime;
|
12003
|
+
}
|
12004
|
+
|
12005
|
+
// Async tick to speed up processing
|
12006
|
+
this.tickImmediate();
|
12007
|
+
}
|
12008
|
+
onMediaEnded() {
|
12009
|
+
// reset startPosition and lastCurrentTime to restart playback @ stream beginning
|
12010
|
+
this.startPosition = this.lastCurrentTime = 0;
|
12011
|
+
}
|
12076
12012
|
onManifestLoaded(event, data) {
|
12077
12013
|
this.startTimeOffset = data.startTimeOffset;
|
12078
12014
|
this.initPTS = [];
|
@@ -12082,7 +12018,7 @@ class BaseStreamController extends TaskLoop {
|
|
12082
12018
|
this.stopLoad();
|
12083
12019
|
super.onHandlerDestroying();
|
12084
12020
|
// @ts-ignore
|
12085
|
-
this.hls =
|
12021
|
+
this.hls = null;
|
12086
12022
|
}
|
12087
12023
|
onHandlerDestroyed() {
|
12088
12024
|
this.state = State.STOPPED;
|
@@ -12213,10 +12149,10 @@ class BaseStreamController extends TaskLoop {
|
|
12213
12149
|
const decryptData = frag.decryptdata;
|
12214
12150
|
|
12215
12151
|
// check to see if the payload needs to be decrypted
|
12216
|
-
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv &&
|
12152
|
+
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
|
12217
12153
|
const startTime = self.performance.now();
|
12218
12154
|
// decrypt init segment data
|
12219
|
-
return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer
|
12155
|
+
return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
|
12220
12156
|
hls.trigger(Events.ERROR, {
|
12221
12157
|
type: ErrorTypes.MEDIA_ERROR,
|
12222
12158
|
details: ErrorDetails.FRAG_DECRYPT_ERROR,
|
@@ -13368,7 +13304,6 @@ const initPTSFn = (timestamp, timeOffset, initPTS) => {
|
|
13368
13304
|
*/
|
13369
13305
|
function getAudioConfig(observer, data, offset, audioCodec) {
|
13370
13306
|
let adtsObjectType;
|
13371
|
-
let originalAdtsObjectType;
|
13372
13307
|
let adtsExtensionSamplingIndex;
|
13373
13308
|
let adtsChannelConfig;
|
13374
13309
|
let config;
|
@@ -13376,7 +13311,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
13376
13311
|
const manifestCodec = audioCodec;
|
13377
13312
|
const adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
|
13378
13313
|
// byte 2
|
13379
|
-
adtsObjectType =
|
13314
|
+
adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
|
13380
13315
|
const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
|
13381
13316
|
if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
|
13382
13317
|
const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
|
@@ -13393,8 +13328,8 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
13393
13328
|
// byte 3
|
13394
13329
|
adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6;
|
13395
13330
|
logger.log(`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`);
|
13396
|
-
//
|
13397
|
-
if (/firefox
|
13331
|
+
// firefox: freq less than 24kHz = AAC SBR (HE-AAC)
|
13332
|
+
if (/firefox/i.test(userAgent)) {
|
13398
13333
|
if (adtsSamplingIndex >= 6) {
|
13399
13334
|
adtsObjectType = 5;
|
13400
13335
|
config = new Array(4);
|
@@ -13488,7 +13423,6 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
13488
13423
|
samplerate: adtsSamplingRates[adtsSamplingIndex],
|
13489
13424
|
channelCount: adtsChannelConfig,
|
13490
13425
|
codec: 'mp4a.40.' + adtsObjectType,
|
13491
|
-
parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
|
13492
13426
|
manifestCodec
|
13493
13427
|
};
|
13494
13428
|
}
|
@@ -13543,8 +13477,7 @@ function initTrackConfig(track, observer, data, offset, audioCodec) {
|
|
13543
13477
|
track.channelCount = config.channelCount;
|
13544
13478
|
track.codec = config.codec;
|
13545
13479
|
track.manifestCodec = config.manifestCodec;
|
13546
|
-
track.
|
13547
|
-
logger.log(`parsed codec:${track.parsedCodec}, codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
|
13480
|
+
logger.log(`parsed codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
|
13548
13481
|
}
|
13549
13482
|
}
|
13550
13483
|
function getFrameDuration(samplerate) {
|
@@ -14607,7 +14540,7 @@ class SampleAesDecrypter {
|
|
14607
14540
|
});
|
14608
14541
|
}
|
14609
14542
|
decryptBuffer(encryptedData) {
|
14610
|
-
return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer
|
14543
|
+
return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer);
|
14611
14544
|
}
|
14612
14545
|
|
14613
14546
|
// AAC - encrypt all full 16 bytes blocks starting from offset 16
|
@@ -16810,7 +16743,7 @@ class MP4Remuxer {
|
|
16810
16743
|
logger.warn(`[mp4-remuxer]: Injecting ${missing} audio frame @ ${(nextPts / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`);
|
16811
16744
|
for (let j = 0; j < missing; j++) {
|
16812
16745
|
const newStamp = Math.max(nextPts, 0);
|
16813
|
-
let fillFrame = AAC.getSilentFrame(track.
|
16746
|
+
let fillFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
|
16814
16747
|
if (!fillFrame) {
|
16815
16748
|
logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.');
|
16816
16749
|
fillFrame = sample.unit.subarray();
|
@@ -16938,7 +16871,7 @@ class MP4Remuxer {
|
|
16938
16871
|
// samples count of this segment's duration
|
16939
16872
|
const nbSamples = Math.ceil((endDTS - startDTS) / frameDuration);
|
16940
16873
|
// silent frame
|
16941
|
-
const silentFrame = AAC.getSilentFrame(track.
|
16874
|
+
const silentFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
|
16942
16875
|
logger.warn('[mp4-remuxer]: remux empty Audio');
|
16943
16876
|
// Can't remux if we can't generate a silent frame...
|
16944
16877
|
if (!silentFrame) {
|
@@ -17329,15 +17262,13 @@ class Transmuxer {
|
|
17329
17262
|
initSegmentData
|
17330
17263
|
} = transmuxConfig;
|
17331
17264
|
const keyData = getEncryptionType(uintData, decryptdata);
|
17332
|
-
if (keyData &&
|
17265
|
+
if (keyData && keyData.method === 'AES-128') {
|
17333
17266
|
const decrypter = this.getDecrypter();
|
17334
|
-
const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
|
17335
|
-
|
17336
17267
|
// Software decryption is synchronous; webCrypto is not
|
17337
17268
|
if (decrypter.isSync()) {
|
17338
17269
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
17339
17270
|
// data is handled in the flush() call
|
17340
|
-
let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer
|
17271
|
+
let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer);
|
17341
17272
|
// For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
|
17342
17273
|
const loadingParts = chunkMeta.part > -1;
|
17343
17274
|
if (loadingParts) {
|
@@ -17349,7 +17280,7 @@ class Transmuxer {
|
|
17349
17280
|
}
|
17350
17281
|
uintData = new Uint8Array(decryptedData);
|
17351
17282
|
} else {
|
17352
|
-
this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer
|
17283
|
+
this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer).then(decryptedData => {
|
17353
17284
|
// Calling push here is important; if flush() is called while this is still resolving, this ensures that
|
17354
17285
|
// the decrypted data has been transmuxed
|
17355
17286
|
const result = this.push(decryptedData, null, chunkMeta);
|
@@ -18003,7 +17934,14 @@ class TransmuxerInterface {
|
|
18003
17934
|
this.observer = new EventEmitter();
|
18004
17935
|
this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
|
18005
17936
|
this.observer.on(Events.ERROR, forwardMessage);
|
18006
|
-
const
|
17937
|
+
const MediaSource = getMediaSource(config.preferManagedMediaSource) || {
|
17938
|
+
isTypeSupported: () => false
|
17939
|
+
};
|
17940
|
+
const m2tsTypeSupported = {
|
17941
|
+
mpeg: MediaSource.isTypeSupported('audio/mpeg'),
|
17942
|
+
mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
|
17943
|
+
ac3: false
|
17944
|
+
};
|
18007
17945
|
|
18008
17946
|
// navigator.vendor is not always available in Web Worker
|
18009
17947
|
// refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
|
@@ -18599,32 +18537,13 @@ class StreamController extends BaseStreamController {
|
|
18599
18537
|
this.altAudio = false;
|
18600
18538
|
this.audioOnly = false;
|
18601
18539
|
this.fragPlaying = null;
|
18540
|
+
this.onvplaying = null;
|
18541
|
+
this.onvseeked = null;
|
18602
18542
|
this.fragLastKbps = 0;
|
18603
18543
|
this.couldBacktrack = false;
|
18604
18544
|
this.backtrackFragment = null;
|
18605
18545
|
this.audioCodecSwitch = false;
|
18606
18546
|
this.videoBuffer = null;
|
18607
|
-
this.onMediaPlaying = () => {
|
18608
|
-
// tick to speed up FRAG_CHANGED triggering
|
18609
|
-
this.tick();
|
18610
|
-
};
|
18611
|
-
this.onMediaSeeked = () => {
|
18612
|
-
const media = this.media;
|
18613
|
-
const currentTime = media ? media.currentTime : null;
|
18614
|
-
if (isFiniteNumber(currentTime)) {
|
18615
|
-
this.log(`Media seeked to ${currentTime.toFixed(3)}`);
|
18616
|
-
}
|
18617
|
-
|
18618
|
-
// If seeked was issued before buffer was appended do not tick immediately
|
18619
|
-
const bufferInfo = this.getMainFwdBufferInfo();
|
18620
|
-
if (bufferInfo === null || bufferInfo.len === 0) {
|
18621
|
-
this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
|
18622
|
-
return;
|
18623
|
-
}
|
18624
|
-
|
18625
|
-
// tick to speed up FRAG_CHANGED triggering
|
18626
|
-
this.tick();
|
18627
|
-
};
|
18628
18547
|
this._registerListeners();
|
18629
18548
|
}
|
18630
18549
|
_registerListeners() {
|
@@ -18666,8 +18585,6 @@ class StreamController extends BaseStreamController {
|
|
18666
18585
|
}
|
18667
18586
|
onHandlerDestroying() {
|
18668
18587
|
this._unregisterListeners();
|
18669
|
-
// @ts-ignore
|
18670
|
-
this.onMediaPlaying = this.onMediaSeeked = null;
|
18671
18588
|
super.onHandlerDestroying();
|
18672
18589
|
}
|
18673
18590
|
startLoad(startPosition) {
|
@@ -18994,17 +18911,20 @@ class StreamController extends BaseStreamController {
|
|
18994
18911
|
onMediaAttached(event, data) {
|
18995
18912
|
super.onMediaAttached(event, data);
|
18996
18913
|
const media = data.media;
|
18997
|
-
|
18998
|
-
|
18914
|
+
this.onvplaying = this.onMediaPlaying.bind(this);
|
18915
|
+
this.onvseeked = this.onMediaSeeked.bind(this);
|
18916
|
+
media.addEventListener('playing', this.onvplaying);
|
18917
|
+
media.addEventListener('seeked', this.onvseeked);
|
18999
18918
|
this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
|
19000
18919
|
}
|
19001
18920
|
onMediaDetaching() {
|
19002
18921
|
const {
|
19003
18922
|
media
|
19004
18923
|
} = this;
|
19005
|
-
if (media) {
|
19006
|
-
media.removeEventListener('playing', this.
|
19007
|
-
media.removeEventListener('seeked', this.
|
18924
|
+
if (media && this.onvplaying && this.onvseeked) {
|
18925
|
+
media.removeEventListener('playing', this.onvplaying);
|
18926
|
+
media.removeEventListener('seeked', this.onvseeked);
|
18927
|
+
this.onvplaying = this.onvseeked = null;
|
19008
18928
|
this.videoBuffer = null;
|
19009
18929
|
}
|
19010
18930
|
this.fragPlaying = null;
|
@@ -19014,6 +18934,27 @@ class StreamController extends BaseStreamController {
|
|
19014
18934
|
}
|
19015
18935
|
super.onMediaDetaching();
|
19016
18936
|
}
|
18937
|
+
onMediaPlaying() {
|
18938
|
+
// tick to speed up FRAG_CHANGED triggering
|
18939
|
+
this.tick();
|
18940
|
+
}
|
18941
|
+
onMediaSeeked() {
|
18942
|
+
const media = this.media;
|
18943
|
+
const currentTime = media ? media.currentTime : null;
|
18944
|
+
if (isFiniteNumber(currentTime)) {
|
18945
|
+
this.log(`Media seeked to ${currentTime.toFixed(3)}`);
|
18946
|
+
}
|
18947
|
+
|
18948
|
+
// If seeked was issued before buffer was appended do not tick immediately
|
18949
|
+
const bufferInfo = this.getMainFwdBufferInfo();
|
18950
|
+
if (bufferInfo === null || bufferInfo.len === 0) {
|
18951
|
+
this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
|
18952
|
+
return;
|
18953
|
+
}
|
18954
|
+
|
18955
|
+
// tick to speed up FRAG_CHANGED triggering
|
18956
|
+
this.tick();
|
18957
|
+
}
|
19017
18958
|
onManifestLoading() {
|
19018
18959
|
// reset buffer on manifest loading
|
19019
18960
|
this.log('Trigger BUFFER_RESET');
|
@@ -19744,7 +19685,7 @@ class Hls {
|
|
19744
19685
|
* Get the video-dev/hls.js package version.
|
19745
19686
|
*/
|
19746
19687
|
static get version() {
|
19747
|
-
return "1.5.2
|
19688
|
+
return "1.5.2";
|
19748
19689
|
}
|
19749
19690
|
|
19750
19691
|
/**
|