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/src/crypt/decrypter.ts
CHANGED
@@ -4,7 +4,6 @@ import AESDecryptor, { removePadding } from './aes-decryptor';
|
|
4
4
|
import { logger } from '../utils/logger';
|
5
5
|
import { appendUint8Array } from '../utils/mp4-tools';
|
6
6
|
import { sliceUint8 } from '../utils/typed-array';
|
7
|
-
import { DecrypterAesMode } from './decrypter-aes-mode';
|
8
7
|
import type { HlsConfig } from '../config';
|
9
8
|
|
10
9
|
const CHUNK_SIZE = 16; // 16 bytes, 128 bits
|
@@ -20,10 +19,9 @@ export default class Decrypter {
|
|
20
19
|
private currentIV: ArrayBuffer | null = null;
|
21
20
|
private currentResult: ArrayBuffer | null = null;
|
22
21
|
private useSoftware: boolean;
|
23
|
-
private enableSoftwareAES: boolean;
|
24
22
|
|
25
23
|
constructor(config: HlsConfig, { removePKCS7Padding = true } = {}) {
|
26
|
-
this.
|
24
|
+
this.useSoftware = config.enableSoftwareAES;
|
27
25
|
this.removePKCS7Padding = removePKCS7Padding;
|
28
26
|
// built in decryptor expects PKCS7 padding
|
29
27
|
if (removePKCS7Padding) {
|
@@ -38,7 +36,9 @@ export default class Decrypter {
|
|
38
36
|
/* no-op */
|
39
37
|
}
|
40
38
|
}
|
41
|
-
|
39
|
+
if (this.subtle === null) {
|
40
|
+
this.useSoftware = true;
|
41
|
+
}
|
42
42
|
}
|
43
43
|
|
44
44
|
destroy() {
|
@@ -82,11 +82,10 @@ export default class Decrypter {
|
|
82
82
|
data: Uint8Array | ArrayBuffer,
|
83
83
|
key: ArrayBuffer,
|
84
84
|
iv: ArrayBuffer,
|
85
|
-
aesMode: DecrypterAesMode,
|
86
85
|
): Promise<ArrayBuffer> {
|
87
86
|
if (this.useSoftware) {
|
88
87
|
return new Promise((resolve, reject) => {
|
89
|
-
this.softwareDecrypt(new Uint8Array(data), key, iv
|
88
|
+
this.softwareDecrypt(new Uint8Array(data), key, iv);
|
90
89
|
const decryptResult = this.flush();
|
91
90
|
if (decryptResult) {
|
92
91
|
resolve(decryptResult.buffer);
|
@@ -95,7 +94,7 @@ export default class Decrypter {
|
|
95
94
|
}
|
96
95
|
});
|
97
96
|
}
|
98
|
-
return this.webCryptoDecrypt(new Uint8Array(data), key, iv
|
97
|
+
return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
|
99
98
|
}
|
100
99
|
|
101
100
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
@@ -104,13 +103,8 @@ export default class Decrypter {
|
|
104
103
|
data: Uint8Array,
|
105
104
|
key: ArrayBuffer,
|
106
105
|
iv: ArrayBuffer,
|
107
|
-
aesMode: DecrypterAesMode,
|
108
106
|
): ArrayBuffer | null {
|
109
107
|
const { currentIV, currentResult, remainderData } = this;
|
110
|
-
if (aesMode !== DecrypterAesMode.cbc || key.byteLength !== 16) {
|
111
|
-
logger.warn('SoftwareDecrypt: can only handle AES-128-CBC');
|
112
|
-
return null;
|
113
|
-
}
|
114
108
|
this.logOnce('JS AES decrypt');
|
115
109
|
// The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
|
116
110
|
// This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached
|
@@ -153,22 +147,21 @@ export default class Decrypter {
|
|
153
147
|
data: Uint8Array,
|
154
148
|
key: ArrayBuffer,
|
155
149
|
iv: ArrayBuffer,
|
156
|
-
aesMode: DecrypterAesMode,
|
157
150
|
): Promise<ArrayBuffer> {
|
158
151
|
const subtle = this.subtle;
|
159
152
|
if (this.key !== key || !this.fastAesKey) {
|
160
153
|
this.key = key;
|
161
|
-
this.fastAesKey = new FastAESKey(subtle, key
|
154
|
+
this.fastAesKey = new FastAESKey(subtle, key);
|
162
155
|
}
|
163
156
|
return this.fastAesKey
|
164
157
|
.expandKey()
|
165
|
-
.then((aesKey
|
158
|
+
.then((aesKey) => {
|
166
159
|
// decrypt using web crypto
|
167
160
|
if (!subtle) {
|
168
161
|
return Promise.reject(new Error('web crypto not initialized'));
|
169
162
|
}
|
170
163
|
this.logOnce('WebCrypto AES decrypt');
|
171
|
-
const crypto = new AESCrypto(subtle, new Uint8Array(iv)
|
164
|
+
const crypto = new AESCrypto(subtle, new Uint8Array(iv));
|
172
165
|
return crypto.decrypt(data.buffer, aesKey);
|
173
166
|
})
|
174
167
|
.catch((err) => {
|
@@ -176,26 +169,19 @@ export default class Decrypter {
|
|
176
169
|
`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`,
|
177
170
|
);
|
178
171
|
|
179
|
-
return this.onWebCryptoError(data, key, iv
|
172
|
+
return this.onWebCryptoError(data, key, iv);
|
180
173
|
});
|
181
174
|
}
|
182
175
|
|
183
|
-
private onWebCryptoError(data, key, iv
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
if (decryptResult) {
|
191
|
-
return decryptResult.buffer;
|
192
|
-
}
|
176
|
+
private onWebCryptoError(data, key, iv): ArrayBuffer | never {
|
177
|
+
this.useSoftware = true;
|
178
|
+
this.logEnabled = true;
|
179
|
+
this.softwareDecrypt(data, key, iv);
|
180
|
+
const decryptResult = this.flush();
|
181
|
+
if (decryptResult) {
|
182
|
+
return decryptResult.buffer;
|
193
183
|
}
|
194
|
-
throw new Error(
|
195
|
-
'WebCrypto' +
|
196
|
-
(enableSoftwareAES ? ' and softwareDecrypt' : '') +
|
197
|
-
': failed to decrypt data',
|
198
|
-
);
|
184
|
+
throw new Error('WebCrypto and softwareDecrypt: failed to decrypt data');
|
199
185
|
}
|
200
186
|
|
201
187
|
private getValidChunk(data: Uint8Array): Uint8Array {
|
@@ -1,35 +1,16 @@
|
|
1
|
-
import { DecrypterAesMode } from './decrypter-aes-mode';
|
2
|
-
|
3
1
|
export default class FastAESKey {
|
4
2
|
private subtle: any;
|
5
3
|
private key: ArrayBuffer;
|
6
|
-
private aesMode: DecrypterAesMode;
|
7
4
|
|
8
|
-
constructor(subtle, key
|
5
|
+
constructor(subtle, key) {
|
9
6
|
this.subtle = subtle;
|
10
7
|
this.key = key;
|
11
|
-
this.aesMode = aesMode;
|
12
8
|
}
|
13
9
|
|
14
10
|
expandKey() {
|
15
|
-
|
16
|
-
|
17
|
-
'
|
18
|
-
|
19
|
-
{ name: subtleAlgoName },
|
20
|
-
false,
|
21
|
-
['encrypt', 'decrypt'],
|
22
|
-
);
|
23
|
-
}
|
24
|
-
}
|
25
|
-
|
26
|
-
function getSubtleAlgoName(aesMode: DecrypterAesMode) {
|
27
|
-
switch (aesMode) {
|
28
|
-
case DecrypterAesMode.cbc:
|
29
|
-
return 'AES-CBC';
|
30
|
-
case DecrypterAesMode.ctr:
|
31
|
-
return 'AES-CTR';
|
32
|
-
default:
|
33
|
-
throw new Error(`[FastAESKey] invalid aes mode ${aesMode}`);
|
11
|
+
return this.subtle.importKey('raw', this.key, { name: 'AES-CBC' }, false, [
|
12
|
+
'encrypt',
|
13
|
+
'decrypt',
|
14
|
+
]);
|
34
15
|
}
|
35
16
|
}
|
package/src/demux/audio/adts.ts
CHANGED
@@ -17,7 +17,6 @@ type AudioConfig = {
|
|
17
17
|
samplerate: number;
|
18
18
|
channelCount: number;
|
19
19
|
codec: string;
|
20
|
-
parsedCodec: string;
|
21
20
|
manifestCodec: string;
|
22
21
|
};
|
23
22
|
|
@@ -33,7 +32,6 @@ export function getAudioConfig(
|
|
33
32
|
audioCodec: string,
|
34
33
|
): AudioConfig | void {
|
35
34
|
let adtsObjectType: number;
|
36
|
-
let originalAdtsObjectType: number;
|
37
35
|
let adtsExtensionSamplingIndex: number;
|
38
36
|
let adtsChannelConfig: number;
|
39
37
|
let config: number[];
|
@@ -44,8 +42,7 @@ export function getAudioConfig(
|
|
44
42
|
8000, 7350,
|
45
43
|
];
|
46
44
|
// byte 2
|
47
|
-
adtsObjectType =
|
48
|
-
((data[offset + 2] & 0xc0) >>> 6) + 1;
|
45
|
+
adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
|
49
46
|
const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
|
50
47
|
if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
|
51
48
|
const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
|
@@ -64,8 +61,8 @@ export function getAudioConfig(
|
|
64
61
|
logger.log(
|
65
62
|
`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`,
|
66
63
|
);
|
67
|
-
//
|
68
|
-
if (/firefox
|
64
|
+
// firefox: freq less than 24kHz = AAC SBR (HE-AAC)
|
65
|
+
if (/firefox/i.test(userAgent)) {
|
69
66
|
if (adtsSamplingIndex >= 6) {
|
70
67
|
adtsObjectType = 5;
|
71
68
|
config = new Array(4);
|
@@ -170,7 +167,6 @@ export function getAudioConfig(
|
|
170
167
|
samplerate: adtsSamplingRates[adtsSamplingIndex],
|
171
168
|
channelCount: adtsChannelConfig,
|
172
169
|
codec: 'mp4a.40.' + adtsObjectType,
|
173
|
-
parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
|
174
170
|
manifestCodec,
|
175
171
|
};
|
176
172
|
}
|
@@ -248,9 +244,8 @@ export function initTrackConfig(
|
|
248
244
|
track.channelCount = config.channelCount;
|
249
245
|
track.codec = config.codec;
|
250
246
|
track.manifestCodec = config.manifestCodec;
|
251
|
-
track.parsedCodec = config.parsedCodec;
|
252
247
|
logger.log(
|
253
|
-
`parsed codec:${track.
|
248
|
+
`parsed codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`,
|
254
249
|
);
|
255
250
|
}
|
256
251
|
}
|
package/src/demux/sample-aes.ts
CHANGED
@@ -4,7 +4,6 @@
|
|
4
4
|
|
5
5
|
import { HlsConfig } from '../config';
|
6
6
|
import Decrypter from '../crypt/decrypter';
|
7
|
-
import { DecrypterAesMode } from '../crypt/decrypter-aes-mode';
|
8
7
|
import { HlsEventEmitter } from '../events';
|
9
8
|
import type {
|
10
9
|
AudioSample,
|
@@ -31,7 +30,6 @@ class SampleAesDecrypter {
|
|
31
30
|
encryptedData,
|
32
31
|
this.keyData.key.buffer,
|
33
32
|
this.keyData.iv.buffer,
|
34
|
-
DecrypterAesMode.cbc,
|
35
33
|
);
|
36
34
|
}
|
37
35
|
|
@@ -12,13 +12,14 @@ import Transmuxer, {
|
|
12
12
|
} from '../demux/transmuxer';
|
13
13
|
import { logger } from '../utils/logger';
|
14
14
|
import { ErrorTypes, ErrorDetails } from '../errors';
|
15
|
+
import { getMediaSource } from '../utils/mediasource-helper';
|
15
16
|
import { EventEmitter } from 'eventemitter3';
|
16
17
|
import { Fragment, Part } from '../loader/fragment';
|
17
|
-
import { getM2TSSupportedAudioTypes } from '../utils/codecs';
|
18
18
|
import type { ChunkMetadata, TransmuxerResult } from '../types/transmuxer';
|
19
19
|
import type Hls from '../hls';
|
20
20
|
import type { HlsEventEmitter } from '../events';
|
21
21
|
import type { PlaylistLevelType } from '../types/loader';
|
22
|
+
import type { TypeSupported } from './tsdemuxer';
|
22
23
|
import type { RationalTimestamp } from '../utils/timescale-conversion';
|
23
24
|
|
24
25
|
export default class TransmuxerInterface {
|
@@ -63,9 +64,16 @@ export default class TransmuxerInterface {
|
|
63
64
|
this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
|
64
65
|
this.observer.on(Events.ERROR, forwardMessage);
|
65
66
|
|
66
|
-
const
|
67
|
-
|
68
|
-
|
67
|
+
const MediaSource = getMediaSource(config.preferManagedMediaSource) || {
|
68
|
+
isTypeSupported: () => false,
|
69
|
+
};
|
70
|
+
const m2tsTypeSupported: TypeSupported = {
|
71
|
+
mpeg: MediaSource.isTypeSupported('audio/mpeg'),
|
72
|
+
mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
|
73
|
+
ac3: __USE_M2TS_ADVANCED_CODECS__
|
74
|
+
? MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
|
75
|
+
: false,
|
76
|
+
};
|
69
77
|
|
70
78
|
// navigator.vendor is not always available in Web Worker
|
71
79
|
// refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
|
package/src/demux/transmuxer.ts
CHANGED
@@ -4,23 +4,18 @@ import { ErrorTypes, ErrorDetails } from '../errors';
|
|
4
4
|
import Decrypter from '../crypt/decrypter';
|
5
5
|
import AACDemuxer from './audio/aacdemuxer';
|
6
6
|
import MP4Demuxer from '../demux/mp4demuxer';
|
7
|
-
import TSDemuxer from '../demux/tsdemuxer';
|
7
|
+
import TSDemuxer, { TypeSupported } from '../demux/tsdemuxer';
|
8
8
|
import MP3Demuxer from './audio/mp3demuxer';
|
9
9
|
import { AC3Demuxer } from './audio/ac3-demuxer';
|
10
10
|
import MP4Remuxer from '../remux/mp4-remuxer';
|
11
11
|
import PassThroughRemuxer from '../remux/passthrough-remuxer';
|
12
12
|
import { logger } from '../utils/logger';
|
13
|
-
import {
|
14
|
-
isFullSegmentEncryption,
|
15
|
-
getAesModeFromFullSegmentMethod,
|
16
|
-
} from '../utils/encryption-methods-util';
|
17
13
|
import type { Demuxer, DemuxerResult, KeyData } from '../types/demuxer';
|
18
14
|
import type { Remuxer } from '../types/remuxer';
|
19
15
|
import type { TransmuxerResult, ChunkMetadata } from '../types/transmuxer';
|
20
16
|
import type { HlsConfig } from '../config';
|
21
17
|
import type { DecryptData } from '../loader/level-key';
|
22
18
|
import type { PlaylistLevelType } from '../types/loader';
|
23
|
-
import type { TypeSupported } from '../utils/codecs';
|
24
19
|
import type { RationalTimestamp } from '../utils/timescale-conversion';
|
25
20
|
import { optionalSelf } from '../utils/global';
|
26
21
|
|
@@ -119,10 +114,8 @@ export default class Transmuxer {
|
|
119
114
|
} = transmuxConfig;
|
120
115
|
|
121
116
|
const keyData = getEncryptionType(uintData, decryptdata);
|
122
|
-
if (keyData &&
|
117
|
+
if (keyData && keyData.method === 'AES-128') {
|
123
118
|
const decrypter = this.getDecrypter();
|
124
|
-
const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
|
125
|
-
|
126
119
|
// Software decryption is synchronous; webCrypto is not
|
127
120
|
if (decrypter.isSync()) {
|
128
121
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
@@ -131,7 +124,6 @@ export default class Transmuxer {
|
|
131
124
|
uintData,
|
132
125
|
keyData.key.buffer,
|
133
126
|
keyData.iv.buffer,
|
134
|
-
aesMode,
|
135
127
|
);
|
136
128
|
// For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
|
137
129
|
const loadingParts = chunkMeta.part > -1;
|
@@ -145,12 +137,7 @@ export default class Transmuxer {
|
|
145
137
|
uintData = new Uint8Array(decryptedData);
|
146
138
|
} else {
|
147
139
|
this.decryptionPromise = decrypter
|
148
|
-
.webCryptoDecrypt(
|
149
|
-
uintData,
|
150
|
-
keyData.key.buffer,
|
151
|
-
keyData.iv.buffer,
|
152
|
-
aesMode,
|
153
|
-
)
|
140
|
+
.webCryptoDecrypt(uintData, keyData.key.buffer, keyData.iv.buffer)
|
154
141
|
.then((decryptedData): TransmuxerResult => {
|
155
142
|
// Calling push here is important; if flush() is called while this is still resolving, this ensures that
|
156
143
|
// the decrypted data has been transmuxed
|
package/src/demux/tsdemuxer.ts
CHANGED
@@ -20,21 +20,20 @@ import { logger } from '../utils/logger';
|
|
20
20
|
import { ErrorTypes, ErrorDetails } from '../errors';
|
21
21
|
import type { HlsConfig } from '../config';
|
22
22
|
import type { HlsEventEmitter } from '../events';
|
23
|
-
import type { TypeSupported } from '../utils/codecs';
|
24
23
|
import {
|
24
|
+
DemuxedVideoTrack,
|
25
|
+
DemuxedAudioTrack,
|
26
|
+
DemuxedTrack,
|
27
|
+
Demuxer,
|
28
|
+
DemuxerResult,
|
29
|
+
VideoSample,
|
30
|
+
DemuxedMetadataTrack,
|
31
|
+
DemuxedUserdataTrack,
|
32
|
+
ElementaryStreamData,
|
33
|
+
KeyData,
|
25
34
|
MetadataSchema,
|
26
|
-
type DemuxedVideoTrack,
|
27
|
-
type DemuxedAudioTrack,
|
28
|
-
type DemuxedTrack,
|
29
|
-
type Demuxer,
|
30
|
-
type DemuxerResult,
|
31
|
-
type VideoSample,
|
32
|
-
type DemuxedMetadataTrack,
|
33
|
-
type DemuxedUserdataTrack,
|
34
|
-
type ElementaryStreamData,
|
35
|
-
type KeyData,
|
36
35
|
} from '../types/demuxer';
|
37
|
-
import
|
36
|
+
import { AudioFrame } from '../types/demuxer';
|
38
37
|
|
39
38
|
export type ParsedTimestamp = {
|
40
39
|
pts?: number;
|
@@ -49,6 +48,12 @@ export type PES = ParsedTimestamp & {
|
|
49
48
|
export type ParsedVideoSample = ParsedTimestamp &
|
50
49
|
Omit<VideoSample, 'pts' | 'dts'>;
|
51
50
|
|
51
|
+
export interface TypeSupported {
|
52
|
+
mpeg: boolean;
|
53
|
+
mp3: boolean;
|
54
|
+
ac3: boolean;
|
55
|
+
}
|
56
|
+
|
52
57
|
const PACKET_LENGTH = 188;
|
53
58
|
|
54
59
|
class TSDemuxer implements Demuxer {
|
@@ -336,11 +336,8 @@ function createLoaderContext(
|
|
336
336
|
if (Number.isFinite(start) && Number.isFinite(end)) {
|
337
337
|
let byteRangeStart = start;
|
338
338
|
let byteRangeEnd = end;
|
339
|
-
if (
|
340
|
-
|
341
|
-
isMethodFullSegmentAesCbc(frag.decryptdata?.method)
|
342
|
-
) {
|
343
|
-
// MAP segment encrypted with method 'AES-128' or 'AES-256' (cbc), when served with HTTP Range,
|
339
|
+
if (frag.sn === 'initSegment' && frag.decryptdata?.method === 'AES-128') {
|
340
|
+
// MAP segment encrypted with method 'AES-128', when served with HTTP Range,
|
344
341
|
// has the unencrypted size specified in the range.
|
345
342
|
// Ref: https://tools.ietf.org/html/draft-pantos-hls-rfc8216bis-08#section-6.3.6
|
346
343
|
const fragmentLen = end - start;
|
@@ -375,10 +372,6 @@ function createGapLoadError(frag: Fragment, part?: Part): LoadError {
|
|
375
372
|
return new LoadError(errorData);
|
376
373
|
}
|
377
374
|
|
378
|
-
function isMethodFullSegmentAesCbc(method) {
|
379
|
-
return method === 'AES-128' || method === 'AES-256';
|
380
|
-
}
|
381
|
-
|
382
375
|
export class LoadError extends Error {
|
383
376
|
public readonly data: FragLoadFailResult;
|
384
377
|
constructor(data: FragLoadFailResult) {
|
package/src/loader/key-loader.ts
CHANGED
@@ -194,8 +194,6 @@ export default class KeyLoader implements ComponentAPI {
|
|
194
194
|
}
|
195
195
|
return this.loadKeyEME(keyInfo, frag);
|
196
196
|
case 'AES-128':
|
197
|
-
case 'AES-256':
|
198
|
-
case 'AES-256-CTR':
|
199
197
|
return this.loadKeyHTTP(keyInfo, frag);
|
200
198
|
default:
|
201
199
|
return Promise.reject(
|
package/src/loader/level-key.ts
CHANGED
@@ -2,7 +2,6 @@ import {
|
|
2
2
|
changeEndianness,
|
3
3
|
convertDataUriToArrayBytes,
|
4
4
|
} from '../utils/keysystem-util';
|
5
|
-
import { isFullSegmentEncryption } from '../utils/encryption-methods-util';
|
6
5
|
import { KeySystemFormats } from '../utils/mediakeys-helper';
|
7
6
|
import { mp4pssh } from '../utils/mp4-tools';
|
8
7
|
import { logger } from '../utils/logger';
|
@@ -52,14 +51,13 @@ export class LevelKey implements DecryptData {
|
|
52
51
|
this.keyFormatVersions = formatversions;
|
53
52
|
this.iv = iv;
|
54
53
|
this.encrypted = method ? method !== 'NONE' : false;
|
55
|
-
this.isCommonEncryption =
|
56
|
-
this.encrypted && !isFullSegmentEncryption(method);
|
54
|
+
this.isCommonEncryption = this.encrypted && method !== 'AES-128';
|
57
55
|
}
|
58
56
|
|
59
57
|
public isSupported(): boolean {
|
60
58
|
// If it's Segment encryption or No encryption, just select that key system
|
61
59
|
if (this.method) {
|
62
|
-
if (
|
60
|
+
if (this.method === 'AES-128' || this.method === 'NONE') {
|
63
61
|
return true;
|
64
62
|
}
|
65
63
|
if (this.keyFormat === 'identity') {
|
@@ -90,15 +88,16 @@ export class LevelKey implements DecryptData {
|
|
90
88
|
return null;
|
91
89
|
}
|
92
90
|
|
93
|
-
if (
|
91
|
+
if (this.method === 'AES-128' && this.uri && !this.iv) {
|
94
92
|
if (typeof sn !== 'number') {
|
95
93
|
// We are fetching decryption data for a initialization segment
|
96
|
-
// If the segment was encrypted with AES-128
|
94
|
+
// If the segment was encrypted with AES-128
|
97
95
|
// It must have an IV defined. We cannot substitute the Segment Number in.
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
96
|
+
if (this.method === 'AES-128' && !this.iv) {
|
97
|
+
logger.warn(
|
98
|
+
`missing IV for initialization segment with method="${this.method}" - compliance issue`,
|
99
|
+
);
|
100
|
+
}
|
102
101
|
// Explicitly set sn to resulting value from implicit conversions 'initSegment' values for IV generation.
|
103
102
|
sn = 0;
|
104
103
|
}
|
package/src/remux/mp4-remuxer.ts
CHANGED
@@ -29,7 +29,6 @@ import type { TrackSet } from '../types/track';
|
|
29
29
|
import type { SourceBufferName } from '../types/buffer';
|
30
30
|
import type { Fragment } from '../loader/fragment';
|
31
31
|
import type { HlsConfig } from '../config';
|
32
|
-
import type { TypeSupported } from '../utils/codecs';
|
33
32
|
|
34
33
|
const MAX_SILENT_FRAME_DURATION = 10 * 1000; // 10 seconds
|
35
34
|
const AAC_SAMPLES_PER_FRAME = 1024;
|
@@ -42,7 +41,7 @@ let safariWebkitVersion: number | null = null;
|
|
42
41
|
export default class MP4Remuxer implements Remuxer {
|
43
42
|
private observer: HlsEventEmitter;
|
44
43
|
private config: HlsConfig;
|
45
|
-
private typeSupported:
|
44
|
+
private typeSupported: any;
|
46
45
|
private ISGenerated: boolean = false;
|
47
46
|
private _initPTS: RationalTimestamp | null = null;
|
48
47
|
private _initDTS: RationalTimestamp | null = null;
|
@@ -928,7 +927,7 @@ export default class MP4Remuxer implements Remuxer {
|
|
928
927
|
for (let j = 0; j < missing; j++) {
|
929
928
|
const newStamp = Math.max(nextPts as number, 0);
|
930
929
|
let fillFrame = AAC.getSilentFrame(
|
931
|
-
track.
|
930
|
+
track.manifestCodec || track.codec,
|
932
931
|
track.channelCount,
|
933
932
|
);
|
934
933
|
if (!fillFrame) {
|
@@ -1078,7 +1077,7 @@ export default class MP4Remuxer implements Remuxer {
|
|
1078
1077
|
const nbSamples: number = Math.ceil((endDTS - startDTS) / frameDuration);
|
1079
1078
|
// silent frame
|
1080
1079
|
const silentFrame: Uint8Array | undefined = AAC.getSilentFrame(
|
1081
|
-
track.
|
1080
|
+
track.manifestCodec || track.codec,
|
1082
1081
|
track.channelCount,
|
1083
1082
|
);
|
1084
1083
|
|
package/src/types/demuxer.ts
CHANGED
package/src/utils/codecs.ts
CHANGED
@@ -147,15 +147,12 @@ function getCodecCompatibleNameLower(
|
|
147
147
|
return CODEC_COMPATIBLE_NAMES[lowerCaseCodec]!;
|
148
148
|
}
|
149
149
|
|
150
|
+
// Idealy fLaC and Opus would be first (spec-compliant) but
|
151
|
+
// some browsers will report that fLaC is supported then fail.
|
152
|
+
// see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
|
150
153
|
const codecsToCheck = {
|
151
|
-
// Idealy fLaC and Opus would be first (spec-compliant) but
|
152
|
-
// some browsers will report that fLaC is supported then fail.
|
153
|
-
// see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
|
154
154
|
flac: ['flac', 'fLaC', 'FLAC'],
|
155
155
|
opus: ['opus', 'Opus'],
|
156
|
-
// Replace audio codec info if browser does not support mp4a.40.34,
|
157
|
-
// and demuxer can fallback to 'audio/mpeg' or 'audio/mp4;codecs="mp3"'
|
158
|
-
'mp4a.40.34': ['mp3'],
|
159
156
|
}[lowerCaseCodec];
|
160
157
|
|
161
158
|
for (let i = 0; i < codecsToCheck.length; i++) {
|
@@ -168,18 +165,13 @@ function getCodecCompatibleNameLower(
|
|
168
165
|
) {
|
169
166
|
CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
|
170
167
|
return codecsToCheck[i];
|
171
|
-
} else if (
|
172
|
-
codecsToCheck[i] === 'mp3' &&
|
173
|
-
getMediaSource(preferManagedMediaSource)?.isTypeSupported('audio/mpeg')
|
174
|
-
) {
|
175
|
-
return '';
|
176
168
|
}
|
177
169
|
}
|
178
170
|
|
179
171
|
return lowerCaseCodec;
|
180
172
|
}
|
181
173
|
|
182
|
-
const AUDIO_CODEC_REGEXP = /flac|opus
|
174
|
+
const AUDIO_CODEC_REGEXP = /flac|opus/i;
|
183
175
|
export function getCodecCompatibleName(
|
184
176
|
codec: string,
|
185
177
|
preferManagedMediaSource = true,
|
@@ -217,24 +209,3 @@ export function convertAVC1ToAVCOTI(codec: string) {
|
|
217
209
|
}
|
218
210
|
return codec;
|
219
211
|
}
|
220
|
-
|
221
|
-
export interface TypeSupported {
|
222
|
-
mpeg: boolean;
|
223
|
-
mp3: boolean;
|
224
|
-
ac3: boolean;
|
225
|
-
}
|
226
|
-
|
227
|
-
export function getM2TSSupportedAudioTypes(
|
228
|
-
preferManagedMediaSource: boolean,
|
229
|
-
): TypeSupported {
|
230
|
-
const MediaSource = getMediaSource(preferManagedMediaSource) || {
|
231
|
-
isTypeSupported: () => false,
|
232
|
-
};
|
233
|
-
return {
|
234
|
-
mpeg: MediaSource.isTypeSupported('audio/mpeg'),
|
235
|
-
mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
|
236
|
-
ac3: __USE_M2TS_ADVANCED_CODECS__
|
237
|
-
? MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
|
238
|
-
: false,
|
239
|
-
};
|
240
|
-
}
|
@@ -1,21 +0,0 @@
|
|
1
|
-
import { DecrypterAesMode } from '../crypt/decrypter-aes-mode';
|
2
|
-
|
3
|
-
export function isFullSegmentEncryption(method: string): boolean {
|
4
|
-
return (
|
5
|
-
method === 'AES-128' || method === 'AES-256' || method === 'AES-256-CTR'
|
6
|
-
);
|
7
|
-
}
|
8
|
-
|
9
|
-
export function getAesModeFromFullSegmentMethod(
|
10
|
-
method: string,
|
11
|
-
): DecrypterAesMode {
|
12
|
-
switch (method) {
|
13
|
-
case 'AES-128':
|
14
|
-
case 'AES-256':
|
15
|
-
return DecrypterAesMode.cbc;
|
16
|
-
case 'AES-256-CTR':
|
17
|
-
return DecrypterAesMode.ctr;
|
18
|
-
default:
|
19
|
-
throw new Error(`invalid full segment method ${method}`);
|
20
|
-
}
|
21
|
-
}
|