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.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
|
}
|
@@ -1036,26 +1036,6 @@ function strToUtf8array(str) {
|
|
1036
1036
|
return Uint8Array.from(unescape(encodeURIComponent(str)), c => c.charCodeAt(0));
|
1037
1037
|
}
|
1038
1038
|
|
1039
|
-
var DecrypterAesMode = {
|
1040
|
-
cbc: 0,
|
1041
|
-
ctr: 1
|
1042
|
-
};
|
1043
|
-
|
1044
|
-
function isFullSegmentEncryption(method) {
|
1045
|
-
return method === 'AES-128' || method === 'AES-256' || method === 'AES-256-CTR';
|
1046
|
-
}
|
1047
|
-
function getAesModeFromFullSegmentMethod(method) {
|
1048
|
-
switch (method) {
|
1049
|
-
case 'AES-128':
|
1050
|
-
case 'AES-256':
|
1051
|
-
return DecrypterAesMode.cbc;
|
1052
|
-
case 'AES-256-CTR':
|
1053
|
-
return DecrypterAesMode.ctr;
|
1054
|
-
default:
|
1055
|
-
throw new Error(`invalid full segment method ${method}`);
|
1056
|
-
}
|
1057
|
-
}
|
1058
|
-
|
1059
1039
|
/** returns `undefined` is `self` is missing, e.g. in node */
|
1060
1040
|
const optionalSelf = typeof self !== 'undefined' ? self : undefined;
|
1061
1041
|
|
@@ -2694,12 +2674,12 @@ class LevelKey {
|
|
2694
2674
|
this.keyFormatVersions = formatversions;
|
2695
2675
|
this.iv = iv;
|
2696
2676
|
this.encrypted = method ? method !== 'NONE' : false;
|
2697
|
-
this.isCommonEncryption = this.encrypted &&
|
2677
|
+
this.isCommonEncryption = this.encrypted && method !== 'AES-128';
|
2698
2678
|
}
|
2699
2679
|
isSupported() {
|
2700
2680
|
// If it's Segment encryption or No encryption, just select that key system
|
2701
2681
|
if (this.method) {
|
2702
|
-
if (
|
2682
|
+
if (this.method === 'AES-128' || this.method === 'NONE') {
|
2703
2683
|
return true;
|
2704
2684
|
}
|
2705
2685
|
if (this.keyFormat === 'identity') {
|
@@ -2721,13 +2701,14 @@ class LevelKey {
|
|
2721
2701
|
if (!this.encrypted || !this.uri) {
|
2722
2702
|
return null;
|
2723
2703
|
}
|
2724
|
-
if (
|
2704
|
+
if (this.method === 'AES-128' && this.uri && !this.iv) {
|
2725
2705
|
if (typeof sn !== 'number') {
|
2726
2706
|
// We are fetching decryption data for a initialization segment
|
2727
|
-
// If the segment was encrypted with AES-128
|
2707
|
+
// If the segment was encrypted with AES-128
|
2728
2708
|
// It must have an IV defined. We cannot substitute the Segment Number in.
|
2729
|
-
|
2730
|
-
|
2709
|
+
if (this.method === 'AES-128' && !this.iv) {
|
2710
|
+
logger.warn(`missing IV for initialization segment with method="${this.method}" - compliance issue`);
|
2711
|
+
}
|
2731
2712
|
// Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
|
2732
2713
|
sn = 0;
|
2733
2714
|
}
|
@@ -3006,28 +2987,23 @@ function getCodecCompatibleNameLower(lowerCaseCodec, preferManagedMediaSource =
|
|
3006
2987
|
if (CODEC_COMPATIBLE_NAMES[lowerCaseCodec]) {
|
3007
2988
|
return CODEC_COMPATIBLE_NAMES[lowerCaseCodec];
|
3008
2989
|
}
|
2990
|
+
|
2991
|
+
// Idealy fLaC and Opus would be first (spec-compliant) but
|
2992
|
+
// some browsers will report that fLaC is supported then fail.
|
2993
|
+
// see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
|
3009
2994
|
const codecsToCheck = {
|
3010
|
-
// Idealy fLaC and Opus would be first (spec-compliant) but
|
3011
|
-
// some browsers will report that fLaC is supported then fail.
|
3012
|
-
// see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
|
3013
2995
|
flac: ['flac', 'fLaC', 'FLAC'],
|
3014
|
-
opus: ['opus', 'Opus']
|
3015
|
-
// Replace audio codec info if browser does not support mp4a.40.34,
|
3016
|
-
// and demuxer can fallback to 'audio/mpeg' or 'audio/mp4;codecs="mp3"'
|
3017
|
-
'mp4a.40.34': ['mp3']
|
2996
|
+
opus: ['opus', 'Opus']
|
3018
2997
|
}[lowerCaseCodec];
|
3019
2998
|
for (let i = 0; i < codecsToCheck.length; i++) {
|
3020
|
-
var _getMediaSource;
|
3021
2999
|
if (isCodecMediaSourceSupported(codecsToCheck[i], 'audio', preferManagedMediaSource)) {
|
3022
3000
|
CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
|
3023
3001
|
return codecsToCheck[i];
|
3024
|
-
} else if (codecsToCheck[i] === 'mp3' && (_getMediaSource = getMediaSource(preferManagedMediaSource)) != null && _getMediaSource.isTypeSupported('audio/mpeg')) {
|
3025
|
-
return '';
|
3026
3002
|
}
|
3027
3003
|
}
|
3028
3004
|
return lowerCaseCodec;
|
3029
3005
|
}
|
3030
|
-
const AUDIO_CODEC_REGEXP = /flac|opus
|
3006
|
+
const AUDIO_CODEC_REGEXP = /flac|opus/i;
|
3031
3007
|
function getCodecCompatibleName(codec, preferManagedMediaSource = true) {
|
3032
3008
|
return codec.replace(AUDIO_CODEC_REGEXP, m => getCodecCompatibleNameLower(m.toLowerCase(), preferManagedMediaSource));
|
3033
3009
|
}
|
@@ -3050,16 +3026,6 @@ function convertAVC1ToAVCOTI(codec) {
|
|
3050
3026
|
}
|
3051
3027
|
return codec;
|
3052
3028
|
}
|
3053
|
-
function getM2TSSupportedAudioTypes(preferManagedMediaSource) {
|
3054
|
-
const MediaSource = getMediaSource(preferManagedMediaSource) || {
|
3055
|
-
isTypeSupported: () => false
|
3056
|
-
};
|
3057
|
-
return {
|
3058
|
-
mpeg: MediaSource.isTypeSupported('audio/mpeg'),
|
3059
|
-
mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
|
3060
|
-
ac3: MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
|
3061
|
-
};
|
3062
|
-
}
|
3063
3029
|
|
3064
3030
|
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;
|
3065
3031
|
const MASTER_PLAYLIST_MEDIA_REGEX = /#EXT-X-MEDIA:(.*)/g;
|
@@ -4726,47 +4692,7 @@ class LatencyController {
|
|
4726
4692
|
this.currentTime = 0;
|
4727
4693
|
this.stallCount = 0;
|
4728
4694
|
this._latency = null;
|
4729
|
-
this.
|
4730
|
-
const {
|
4731
|
-
media,
|
4732
|
-
levelDetails
|
4733
|
-
} = this;
|
4734
|
-
if (!media || !levelDetails) {
|
4735
|
-
return;
|
4736
|
-
}
|
4737
|
-
this.currentTime = media.currentTime;
|
4738
|
-
const latency = this.computeLatency();
|
4739
|
-
if (latency === null) {
|
4740
|
-
return;
|
4741
|
-
}
|
4742
|
-
this._latency = latency;
|
4743
|
-
|
4744
|
-
// Adapt playbackRate to meet target latency in low-latency mode
|
4745
|
-
const {
|
4746
|
-
lowLatencyMode,
|
4747
|
-
maxLiveSyncPlaybackRate
|
4748
|
-
} = this.config;
|
4749
|
-
if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
|
4750
|
-
return;
|
4751
|
-
}
|
4752
|
-
const targetLatency = this.targetLatency;
|
4753
|
-
if (targetLatency === null) {
|
4754
|
-
return;
|
4755
|
-
}
|
4756
|
-
const distanceFromTarget = latency - targetLatency;
|
4757
|
-
// Only adjust playbackRate when within one target duration of targetLatency
|
4758
|
-
// and more than one second from under-buffering.
|
4759
|
-
// Playback further than one target duration from target can be considered DVR playback.
|
4760
|
-
const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
|
4761
|
-
const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
|
4762
|
-
if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
|
4763
|
-
const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
|
4764
|
-
const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
|
4765
|
-
media.playbackRate = Math.min(max, Math.max(1, rate));
|
4766
|
-
} else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
|
4767
|
-
media.playbackRate = 1;
|
4768
|
-
}
|
4769
|
-
};
|
4695
|
+
this.timeupdateHandler = () => this.timeupdate();
|
4770
4696
|
this.hls = hls;
|
4771
4697
|
this.config = hls.config;
|
4772
4698
|
this.registerListeners();
|
@@ -4858,7 +4784,7 @@ class LatencyController {
|
|
4858
4784
|
this.onMediaDetaching();
|
4859
4785
|
this.levelDetails = null;
|
4860
4786
|
// @ts-ignore
|
4861
|
-
this.hls = this.
|
4787
|
+
this.hls = this.timeupdateHandler = null;
|
4862
4788
|
}
|
4863
4789
|
registerListeners() {
|
4864
4790
|
this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
@@ -4876,11 +4802,11 @@ class LatencyController {
|
|
4876
4802
|
}
|
4877
4803
|
onMediaAttached(event, data) {
|
4878
4804
|
this.media = data.media;
|
4879
|
-
this.media.addEventListener('timeupdate', this.
|
4805
|
+
this.media.addEventListener('timeupdate', this.timeupdateHandler);
|
4880
4806
|
}
|
4881
4807
|
onMediaDetaching() {
|
4882
4808
|
if (this.media) {
|
4883
|
-
this.media.removeEventListener('timeupdate', this.
|
4809
|
+
this.media.removeEventListener('timeupdate', this.timeupdateHandler);
|
4884
4810
|
this.media = null;
|
4885
4811
|
}
|
4886
4812
|
}
|
@@ -4894,10 +4820,10 @@ class LatencyController {
|
|
4894
4820
|
}) {
|
4895
4821
|
this.levelDetails = details;
|
4896
4822
|
if (details.advanced) {
|
4897
|
-
this.
|
4823
|
+
this.timeupdate();
|
4898
4824
|
}
|
4899
4825
|
if (!details.live && this.media) {
|
4900
|
-
this.media.removeEventListener('timeupdate', this.
|
4826
|
+
this.media.removeEventListener('timeupdate', this.timeupdateHandler);
|
4901
4827
|
}
|
4902
4828
|
}
|
4903
4829
|
onError(event, data) {
|
@@ -4910,6 +4836,47 @@ class LatencyController {
|
|
4910
4836
|
logger.warn('[playback-rate-controller]: Stall detected, adjusting target latency');
|
4911
4837
|
}
|
4912
4838
|
}
|
4839
|
+
timeupdate() {
|
4840
|
+
const {
|
4841
|
+
media,
|
4842
|
+
levelDetails
|
4843
|
+
} = this;
|
4844
|
+
if (!media || !levelDetails) {
|
4845
|
+
return;
|
4846
|
+
}
|
4847
|
+
this.currentTime = media.currentTime;
|
4848
|
+
const latency = this.computeLatency();
|
4849
|
+
if (latency === null) {
|
4850
|
+
return;
|
4851
|
+
}
|
4852
|
+
this._latency = latency;
|
4853
|
+
|
4854
|
+
// Adapt playbackRate to meet target latency in low-latency mode
|
4855
|
+
const {
|
4856
|
+
lowLatencyMode,
|
4857
|
+
maxLiveSyncPlaybackRate
|
4858
|
+
} = this.config;
|
4859
|
+
if (!lowLatencyMode || maxLiveSyncPlaybackRate === 1 || !levelDetails.live) {
|
4860
|
+
return;
|
4861
|
+
}
|
4862
|
+
const targetLatency = this.targetLatency;
|
4863
|
+
if (targetLatency === null) {
|
4864
|
+
return;
|
4865
|
+
}
|
4866
|
+
const distanceFromTarget = latency - targetLatency;
|
4867
|
+
// Only adjust playbackRate when within one target duration of targetLatency
|
4868
|
+
// and more than one second from under-buffering.
|
4869
|
+
// Playback further than one target duration from target can be considered DVR playback.
|
4870
|
+
const liveMinLatencyDuration = Math.min(this.maxLatency, targetLatency + levelDetails.targetduration);
|
4871
|
+
const inLiveRange = distanceFromTarget < liveMinLatencyDuration;
|
4872
|
+
if (inLiveRange && distanceFromTarget > 0.05 && this.forwardBufferLength > 1) {
|
4873
|
+
const max = Math.min(2, Math.max(1.0, maxLiveSyncPlaybackRate));
|
4874
|
+
const rate = Math.round(2 / (1 + Math.exp(-0.75 * distanceFromTarget - this.edgeStalled)) * 20) / 20;
|
4875
|
+
media.playbackRate = Math.min(max, Math.max(1, rate));
|
4876
|
+
} else if (media.playbackRate !== 1 && media.playbackRate !== 0) {
|
4877
|
+
media.playbackRate = 1;
|
4878
|
+
}
|
4879
|
+
}
|
4913
4880
|
estimateLiveEdge() {
|
4914
4881
|
const {
|
4915
4882
|
levelDetails
|
@@ -6910,7 +6877,7 @@ class AbrController {
|
|
6910
6877
|
const bwEstimate = this.getBwEstimate();
|
6911
6878
|
const levels = hls.levels;
|
6912
6879
|
const level = levels[frag.level];
|
6913
|
-
const expectedLen = stats.total || Math.max(stats.loaded, Math.round(duration * level.
|
6880
|
+
const expectedLen = stats.total || Math.max(stats.loaded, Math.round(duration * level.averageBitrate / 8));
|
6914
6881
|
let timeStreaming = loadedFirstByte ? timeLoading - ttfb : timeLoading;
|
6915
6882
|
if (timeStreaming < 1 && loadedFirstByte) {
|
6916
6883
|
timeStreaming = Math.min(timeLoading, stats.loaded * 8 / bwEstimate);
|
@@ -6953,7 +6920,7 @@ class AbrController {
|
|
6953
6920
|
// If there has been no loading progress, sample TTFB
|
6954
6921
|
this.bwEstimator.sampleTTFB(timeLoading);
|
6955
6922
|
}
|
6956
|
-
const nextLoadLevelBitrate = levels[nextLoadLevel].
|
6923
|
+
const nextLoadLevelBitrate = levels[nextLoadLevel].maxBitrate;
|
6957
6924
|
if (this.getBwEstimate() * this.hls.config.abrBandWidthUpFactor > nextLoadLevelBitrate) {
|
6958
6925
|
this.resetEstimator(nextLoadLevelBitrate);
|
6959
6926
|
}
|
@@ -8582,8 +8549,8 @@ function createLoaderContext(frag, part = null) {
|
|
8582
8549
|
var _frag$decryptdata;
|
8583
8550
|
let byteRangeStart = start;
|
8584
8551
|
let byteRangeEnd = end;
|
8585
|
-
if (frag.sn === 'initSegment' &&
|
8586
|
-
// MAP segment encrypted with method 'AES-128'
|
8552
|
+
if (frag.sn === 'initSegment' && ((_frag$decryptdata = frag.decryptdata) == null ? void 0 : _frag$decryptdata.method) === 'AES-128') {
|
8553
|
+
// MAP segment encrypted with method 'AES-128', when served with HTTP Range,
|
8587
8554
|
// has the unencrypted size specified in the range.
|
8588
8555
|
// Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
|
8589
8556
|
const fragmentLen = end - start;
|
@@ -8616,9 +8583,6 @@ function createGapLoadError(frag, part) {
|
|
8616
8583
|
(part ? part : frag).stats.aborted = true;
|
8617
8584
|
return new LoadError(errorData);
|
8618
8585
|
}
|
8619
|
-
function isMethodFullSegmentAesCbc(method) {
|
8620
|
-
return method === 'AES-128' || method === 'AES-256';
|
8621
|
-
}
|
8622
8586
|
class LoadError extends Error {
|
8623
8587
|
constructor(data) {
|
8624
8588
|
super(data.error.message);
|
@@ -8628,61 +8592,33 @@ class LoadError extends Error {
|
|
8628
8592
|
}
|
8629
8593
|
|
8630
8594
|
class AESCrypto {
|
8631
|
-
constructor(subtle, iv
|
8595
|
+
constructor(subtle, iv) {
|
8632
8596
|
this.subtle = void 0;
|
8633
8597
|
this.aesIV = void 0;
|
8634
|
-
this.aesMode = void 0;
|
8635
8598
|
this.subtle = subtle;
|
8636
8599
|
this.aesIV = iv;
|
8637
|
-
this.aesMode = aesMode;
|
8638
8600
|
}
|
8639
8601
|
decrypt(data, key) {
|
8640
|
-
|
8641
|
-
|
8642
|
-
|
8643
|
-
|
8644
|
-
iv: this.aesIV
|
8645
|
-
}, key, data);
|
8646
|
-
case DecrypterAesMode.ctr:
|
8647
|
-
return this.subtle.decrypt({
|
8648
|
-
name: 'AES-CTR',
|
8649
|
-
counter: this.aesIV,
|
8650
|
-
length: 64
|
8651
|
-
},
|
8652
|
-
//64 : NIST SP800-38A standard suggests that the counter should occupy half of the counter block
|
8653
|
-
key, data);
|
8654
|
-
default:
|
8655
|
-
throw new Error(`[AESCrypto] invalid aes mode ${this.aesMode}`);
|
8656
|
-
}
|
8602
|
+
return this.subtle.decrypt({
|
8603
|
+
name: 'AES-CBC',
|
8604
|
+
iv: this.aesIV
|
8605
|
+
}, key, data);
|
8657
8606
|
}
|
8658
8607
|
}
|
8659
8608
|
|
8660
8609
|
class FastAESKey {
|
8661
|
-
constructor(subtle, key
|
8610
|
+
constructor(subtle, key) {
|
8662
8611
|
this.subtle = void 0;
|
8663
8612
|
this.key = void 0;
|
8664
|
-
this.aesMode = void 0;
|
8665
8613
|
this.subtle = subtle;
|
8666
8614
|
this.key = key;
|
8667
|
-
this.aesMode = aesMode;
|
8668
8615
|
}
|
8669
8616
|
expandKey() {
|
8670
|
-
const subtleAlgoName = getSubtleAlgoName(this.aesMode);
|
8671
8617
|
return this.subtle.importKey('raw', this.key, {
|
8672
|
-
name:
|
8618
|
+
name: 'AES-CBC'
|
8673
8619
|
}, false, ['encrypt', 'decrypt']);
|
8674
8620
|
}
|
8675
8621
|
}
|
8676
|
-
function getSubtleAlgoName(aesMode) {
|
8677
|
-
switch (aesMode) {
|
8678
|
-
case DecrypterAesMode.cbc:
|
8679
|
-
return 'AES-CBC';
|
8680
|
-
case DecrypterAesMode.ctr:
|
8681
|
-
return 'AES-CTR';
|
8682
|
-
default:
|
8683
|
-
throw new Error(`[FastAESKey] invalid aes mode ${aesMode}`);
|
8684
|
-
}
|
8685
|
-
}
|
8686
8622
|
|
8687
8623
|
// PKCS7
|
8688
8624
|
function removePadding(array) {
|
@@ -8932,8 +8868,7 @@ class Decrypter {
|
|
8932
8868
|
this.currentIV = null;
|
8933
8869
|
this.currentResult = null;
|
8934
8870
|
this.useSoftware = void 0;
|
8935
|
-
this.
|
8936
|
-
this.enableSoftwareAES = config.enableSoftwareAES;
|
8871
|
+
this.useSoftware = config.enableSoftwareAES;
|
8937
8872
|
this.removePKCS7Padding = removePKCS7Padding;
|
8938
8873
|
// built in decryptor expects PKCS7 padding
|
8939
8874
|
if (removePKCS7Padding) {
|
@@ -8946,7 +8881,9 @@ class Decrypter {
|
|
8946
8881
|
/* no-op */
|
8947
8882
|
}
|
8948
8883
|
}
|
8949
|
-
|
8884
|
+
if (this.subtle === null) {
|
8885
|
+
this.useSoftware = true;
|
8886
|
+
}
|
8950
8887
|
}
|
8951
8888
|
destroy() {
|
8952
8889
|
this.subtle = null;
|
@@ -8984,10 +8921,10 @@ class Decrypter {
|
|
8984
8921
|
this.softwareDecrypter = null;
|
8985
8922
|
}
|
8986
8923
|
}
|
8987
|
-
decrypt(data, key, iv
|
8924
|
+
decrypt(data, key, iv) {
|
8988
8925
|
if (this.useSoftware) {
|
8989
8926
|
return new Promise((resolve, reject) => {
|
8990
|
-
this.softwareDecrypt(new Uint8Array(data), key, iv
|
8927
|
+
this.softwareDecrypt(new Uint8Array(data), key, iv);
|
8991
8928
|
const decryptResult = this.flush();
|
8992
8929
|
if (decryptResult) {
|
8993
8930
|
resolve(decryptResult.buffer);
|
@@ -8996,21 +8933,17 @@ class Decrypter {
|
|
8996
8933
|
}
|
8997
8934
|
});
|
8998
8935
|
}
|
8999
|
-
return this.webCryptoDecrypt(new Uint8Array(data), key, iv
|
8936
|
+
return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
|
9000
8937
|
}
|
9001
8938
|
|
9002
8939
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
9003
8940
|
// data is handled in the flush() call
|
9004
|
-
softwareDecrypt(data, key, iv
|
8941
|
+
softwareDecrypt(data, key, iv) {
|
9005
8942
|
const {
|
9006
8943
|
currentIV,
|
9007
8944
|
currentResult,
|
9008
8945
|
remainderData
|
9009
8946
|
} = this;
|
9010
|
-
if (aesMode !== DecrypterAesMode.cbc || key.byteLength !== 16) {
|
9011
|
-
logger.warn('SoftwareDecrypt: can only handle AES-128-CBC');
|
9012
|
-
return null;
|
9013
|
-
}
|
9014
8947
|
this.logOnce('JS AES decrypt');
|
9015
8948
|
// The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
|
9016
8949
|
// This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached
|
@@ -9043,11 +8976,11 @@ class Decrypter {
|
|
9043
8976
|
}
|
9044
8977
|
return result;
|
9045
8978
|
}
|
9046
|
-
webCryptoDecrypt(data, key, iv
|
8979
|
+
webCryptoDecrypt(data, key, iv) {
|
9047
8980
|
const subtle = this.subtle;
|
9048
8981
|
if (this.key !== key || !this.fastAesKey) {
|
9049
8982
|
this.key = key;
|
9050
|
-
this.fastAesKey = new FastAESKey(subtle, key
|
8983
|
+
this.fastAesKey = new FastAESKey(subtle, key);
|
9051
8984
|
}
|
9052
8985
|
return this.fastAesKey.expandKey().then(aesKey => {
|
9053
8986
|
// decrypt using web crypto
|
@@ -9055,25 +8988,22 @@ class Decrypter {
|
|
9055
8988
|
return Promise.reject(new Error('web crypto not initialized'));
|
9056
8989
|
}
|
9057
8990
|
this.logOnce('WebCrypto AES decrypt');
|
9058
|
-
const crypto = new AESCrypto(subtle, new Uint8Array(iv)
|
8991
|
+
const crypto = new AESCrypto(subtle, new Uint8Array(iv));
|
9059
8992
|
return crypto.decrypt(data.buffer, aesKey);
|
9060
8993
|
}).catch(err => {
|
9061
8994
|
logger.warn(`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`);
|
9062
|
-
return this.onWebCryptoError(data, key, iv
|
8995
|
+
return this.onWebCryptoError(data, key, iv);
|
9063
8996
|
});
|
9064
8997
|
}
|
9065
|
-
onWebCryptoError(data, key, iv
|
9066
|
-
|
9067
|
-
|
9068
|
-
|
9069
|
-
|
9070
|
-
|
9071
|
-
|
9072
|
-
if (decryptResult) {
|
9073
|
-
return decryptResult.buffer;
|
9074
|
-
}
|
8998
|
+
onWebCryptoError(data, key, iv) {
|
8999
|
+
this.useSoftware = true;
|
9000
|
+
this.logEnabled = true;
|
9001
|
+
this.softwareDecrypt(data, key, iv);
|
9002
|
+
const decryptResult = this.flush();
|
9003
|
+
if (decryptResult) {
|
9004
|
+
return decryptResult.buffer;
|
9075
9005
|
}
|
9076
|
-
throw new Error('WebCrypto
|
9006
|
+
throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data');
|
9077
9007
|
}
|
9078
9008
|
getValidChunk(data) {
|
9079
9009
|
let currentChunk = data;
|
@@ -9149,59 +9079,11 @@ class BaseStreamController extends TaskLoop {
|
|
9149
9079
|
this.startFragRequested = false;
|
9150
9080
|
this.decrypter = void 0;
|
9151
9081
|
this.initPTS = [];
|
9082
|
+
this.onvseeking = null;
|
9083
|
+
this.onvended = null;
|
9152
9084
|
this.logPrefix = '';
|
9153
9085
|
this.log = void 0;
|
9154
9086
|
this.warn = void 0;
|
9155
|
-
this.onMediaSeeking = () => {
|
9156
|
-
const {
|
9157
|
-
config,
|
9158
|
-
fragCurrent,
|
9159
|
-
media,
|
9160
|
-
mediaBuffer,
|
9161
|
-
state
|
9162
|
-
} = this;
|
9163
|
-
const currentTime = media ? media.currentTime : 0;
|
9164
|
-
const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
|
9165
|
-
this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
|
9166
|
-
if (this.state === State.ENDED) {
|
9167
|
-
this.resetLoadingState();
|
9168
|
-
} else if (fragCurrent) {
|
9169
|
-
// Seeking while frag load is in progress
|
9170
|
-
const tolerance = config.maxFragLookUpTolerance;
|
9171
|
-
const fragStartOffset = fragCurrent.start - tolerance;
|
9172
|
-
const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
|
9173
|
-
// if seeking out of buffered range or into new one
|
9174
|
-
if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
|
9175
|
-
const pastFragment = currentTime > fragEndOffset;
|
9176
|
-
// if the seek position is outside the current fragment range
|
9177
|
-
if (currentTime < fragStartOffset || pastFragment) {
|
9178
|
-
if (pastFragment && fragCurrent.loader) {
|
9179
|
-
this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
|
9180
|
-
fragCurrent.abortRequests();
|
9181
|
-
this.resetLoadingState();
|
9182
|
-
}
|
9183
|
-
this.fragPrevious = null;
|
9184
|
-
}
|
9185
|
-
}
|
9186
|
-
}
|
9187
|
-
if (media) {
|
9188
|
-
// Remove gap fragments
|
9189
|
-
this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
|
9190
|
-
this.lastCurrentTime = currentTime;
|
9191
|
-
}
|
9192
|
-
|
9193
|
-
// in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
|
9194
|
-
if (!this.loadedmetadata && !bufferInfo.len) {
|
9195
|
-
this.nextLoadPosition = this.startPosition = currentTime;
|
9196
|
-
}
|
9197
|
-
|
9198
|
-
// Async tick to speed up processing
|
9199
|
-
this.tickImmediate();
|
9200
|
-
};
|
9201
|
-
this.onMediaEnded = () => {
|
9202
|
-
// reset startPosition and lastCurrentTime to restart playback @ stream beginning
|
9203
|
-
this.startPosition = this.lastCurrentTime = 0;
|
9204
|
-
};
|
9205
9087
|
this.playlistType = playlistType;
|
9206
9088
|
this.logPrefix = logPrefix;
|
9207
9089
|
this.log = logger.log.bind(logger, `${logPrefix}:`);
|
@@ -9266,8 +9148,10 @@ class BaseStreamController extends TaskLoop {
|
|
9266
9148
|
}
|
9267
9149
|
onMediaAttached(event, data) {
|
9268
9150
|
const media = this.media = this.mediaBuffer = data.media;
|
9269
|
-
|
9270
|
-
|
9151
|
+
this.onvseeking = this.onMediaSeeking.bind(this);
|
9152
|
+
this.onvended = this.onMediaEnded.bind(this);
|
9153
|
+
media.addEventListener('seeking', this.onvseeking);
|
9154
|
+
media.addEventListener('ended', this.onvended);
|
9271
9155
|
const config = this.config;
|
9272
9156
|
if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
|
9273
9157
|
this.startLoad(config.startPosition);
|
@@ -9281,9 +9165,10 @@ class BaseStreamController extends TaskLoop {
|
|
9281
9165
|
}
|
9282
9166
|
|
9283
9167
|
// remove video listeners
|
9284
|
-
if (media) {
|
9285
|
-
media.removeEventListener('seeking', this.
|
9286
|
-
media.removeEventListener('ended', this.
|
9168
|
+
if (media && this.onvseeking && this.onvended) {
|
9169
|
+
media.removeEventListener('seeking', this.onvseeking);
|
9170
|
+
media.removeEventListener('ended', this.onvended);
|
9171
|
+
this.onvseeking = this.onvended = null;
|
9287
9172
|
}
|
9288
9173
|
if (this.keyLoader) {
|
9289
9174
|
this.keyLoader.detach();
|
@@ -9293,6 +9178,56 @@ class BaseStreamController extends TaskLoop {
|
|
9293
9178
|
this.fragmentTracker.removeAllFragments();
|
9294
9179
|
this.stopLoad();
|
9295
9180
|
}
|
9181
|
+
onMediaSeeking() {
|
9182
|
+
const {
|
9183
|
+
config,
|
9184
|
+
fragCurrent,
|
9185
|
+
media,
|
9186
|
+
mediaBuffer,
|
9187
|
+
state
|
9188
|
+
} = this;
|
9189
|
+
const currentTime = media ? media.currentTime : 0;
|
9190
|
+
const bufferInfo = BufferHelper.bufferInfo(mediaBuffer ? mediaBuffer : media, currentTime, config.maxBufferHole);
|
9191
|
+
this.log(`media seeking to ${isFiniteNumber(currentTime) ? currentTime.toFixed(3) : currentTime}, state: ${state}`);
|
9192
|
+
if (this.state === State.ENDED) {
|
9193
|
+
this.resetLoadingState();
|
9194
|
+
} else if (fragCurrent) {
|
9195
|
+
// Seeking while frag load is in progress
|
9196
|
+
const tolerance = config.maxFragLookUpTolerance;
|
9197
|
+
const fragStartOffset = fragCurrent.start - tolerance;
|
9198
|
+
const fragEndOffset = fragCurrent.start + fragCurrent.duration + tolerance;
|
9199
|
+
// if seeking out of buffered range or into new one
|
9200
|
+
if (!bufferInfo.len || fragEndOffset < bufferInfo.start || fragStartOffset > bufferInfo.end) {
|
9201
|
+
const pastFragment = currentTime > fragEndOffset;
|
9202
|
+
// if the seek position is outside the current fragment range
|
9203
|
+
if (currentTime < fragStartOffset || pastFragment) {
|
9204
|
+
if (pastFragment && fragCurrent.loader) {
|
9205
|
+
this.log('seeking outside of buffer while fragment load in progress, cancel fragment load');
|
9206
|
+
fragCurrent.abortRequests();
|
9207
|
+
this.resetLoadingState();
|
9208
|
+
}
|
9209
|
+
this.fragPrevious = null;
|
9210
|
+
}
|
9211
|
+
}
|
9212
|
+
}
|
9213
|
+
if (media) {
|
9214
|
+
// Remove gap fragments
|
9215
|
+
this.fragmentTracker.removeFragmentsInRange(currentTime, Infinity, this.playlistType, true);
|
9216
|
+
this.lastCurrentTime = currentTime;
|
9217
|
+
}
|
9218
|
+
|
9219
|
+
// in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
|
9220
|
+
if (!this.loadedmetadata && !bufferInfo.len) {
|
9221
|
+
this.nextLoadPosition = this.startPosition = currentTime;
|
9222
|
+
}
|
9223
|
+
|
9224
|
+
// Async tick to speed up processing
|
9225
|
+
this.tickImmediate();
|
9226
|
+
}
|
9227
|
+
onMediaEnded() {
|
9228
|
+
// reset startPosition and lastCurrentTime to restart playback @ stream beginning
|
9229
|
+
this.startPosition = this.lastCurrentTime = 0;
|
9230
|
+
}
|
9296
9231
|
onManifestLoaded(event, data) {
|
9297
9232
|
this.startTimeOffset = data.startTimeOffset;
|
9298
9233
|
this.initPTS = [];
|
@@ -9302,7 +9237,7 @@ class BaseStreamController extends TaskLoop {
|
|
9302
9237
|
this.stopLoad();
|
9303
9238
|
super.onHandlerDestroying();
|
9304
9239
|
// @ts-ignore
|
9305
|
-
this.hls =
|
9240
|
+
this.hls = null;
|
9306
9241
|
}
|
9307
9242
|
onHandlerDestroyed() {
|
9308
9243
|
this.state = State.STOPPED;
|
@@ -9433,10 +9368,10 @@ class BaseStreamController extends TaskLoop {
|
|
9433
9368
|
const decryptData = frag.decryptdata;
|
9434
9369
|
|
9435
9370
|
// check to see if the payload needs to be decrypted
|
9436
|
-
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv &&
|
9371
|
+
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
|
9437
9372
|
const startTime = self.performance.now();
|
9438
9373
|
// decrypt init segment data
|
9439
|
-
return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer
|
9374
|
+
return this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
|
9440
9375
|
hls.trigger(Events.ERROR, {
|
9441
9376
|
type: ErrorTypes.MEDIA_ERROR,
|
9442
9377
|
details: ErrorDetails.FRAG_DECRYPT_ERROR,
|
@@ -10602,7 +10537,6 @@ const initPTSFn = (timestamp, timeOffset, initPTS) => {
|
|
10602
10537
|
*/
|
10603
10538
|
function getAudioConfig(observer, data, offset, audioCodec) {
|
10604
10539
|
let adtsObjectType;
|
10605
|
-
let originalAdtsObjectType;
|
10606
10540
|
let adtsExtensionSamplingIndex;
|
10607
10541
|
let adtsChannelConfig;
|
10608
10542
|
let config;
|
@@ -10610,7 +10544,7 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
10610
10544
|
const manifestCodec = audioCodec;
|
10611
10545
|
const adtsSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350];
|
10612
10546
|
// byte 2
|
10613
|
-
adtsObjectType =
|
10547
|
+
adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
|
10614
10548
|
const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
|
10615
10549
|
if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
|
10616
10550
|
const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
|
@@ -10627,8 +10561,8 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
10627
10561
|
// byte 3
|
10628
10562
|
adtsChannelConfig |= (data[offset + 3] & 0xc0) >>> 6;
|
10629
10563
|
logger.log(`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`);
|
10630
|
-
//
|
10631
|
-
if (/firefox
|
10564
|
+
// firefox: freq less than 24kHz = AAC SBR (HE-AAC)
|
10565
|
+
if (/firefox/i.test(userAgent)) {
|
10632
10566
|
if (adtsSamplingIndex >= 6) {
|
10633
10567
|
adtsObjectType = 5;
|
10634
10568
|
config = new Array(4);
|
@@ -10722,7 +10656,6 @@ function getAudioConfig(observer, data, offset, audioCodec) {
|
|
10722
10656
|
samplerate: adtsSamplingRates[adtsSamplingIndex],
|
10723
10657
|
channelCount: adtsChannelConfig,
|
10724
10658
|
codec: 'mp4a.40.' + adtsObjectType,
|
10725
|
-
parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
|
10726
10659
|
manifestCodec
|
10727
10660
|
};
|
10728
10661
|
}
|
@@ -10777,8 +10710,7 @@ function initTrackConfig(track, observer, data, offset, audioCodec) {
|
|
10777
10710
|
track.channelCount = config.channelCount;
|
10778
10711
|
track.codec = config.codec;
|
10779
10712
|
track.manifestCodec = config.manifestCodec;
|
10780
|
-
track.
|
10781
|
-
logger.log(`parsed codec:${track.parsedCodec}, codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
|
10713
|
+
logger.log(`parsed codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`);
|
10782
10714
|
}
|
10783
10715
|
}
|
10784
10716
|
function getFrameDuration(samplerate) {
|
@@ -11954,7 +11886,7 @@ class SampleAesDecrypter {
|
|
11954
11886
|
});
|
11955
11887
|
}
|
11956
11888
|
decryptBuffer(encryptedData) {
|
11957
|
-
return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer
|
11889
|
+
return this.decrypter.decrypt(encryptedData, this.keyData.key.buffer, this.keyData.iv.buffer);
|
11958
11890
|
}
|
11959
11891
|
|
11960
11892
|
// AAC - encrypt all full 16 bytes blocks starting from offset 16
|
@@ -14198,7 +14130,7 @@ class MP4Remuxer {
|
|
14198
14130
|
logger.warn(`[mp4-remuxer]: Injecting ${missing} audio frame @ ${(nextPts / inputTimeScale).toFixed(3)}s due to ${Math.round(1000 * delta / inputTimeScale)} ms gap.`);
|
14199
14131
|
for (let j = 0; j < missing; j++) {
|
14200
14132
|
const newStamp = Math.max(nextPts, 0);
|
14201
|
-
let fillFrame = AAC.getSilentFrame(track.
|
14133
|
+
let fillFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
|
14202
14134
|
if (!fillFrame) {
|
14203
14135
|
logger.log('[mp4-remuxer]: Unable to get silent frame for given audio codec; duplicating last frame instead.');
|
14204
14136
|
fillFrame = sample.unit.subarray();
|
@@ -14326,7 +14258,7 @@ class MP4Remuxer {
|
|
14326
14258
|
// samples count of this segment's duration
|
14327
14259
|
const nbSamples = Math.ceil((endDTS - startDTS) / frameDuration);
|
14328
14260
|
// silent frame
|
14329
|
-
const silentFrame = AAC.getSilentFrame(track.
|
14261
|
+
const silentFrame = AAC.getSilentFrame(track.manifestCodec || track.codec, track.channelCount);
|
14330
14262
|
logger.warn('[mp4-remuxer]: remux empty Audio');
|
14331
14263
|
// Can't remux if we can't generate a silent frame...
|
14332
14264
|
if (!silentFrame) {
|
@@ -14720,15 +14652,13 @@ class Transmuxer {
|
|
14720
14652
|
initSegmentData
|
14721
14653
|
} = transmuxConfig;
|
14722
14654
|
const keyData = getEncryptionType(uintData, decryptdata);
|
14723
|
-
if (keyData &&
|
14655
|
+
if (keyData && keyData.method === 'AES-128') {
|
14724
14656
|
const decrypter = this.getDecrypter();
|
14725
|
-
const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
|
14726
|
-
|
14727
14657
|
// Software decryption is synchronous; webCrypto is not
|
14728
14658
|
if (decrypter.isSync()) {
|
14729
14659
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
14730
14660
|
// data is handled in the flush() call
|
14731
|
-
let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer
|
14661
|
+
let decryptedData = decrypter.softwareDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer);
|
14732
14662
|
// For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
|
14733
14663
|
const loadingParts = chunkMeta.part > -1;
|
14734
14664
|
if (loadingParts) {
|
@@ -14740,7 +14670,7 @@ class Transmuxer {
|
|
14740
14670
|
}
|
14741
14671
|
uintData = new Uint8Array(decryptedData);
|
14742
14672
|
} else {
|
14743
|
-
this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer
|
14673
|
+
this.decryptionPromise = decrypter.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer).then(decryptedData => {
|
14744
14674
|
// Calling push here is important; if flush() is called while this is still resolving, this ensures that
|
14745
14675
|
// the decrypted data has been transmuxed
|
14746
14676
|
const result = this.push(decryptedData, null, chunkMeta);
|
@@ -15394,7 +15324,14 @@ class TransmuxerInterface {
|
|
15394
15324
|
this.observer = new EventEmitter();
|
15395
15325
|
this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
|
15396
15326
|
this.observer.on(Events.ERROR, forwardMessage);
|
15397
|
-
const
|
15327
|
+
const MediaSource = getMediaSource(config.preferManagedMediaSource) || {
|
15328
|
+
isTypeSupported: () => false
|
15329
|
+
};
|
15330
|
+
const m2tsTypeSupported = {
|
15331
|
+
mpeg: MediaSource.isTypeSupported('audio/mpeg'),
|
15332
|
+
mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
|
15333
|
+
ac3: MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
|
15334
|
+
};
|
15398
15335
|
|
15399
15336
|
// navigator.vendor is not always available in Web Worker
|
15400
15337
|
// refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
|
@@ -17046,10 +16983,10 @@ class SubtitleStreamController extends BaseStreamController {
|
|
17046
16983
|
return;
|
17047
16984
|
}
|
17048
16985
|
// check to see if the payload needs to be decrypted
|
17049
|
-
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv &&
|
16986
|
+
if (payload && payload.byteLength > 0 && decryptData != null && decryptData.key && decryptData.iv && decryptData.method === 'AES-128') {
|
17050
16987
|
const startTime = performance.now();
|
17051
16988
|
// decrypt the subtitles
|
17052
|
-
this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer
|
16989
|
+
this.decrypter.decrypt(new Uint8Array(payload), decryptData.key.buffer, decryptData.iv.buffer).catch(err => {
|
17053
16990
|
hls.trigger(Events.ERROR, {
|
17054
16991
|
type: ErrorTypes.MEDIA_ERROR,
|
17055
16992
|
details: ErrorDetails.FRAG_DECRYPT_ERROR,
|
@@ -17192,10 +17129,10 @@ class SubtitleTrackController extends BasePlaylistController {
|
|
17192
17129
|
this.currentTrack = null;
|
17193
17130
|
this.selectDefaultTrack = true;
|
17194
17131
|
this.queuedDefaultTrack = -1;
|
17132
|
+
this.asyncPollTrackChange = () => this.pollTrackChange(0);
|
17195
17133
|
this.useTextTrackPolling = false;
|
17196
17134
|
this.subtitlePollingInterval = -1;
|
17197
17135
|
this._subtitleDisplay = true;
|
17198
|
-
this.asyncPollTrackChange = () => this.pollTrackChange(0);
|
17199
17136
|
this.onTextTracksChanged = () => {
|
17200
17137
|
if (!this.useTextTrackPolling) {
|
17201
17138
|
self.clearInterval(this.subtitlePollingInterval);
|
@@ -17229,7 +17166,6 @@ class SubtitleTrackController extends BasePlaylistController {
|
|
17229
17166
|
this.tracks.length = 0;
|
17230
17167
|
this.tracksInGroup.length = 0;
|
17231
17168
|
this.currentTrack = null;
|
17232
|
-
// @ts-ignore
|
17233
17169
|
this.onTextTracksChanged = this.asyncPollTrackChange = null;
|
17234
17170
|
super.destroy();
|
17235
17171
|
}
|
@@ -17790,12 +17726,6 @@ class BufferController {
|
|
17790
17726
|
this.lastMpegAudioChunk = null;
|
17791
17727
|
// @ts-ignore
|
17792
17728
|
this.hls = null;
|
17793
|
-
// @ts-ignore
|
17794
|
-
this._onMediaSourceOpen = this._onMediaSourceClose = null;
|
17795
|
-
// @ts-ignore
|
17796
|
-
this._onMediaSourceEnded = null;
|
17797
|
-
// @ts-ignore
|
17798
|
-
this._onStartStreaming = this._onEndStreaming = null;
|
17799
17729
|
}
|
17800
17730
|
registerListeners() {
|
17801
17731
|
const {
|
@@ -21060,12 +20990,14 @@ class TimelineController {
|
|
21060
20990
|
this.cea608Parser1 = this.cea608Parser2 = undefined;
|
21061
20991
|
}
|
21062
20992
|
initCea608Parsers() {
|
21063
|
-
|
21064
|
-
|
21065
|
-
|
21066
|
-
|
21067
|
-
|
21068
|
-
|
20993
|
+
if (this.config.enableCEA708Captions && (!this.cea608Parser1 || !this.cea608Parser2)) {
|
20994
|
+
const channel1 = new OutputFilter(this, 'textTrack1');
|
20995
|
+
const channel2 = new OutputFilter(this, 'textTrack2');
|
20996
|
+
const channel3 = new OutputFilter(this, 'textTrack3');
|
20997
|
+
const channel4 = new OutputFilter(this, 'textTrack4');
|
20998
|
+
this.cea608Parser1 = new Cea608Parser(1, channel1, channel2);
|
20999
|
+
this.cea608Parser2 = new Cea608Parser(3, channel3, channel4);
|
21000
|
+
}
|
21069
21001
|
}
|
21070
21002
|
addCues(trackName, startTime, endTime, screen, cueRanges) {
|
21071
21003
|
// skip cues which overlap more than 50% with previously parsed time ranges
|
@@ -21348,23 +21280,26 @@ class TimelineController {
|
|
21348
21280
|
return level == null ? void 0 : level.attrs['CLOSED-CAPTIONS'];
|
21349
21281
|
}
|
21350
21282
|
onFragLoading(event, data) {
|
21283
|
+
this.initCea608Parsers();
|
21284
|
+
const {
|
21285
|
+
cea608Parser1,
|
21286
|
+
cea608Parser2,
|
21287
|
+
lastCc,
|
21288
|
+
lastSn,
|
21289
|
+
lastPartIndex
|
21290
|
+
} = this;
|
21291
|
+
if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
|
21292
|
+
return;
|
21293
|
+
}
|
21351
21294
|
// if this frag isn't contiguous, clear the parser so cues with bad start/end times aren't added to the textTrack
|
21352
|
-
if (
|
21295
|
+
if (data.frag.type === PlaylistLevelType.MAIN) {
|
21353
21296
|
var _data$part$index, _data$part;
|
21354
|
-
const {
|
21355
|
-
cea608Parser1,
|
21356
|
-
cea608Parser2,
|
21357
|
-
lastSn
|
21358
|
-
} = this;
|
21359
|
-
if (!cea608Parser1 || !cea608Parser2) {
|
21360
|
-
return;
|
21361
|
-
}
|
21362
21297
|
const {
|
21363
21298
|
cc,
|
21364
21299
|
sn
|
21365
21300
|
} = data.frag;
|
21366
|
-
const partIndex = (_data$part$index = (_data$part = data.part) == null ? void 0 : _data$part.index) != null ? _data$part$index : -1;
|
21367
|
-
if (!(sn === lastSn + 1 || sn === lastSn && partIndex ===
|
21301
|
+
const partIndex = (_data$part$index = data == null ? void 0 : (_data$part = data.part) == null ? void 0 : _data$part.index) != null ? _data$part$index : -1;
|
21302
|
+
if (!(sn === lastSn + 1 || sn === lastSn && partIndex === lastPartIndex + 1 || cc === lastCc)) {
|
21368
21303
|
cea608Parser1.reset();
|
21369
21304
|
cea608Parser2.reset();
|
21370
21305
|
}
|
@@ -21523,7 +21458,12 @@ class TimelineController {
|
|
21523
21458
|
this.captionsTracks = {};
|
21524
21459
|
}
|
21525
21460
|
onFragParsingUserdata(event, data) {
|
21526
|
-
|
21461
|
+
this.initCea608Parsers();
|
21462
|
+
const {
|
21463
|
+
cea608Parser1,
|
21464
|
+
cea608Parser2
|
21465
|
+
} = this;
|
21466
|
+
if (!this.enabled || !cea608Parser1 || !cea608Parser2) {
|
21527
21467
|
return;
|
21528
21468
|
}
|
21529
21469
|
const {
|
@@ -21538,12 +21478,9 @@ class TimelineController {
|
|
21538
21478
|
for (let i = 0; i < samples.length; i++) {
|
21539
21479
|
const ccBytes = samples[i].bytes;
|
21540
21480
|
if (ccBytes) {
|
21541
|
-
if (!this.cea608Parser1) {
|
21542
|
-
this.initCea608Parsers();
|
21543
|
-
}
|
21544
21481
|
const ccdatas = this.extractCea608Data(ccBytes);
|
21545
|
-
|
21546
|
-
|
21482
|
+
cea608Parser1.addData(samples[i].pts, ccdatas[0]);
|
21483
|
+
cea608Parser2.addData(samples[i].pts, ccdatas[1]);
|
21547
21484
|
}
|
21548
21485
|
}
|
21549
21486
|
}
|
@@ -21971,104 +21908,12 @@ class EMEController {
|
|
21971
21908
|
this.mediaKeySessions = [];
|
21972
21909
|
this.keyIdToKeySessionPromise = {};
|
21973
21910
|
this.setMediaKeysQueue = EMEController.CDMCleanupPromise ? [EMEController.CDMCleanupPromise] : [];
|
21911
|
+
this.onMediaEncrypted = this._onMediaEncrypted.bind(this);
|
21912
|
+
this.onWaitingForKey = this._onWaitingForKey.bind(this);
|
21974
21913
|
this.debug = logger.debug.bind(logger, LOGGER_PREFIX);
|
21975
21914
|
this.log = logger.log.bind(logger, LOGGER_PREFIX);
|
21976
21915
|
this.warn = logger.warn.bind(logger, LOGGER_PREFIX);
|
21977
21916
|
this.error = logger.error.bind(logger, LOGGER_PREFIX);
|
21978
|
-
this.onMediaEncrypted = event => {
|
21979
|
-
const {
|
21980
|
-
initDataType,
|
21981
|
-
initData
|
21982
|
-
} = event;
|
21983
|
-
this.debug(`"${event.type}" event: init data type: "${initDataType}"`);
|
21984
|
-
|
21985
|
-
// Ignore event when initData is null
|
21986
|
-
if (initData === null) {
|
21987
|
-
return;
|
21988
|
-
}
|
21989
|
-
let keyId;
|
21990
|
-
let keySystemDomain;
|
21991
|
-
if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) {
|
21992
|
-
// Match sinf keyId to playlist skd://keyId=
|
21993
|
-
const json = bin2str(new Uint8Array(initData));
|
21994
|
-
try {
|
21995
|
-
const sinf = base64Decode(JSON.parse(json).sinf);
|
21996
|
-
const tenc = parseSinf(new Uint8Array(sinf));
|
21997
|
-
if (!tenc) {
|
21998
|
-
return;
|
21999
|
-
}
|
22000
|
-
keyId = tenc.subarray(8, 24);
|
22001
|
-
keySystemDomain = KeySystems.FAIRPLAY;
|
22002
|
-
} catch (error) {
|
22003
|
-
this.warn('Failed to parse sinf "encrypted" event message initData');
|
22004
|
-
return;
|
22005
|
-
}
|
22006
|
-
} else {
|
22007
|
-
// Support clear-lead key-session creation (otherwise depend on playlist keys)
|
22008
|
-
const psshInfo = parsePssh(initData);
|
22009
|
-
if (psshInfo === null) {
|
22010
|
-
return;
|
22011
|
-
}
|
22012
|
-
if (psshInfo.version === 0 && psshInfo.systemId === KeySystemIds.WIDEVINE && psshInfo.data) {
|
22013
|
-
keyId = psshInfo.data.subarray(8, 24);
|
22014
|
-
}
|
22015
|
-
keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
|
22016
|
-
}
|
22017
|
-
if (!keySystemDomain || !keyId) {
|
22018
|
-
return;
|
22019
|
-
}
|
22020
|
-
const keyIdHex = Hex.hexDump(keyId);
|
22021
|
-
const {
|
22022
|
-
keyIdToKeySessionPromise,
|
22023
|
-
mediaKeySessions
|
22024
|
-
} = this;
|
22025
|
-
let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
|
22026
|
-
for (let i = 0; i < mediaKeySessions.length; i++) {
|
22027
|
-
// Match playlist key
|
22028
|
-
const keyContext = mediaKeySessions[i];
|
22029
|
-
const decryptdata = keyContext.decryptdata;
|
22030
|
-
if (decryptdata.pssh || !decryptdata.keyId) {
|
22031
|
-
continue;
|
22032
|
-
}
|
22033
|
-
const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
|
22034
|
-
if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
|
22035
|
-
keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
|
22036
|
-
delete keyIdToKeySessionPromise[oldKeyIdHex];
|
22037
|
-
decryptdata.pssh = new Uint8Array(initData);
|
22038
|
-
decryptdata.keyId = keyId;
|
22039
|
-
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
|
22040
|
-
return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
|
22041
|
-
});
|
22042
|
-
break;
|
22043
|
-
}
|
22044
|
-
}
|
22045
|
-
if (!keySessionContextPromise) {
|
22046
|
-
// Clear-lead key (not encountered in playlist)
|
22047
|
-
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
|
22048
|
-
keySystem,
|
22049
|
-
mediaKeys
|
22050
|
-
}) => {
|
22051
|
-
var _keySystemToKeySystem;
|
22052
|
-
this.throwIfDestroyed();
|
22053
|
-
const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
|
22054
|
-
decryptdata.pssh = new Uint8Array(initData);
|
22055
|
-
decryptdata.keyId = keyId;
|
22056
|
-
return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
|
22057
|
-
this.throwIfDestroyed();
|
22058
|
-
const keySessionContext = this.createMediaKeySessionContext({
|
22059
|
-
decryptdata,
|
22060
|
-
keySystem,
|
22061
|
-
mediaKeys
|
22062
|
-
});
|
22063
|
-
return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
|
22064
|
-
});
|
22065
|
-
});
|
22066
|
-
}
|
22067
|
-
keySessionContextPromise.catch(error => this.handleError(error));
|
22068
|
-
};
|
22069
|
-
this.onWaitingForKey = event => {
|
22070
|
-
this.log(`"${event.type}" event`);
|
22071
|
-
};
|
22072
21917
|
this.hls = hls;
|
22073
21918
|
this.config = hls.config;
|
22074
21919
|
this.registerListeners();
|
@@ -22082,9 +21927,9 @@ class EMEController {
|
|
22082
21927
|
config.licenseXhrSetup = config.licenseResponseCallback = undefined;
|
22083
21928
|
config.drmSystems = config.drmSystemOptions = {};
|
22084
21929
|
// @ts-ignore
|
22085
|
-
this.hls = this.
|
21930
|
+
this.hls = this.onMediaEncrypted = this.onWaitingForKey = this.keyIdToKeySessionPromise = null;
|
22086
21931
|
// @ts-ignore
|
22087
|
-
this.
|
21932
|
+
this.config = null;
|
22088
21933
|
}
|
22089
21934
|
registerListeners() {
|
22090
21935
|
this.hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
@@ -22348,6 +22193,100 @@ class EMEController {
|
|
22348
22193
|
}
|
22349
22194
|
return this.attemptKeySystemAccess(keySystemsToAttempt);
|
22350
22195
|
}
|
22196
|
+
_onMediaEncrypted(event) {
|
22197
|
+
const {
|
22198
|
+
initDataType,
|
22199
|
+
initData
|
22200
|
+
} = event;
|
22201
|
+
this.debug(`"${event.type}" event: init data type: "${initDataType}"`);
|
22202
|
+
|
22203
|
+
// Ignore event when initData is null
|
22204
|
+
if (initData === null) {
|
22205
|
+
return;
|
22206
|
+
}
|
22207
|
+
let keyId;
|
22208
|
+
let keySystemDomain;
|
22209
|
+
if (initDataType === 'sinf' && this.config.drmSystems[KeySystems.FAIRPLAY]) {
|
22210
|
+
// Match sinf keyId to playlist skd://keyId=
|
22211
|
+
const json = bin2str(new Uint8Array(initData));
|
22212
|
+
try {
|
22213
|
+
const sinf = base64Decode(JSON.parse(json).sinf);
|
22214
|
+
const tenc = parseSinf(new Uint8Array(sinf));
|
22215
|
+
if (!tenc) {
|
22216
|
+
return;
|
22217
|
+
}
|
22218
|
+
keyId = tenc.subarray(8, 24);
|
22219
|
+
keySystemDomain = KeySystems.FAIRPLAY;
|
22220
|
+
} catch (error) {
|
22221
|
+
this.warn('Failed to parse sinf "encrypted" event message initData');
|
22222
|
+
return;
|
22223
|
+
}
|
22224
|
+
} else {
|
22225
|
+
// Support clear-lead key-session creation (otherwise depend on playlist keys)
|
22226
|
+
const psshInfo = parsePssh(initData);
|
22227
|
+
if (psshInfo === null) {
|
22228
|
+
return;
|
22229
|
+
}
|
22230
|
+
if (psshInfo.version === 0 && psshInfo.systemId === KeySystemIds.WIDEVINE && psshInfo.data) {
|
22231
|
+
keyId = psshInfo.data.subarray(8, 24);
|
22232
|
+
}
|
22233
|
+
keySystemDomain = keySystemIdToKeySystemDomain(psshInfo.systemId);
|
22234
|
+
}
|
22235
|
+
if (!keySystemDomain || !keyId) {
|
22236
|
+
return;
|
22237
|
+
}
|
22238
|
+
const keyIdHex = Hex.hexDump(keyId);
|
22239
|
+
const {
|
22240
|
+
keyIdToKeySessionPromise,
|
22241
|
+
mediaKeySessions
|
22242
|
+
} = this;
|
22243
|
+
let keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex];
|
22244
|
+
for (let i = 0; i < mediaKeySessions.length; i++) {
|
22245
|
+
// Match playlist key
|
22246
|
+
const keyContext = mediaKeySessions[i];
|
22247
|
+
const decryptdata = keyContext.decryptdata;
|
22248
|
+
if (decryptdata.pssh || !decryptdata.keyId) {
|
22249
|
+
continue;
|
22250
|
+
}
|
22251
|
+
const oldKeyIdHex = Hex.hexDump(decryptdata.keyId);
|
22252
|
+
if (keyIdHex === oldKeyIdHex || decryptdata.uri.replace(/-/g, '').indexOf(keyIdHex) !== -1) {
|
22253
|
+
keySessionContextPromise = keyIdToKeySessionPromise[oldKeyIdHex];
|
22254
|
+
delete keyIdToKeySessionPromise[oldKeyIdHex];
|
22255
|
+
decryptdata.pssh = new Uint8Array(initData);
|
22256
|
+
decryptdata.keyId = keyId;
|
22257
|
+
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = keySessionContextPromise.then(() => {
|
22258
|
+
return this.generateRequestWithPreferredKeySession(keyContext, initDataType, initData, 'encrypted-event-key-match');
|
22259
|
+
});
|
22260
|
+
break;
|
22261
|
+
}
|
22262
|
+
}
|
22263
|
+
if (!keySessionContextPromise) {
|
22264
|
+
// Clear-lead key (not encountered in playlist)
|
22265
|
+
keySessionContextPromise = keyIdToKeySessionPromise[keyIdHex] = this.getKeySystemSelectionPromise([keySystemDomain]).then(({
|
22266
|
+
keySystem,
|
22267
|
+
mediaKeys
|
22268
|
+
}) => {
|
22269
|
+
var _keySystemToKeySystem;
|
22270
|
+
this.throwIfDestroyed();
|
22271
|
+
const decryptdata = new LevelKey('ISO-23001-7', keyIdHex, (_keySystemToKeySystem = keySystemDomainToKeySystemFormat(keySystem)) != null ? _keySystemToKeySystem : '');
|
22272
|
+
decryptdata.pssh = new Uint8Array(initData);
|
22273
|
+
decryptdata.keyId = keyId;
|
22274
|
+
return this.attemptSetMediaKeys(keySystem, mediaKeys).then(() => {
|
22275
|
+
this.throwIfDestroyed();
|
22276
|
+
const keySessionContext = this.createMediaKeySessionContext({
|
22277
|
+
decryptdata,
|
22278
|
+
keySystem,
|
22279
|
+
mediaKeys
|
22280
|
+
});
|
22281
|
+
return this.generateRequestWithPreferredKeySession(keySessionContext, initDataType, initData, 'encrypted-event-no-match');
|
22282
|
+
});
|
22283
|
+
});
|
22284
|
+
}
|
22285
|
+
keySessionContextPromise.catch(error => this.handleError(error));
|
22286
|
+
}
|
22287
|
+
_onWaitingForKey(event) {
|
22288
|
+
this.log(`"${event.type}" event`);
|
22289
|
+
}
|
22351
22290
|
attemptSetMediaKeys(keySystem, mediaKeys) {
|
22352
22291
|
const queue = this.setMediaKeysQueue.slice();
|
22353
22292
|
this.log(`Setting media-keys for "${keySystem}"`);
|
@@ -25450,6 +25389,7 @@ function enableStreamingMode(config) {
|
|
25450
25389
|
}
|
25451
25390
|
}
|
25452
25391
|
|
25392
|
+
let chromeOrFirefox;
|
25453
25393
|
class LevelController extends BasePlaylistController {
|
25454
25394
|
constructor(hls, contentSteeringController) {
|
25455
25395
|
super(hls, '[level-controller]');
|
@@ -25523,15 +25463,23 @@ class LevelController extends BasePlaylistController {
|
|
25523
25463
|
let videoCodecFound = false;
|
25524
25464
|
let audioCodecFound = false;
|
25525
25465
|
data.levels.forEach(levelParsed => {
|
25526
|
-
var _videoCodec;
|
25466
|
+
var _audioCodec, _videoCodec;
|
25527
25467
|
const attributes = levelParsed.attrs;
|
25468
|
+
|
25469
|
+
// erase audio codec info if browser does not support mp4a.40.34.
|
25470
|
+
// demuxer will autodetect codec and fallback to mpeg/audio
|
25528
25471
|
let {
|
25529
25472
|
audioCodec,
|
25530
25473
|
videoCodec
|
25531
25474
|
} = levelParsed;
|
25475
|
+
if (((_audioCodec = audioCodec) == null ? void 0 : _audioCodec.indexOf('mp4a.40.34')) !== -1) {
|
25476
|
+
chromeOrFirefox || (chromeOrFirefox = /chrome|firefox/i.test(navigator.userAgent));
|
25477
|
+
if (chromeOrFirefox) {
|
25478
|
+
levelParsed.audioCodec = audioCodec = undefined;
|
25479
|
+
}
|
25480
|
+
}
|
25532
25481
|
if (audioCodec) {
|
25533
|
-
|
25534
|
-
levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource) || undefined;
|
25482
|
+
levelParsed.audioCodec = audioCodec = getCodecCompatibleName(audioCodec, preferManagedMediaSource);
|
25535
25483
|
}
|
25536
25484
|
if (((_videoCodec = videoCodec) == null ? void 0 : _videoCodec.indexOf('avc1')) === 0) {
|
25537
25485
|
videoCodec = levelParsed.videoCodec = convertAVC1ToAVCOTI(videoCodec);
|
@@ -25656,8 +25604,8 @@ class LevelController extends BasePlaylistController {
|
|
25656
25604
|
return valueB - valueA;
|
25657
25605
|
}
|
25658
25606
|
}
|
25659
|
-
if (a.
|
25660
|
-
return a.
|
25607
|
+
if (a.averageBitrate !== b.averageBitrate) {
|
25608
|
+
return a.averageBitrate - b.averageBitrate;
|
25661
25609
|
}
|
25662
25610
|
return 0;
|
25663
25611
|
});
|
@@ -26117,8 +26065,6 @@ class KeyLoader {
|
|
26117
26065
|
}
|
26118
26066
|
return this.loadKeyEME(keyInfo, frag);
|
26119
26067
|
case 'AES-128':
|
26120
|
-
case 'AES-256':
|
26121
|
-
case 'AES-256-CTR':
|
26122
26068
|
return this.loadKeyHTTP(keyInfo, frag);
|
26123
26069
|
default:
|
26124
26070
|
return Promise.reject(this.createKeyLoadError(frag, ErrorDetails.KEY_LOAD_ERROR, new Error(`Key supplied with unsupported METHOD: "${decryptdata.method}"`)));
|
@@ -26588,32 +26534,13 @@ class StreamController extends BaseStreamController {
|
|
26588
26534
|
this.altAudio = false;
|
26589
26535
|
this.audioOnly = false;
|
26590
26536
|
this.fragPlaying = null;
|
26537
|
+
this.onvplaying = null;
|
26538
|
+
this.onvseeked = null;
|
26591
26539
|
this.fragLastKbps = 0;
|
26592
26540
|
this.couldBacktrack = false;
|
26593
26541
|
this.backtrackFragment = null;
|
26594
26542
|
this.audioCodecSwitch = false;
|
26595
26543
|
this.videoBuffer = null;
|
26596
|
-
this.onMediaPlaying = () => {
|
26597
|
-
// tick to speed up FRAG_CHANGED triggering
|
26598
|
-
this.tick();
|
26599
|
-
};
|
26600
|
-
this.onMediaSeeked = () => {
|
26601
|
-
const media = this.media;
|
26602
|
-
const currentTime = media ? media.currentTime : null;
|
26603
|
-
if (isFiniteNumber(currentTime)) {
|
26604
|
-
this.log(`Media seeked to ${currentTime.toFixed(3)}`);
|
26605
|
-
}
|
26606
|
-
|
26607
|
-
// If seeked was issued before buffer was appended do not tick immediately
|
26608
|
-
const bufferInfo = this.getMainFwdBufferInfo();
|
26609
|
-
if (bufferInfo === null || bufferInfo.len === 0) {
|
26610
|
-
this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
|
26611
|
-
return;
|
26612
|
-
}
|
26613
|
-
|
26614
|
-
// tick to speed up FRAG_CHANGED triggering
|
26615
|
-
this.tick();
|
26616
|
-
};
|
26617
26544
|
this._registerListeners();
|
26618
26545
|
}
|
26619
26546
|
_registerListeners() {
|
@@ -26655,8 +26582,6 @@ class StreamController extends BaseStreamController {
|
|
26655
26582
|
}
|
26656
26583
|
onHandlerDestroying() {
|
26657
26584
|
this._unregisterListeners();
|
26658
|
-
// @ts-ignore
|
26659
|
-
this.onMediaPlaying = this.onMediaSeeked = null;
|
26660
26585
|
super.onHandlerDestroying();
|
26661
26586
|
}
|
26662
26587
|
startLoad(startPosition) {
|
@@ -26983,17 +26908,20 @@ class StreamController extends BaseStreamController {
|
|
26983
26908
|
onMediaAttached(event, data) {
|
26984
26909
|
super.onMediaAttached(event, data);
|
26985
26910
|
const media = data.media;
|
26986
|
-
|
26987
|
-
|
26911
|
+
this.onvplaying = this.onMediaPlaying.bind(this);
|
26912
|
+
this.onvseeked = this.onMediaSeeked.bind(this);
|
26913
|
+
media.addEventListener('playing', this.onvplaying);
|
26914
|
+
media.addEventListener('seeked', this.onvseeked);
|
26988
26915
|
this.gapController = new GapController(this.config, media, this.fragmentTracker, this.hls);
|
26989
26916
|
}
|
26990
26917
|
onMediaDetaching() {
|
26991
26918
|
const {
|
26992
26919
|
media
|
26993
26920
|
} = this;
|
26994
|
-
if (media) {
|
26995
|
-
media.removeEventListener('playing', this.
|
26996
|
-
media.removeEventListener('seeked', this.
|
26921
|
+
if (media && this.onvplaying && this.onvseeked) {
|
26922
|
+
media.removeEventListener('playing', this.onvplaying);
|
26923
|
+
media.removeEventListener('seeked', this.onvseeked);
|
26924
|
+
this.onvplaying = this.onvseeked = null;
|
26997
26925
|
this.videoBuffer = null;
|
26998
26926
|
}
|
26999
26927
|
this.fragPlaying = null;
|
@@ -27003,6 +26931,27 @@ class StreamController extends BaseStreamController {
|
|
27003
26931
|
}
|
27004
26932
|
super.onMediaDetaching();
|
27005
26933
|
}
|
26934
|
+
onMediaPlaying() {
|
26935
|
+
// tick to speed up FRAG_CHANGED triggering
|
26936
|
+
this.tick();
|
26937
|
+
}
|
26938
|
+
onMediaSeeked() {
|
26939
|
+
const media = this.media;
|
26940
|
+
const currentTime = media ? media.currentTime : null;
|
26941
|
+
if (isFiniteNumber(currentTime)) {
|
26942
|
+
this.log(`Media seeked to ${currentTime.toFixed(3)}`);
|
26943
|
+
}
|
26944
|
+
|
26945
|
+
// If seeked was issued before buffer was appended do not tick immediately
|
26946
|
+
const bufferInfo = this.getMainFwdBufferInfo();
|
26947
|
+
if (bufferInfo === null || bufferInfo.len === 0) {
|
26948
|
+
this.warn(`Main forward buffer length on "seeked" event ${bufferInfo ? bufferInfo.len : 'empty'})`);
|
26949
|
+
return;
|
26950
|
+
}
|
26951
|
+
|
26952
|
+
// tick to speed up FRAG_CHANGED triggering
|
26953
|
+
this.tick();
|
26954
|
+
}
|
27006
26955
|
onManifestLoading() {
|
27007
26956
|
// reset buffer on manifest loading
|
27008
26957
|
this.log('Trigger BUFFER_RESET');
|
@@ -27733,7 +27682,7 @@ class Hls {
|
|
27733
27682
|
* Get the video-dev/hls.js package version.
|
27734
27683
|
*/
|
27735
27684
|
static get version() {
|
27736
|
-
return "1.5.2
|
27685
|
+
return "1.5.2";
|
27737
27686
|
}
|
27738
27687
|
|
27739
27688
|
/**
|