hls.js 1.5.6 → 1.5.7-0.canary.10015
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/README.md +1 -0
- package/dist/hls-demo.js +10 -0
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +2077 -1166
- package/dist/hls.js.d.ts +65 -50
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +1149 -858
- 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 +985 -695
- 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 +1758 -862
- 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 +21 -21
- package/src/config.ts +3 -2
- package/src/controller/abr-controller.ts +21 -20
- package/src/controller/audio-stream-controller.ts +15 -16
- package/src/controller/audio-track-controller.ts +1 -1
- package/src/controller/base-playlist-controller.ts +20 -8
- package/src/controller/base-stream-controller.ts +149 -33
- package/src/controller/buffer-controller.ts +11 -11
- package/src/controller/cap-level-controller.ts +1 -2
- package/src/controller/cmcd-controller.ts +27 -6
- package/src/controller/content-steering-controller.ts +8 -6
- package/src/controller/eme-controller.ts +9 -22
- package/src/controller/error-controller.ts +6 -8
- package/src/controller/fps-controller.ts +2 -3
- package/src/controller/gap-controller.ts +43 -16
- package/src/controller/latency-controller.ts +9 -11
- package/src/controller/level-controller.ts +12 -18
- package/src/controller/stream-controller.ts +24 -31
- package/src/controller/subtitle-stream-controller.ts +13 -14
- package/src/controller/subtitle-track-controller.ts +5 -3
- package/src/controller/timeline-controller.ts +23 -30
- package/src/crypt/aes-crypto.ts +21 -2
- package/src/crypt/decrypter-aes-mode.ts +4 -0
- package/src/crypt/decrypter.ts +32 -18
- package/src/crypt/fast-aes-key.ts +24 -5
- package/src/demux/audio/adts.ts +9 -4
- package/src/demux/sample-aes.ts +2 -0
- package/src/demux/transmuxer-interface.ts +4 -12
- package/src/demux/transmuxer-worker.ts +4 -4
- package/src/demux/transmuxer.ts +16 -3
- package/src/demux/tsdemuxer.ts +71 -37
- package/src/demux/video/avc-video-parser.ts +208 -119
- package/src/demux/video/base-video-parser.ts +134 -2
- package/src/demux/video/exp-golomb.ts +0 -208
- package/src/demux/video/hevc-video-parser.ts +746 -0
- package/src/events.ts +7 -0
- package/src/hls.ts +42 -34
- package/src/loader/fragment-loader.ts +9 -2
- package/src/loader/key-loader.ts +2 -0
- package/src/loader/level-key.ts +10 -9
- package/src/loader/playlist-loader.ts +4 -5
- package/src/remux/mp4-generator.ts +196 -1
- package/src/remux/mp4-remuxer.ts +23 -7
- package/src/task-loop.ts +5 -2
- package/src/types/component-api.ts +2 -0
- package/src/types/demuxer.ts +3 -0
- package/src/types/events.ts +4 -0
- package/src/utils/codecs.ts +33 -4
- package/src/utils/encryption-methods-util.ts +21 -0
- package/src/utils/logger.ts +54 -24
- package/src/utils/mp4-tools.ts +3 -1
package/src/crypt/aes-crypto.ts
CHANGED
@@ -1,13 +1,32 @@
|
|
1
|
+
import { DecrypterAesMode } from './decrypter-aes-mode';
|
2
|
+
|
1
3
|
export default class AESCrypto {
|
2
4
|
private subtle: SubtleCrypto;
|
3
5
|
private aesIV: Uint8Array;
|
6
|
+
private aesMode: DecrypterAesMode;
|
4
7
|
|
5
|
-
constructor(subtle: SubtleCrypto, iv: Uint8Array) {
|
8
|
+
constructor(subtle: SubtleCrypto, iv: Uint8Array, aesMode: DecrypterAesMode) {
|
6
9
|
this.subtle = subtle;
|
7
10
|
this.aesIV = iv;
|
11
|
+
this.aesMode = aesMode;
|
8
12
|
}
|
9
13
|
|
10
14
|
decrypt(data: ArrayBuffer, key: CryptoKey) {
|
11
|
-
|
15
|
+
switch (this.aesMode) {
|
16
|
+
case DecrypterAesMode.cbc:
|
17
|
+
return this.subtle.decrypt(
|
18
|
+
{ name: 'AES-CBC', iv: this.aesIV },
|
19
|
+
key,
|
20
|
+
data,
|
21
|
+
);
|
22
|
+
case DecrypterAesMode.ctr:
|
23
|
+
return this.subtle.decrypt(
|
24
|
+
{ name: 'AES-CTR', counter: this.aesIV, length: 64 }, //64 : NIST SP800-38A standard suggests that the counter should occupy half of the counter block
|
25
|
+
key,
|
26
|
+
data,
|
27
|
+
);
|
28
|
+
default:
|
29
|
+
throw new Error(`[AESCrypto] invalid aes mode ${this.aesMode}`);
|
30
|
+
}
|
12
31
|
}
|
13
32
|
}
|
package/src/crypt/decrypter.ts
CHANGED
@@ -4,6 +4,7 @@ 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';
|
7
8
|
import type { HlsConfig } from '../config';
|
8
9
|
|
9
10
|
const CHUNK_SIZE = 16; // 16 bytes, 128 bits
|
@@ -19,9 +20,10 @@ export default class Decrypter {
|
|
19
20
|
private currentIV: ArrayBuffer | null = null;
|
20
21
|
private currentResult: ArrayBuffer | null = null;
|
21
22
|
private useSoftware: boolean;
|
23
|
+
private enableSoftwareAES: boolean;
|
22
24
|
|
23
25
|
constructor(config: HlsConfig, { removePKCS7Padding = true } = {}) {
|
24
|
-
this.
|
26
|
+
this.enableSoftwareAES = config.enableSoftwareAES;
|
25
27
|
this.removePKCS7Padding = removePKCS7Padding;
|
26
28
|
// built in decryptor expects PKCS7 padding
|
27
29
|
if (removePKCS7Padding) {
|
@@ -36,9 +38,7 @@ export default class Decrypter {
|
|
36
38
|
/* no-op */
|
37
39
|
}
|
38
40
|
}
|
39
|
-
|
40
|
-
this.useSoftware = true;
|
41
|
-
}
|
41
|
+
this.useSoftware = this.subtle === null;
|
42
42
|
}
|
43
43
|
|
44
44
|
destroy() {
|
@@ -82,10 +82,11 @@ export default class Decrypter {
|
|
82
82
|
data: Uint8Array | ArrayBuffer,
|
83
83
|
key: ArrayBuffer,
|
84
84
|
iv: ArrayBuffer,
|
85
|
+
aesMode: DecrypterAesMode,
|
85
86
|
): Promise<ArrayBuffer> {
|
86
87
|
if (this.useSoftware) {
|
87
88
|
return new Promise((resolve, reject) => {
|
88
|
-
this.softwareDecrypt(new Uint8Array(data), key, iv);
|
89
|
+
this.softwareDecrypt(new Uint8Array(data), key, iv, aesMode);
|
89
90
|
const decryptResult = this.flush();
|
90
91
|
if (decryptResult) {
|
91
92
|
resolve(decryptResult.buffer);
|
@@ -94,7 +95,7 @@ export default class Decrypter {
|
|
94
95
|
}
|
95
96
|
});
|
96
97
|
}
|
97
|
-
return this.webCryptoDecrypt(new Uint8Array(data), key, iv);
|
98
|
+
return this.webCryptoDecrypt(new Uint8Array(data), key, iv, aesMode);
|
98
99
|
}
|
99
100
|
|
100
101
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
@@ -103,8 +104,13 @@ export default class Decrypter {
|
|
103
104
|
data: Uint8Array,
|
104
105
|
key: ArrayBuffer,
|
105
106
|
iv: ArrayBuffer,
|
107
|
+
aesMode: DecrypterAesMode,
|
106
108
|
): ArrayBuffer | null {
|
107
109
|
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
|
+
}
|
108
114
|
this.logOnce('JS AES decrypt');
|
109
115
|
// The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
|
110
116
|
// This is done in order to strip PKCS7 padding, which is found at the end of each segment. We only know we've reached
|
@@ -147,21 +153,22 @@ export default class Decrypter {
|
|
147
153
|
data: Uint8Array,
|
148
154
|
key: ArrayBuffer,
|
149
155
|
iv: ArrayBuffer,
|
156
|
+
aesMode: DecrypterAesMode,
|
150
157
|
): Promise<ArrayBuffer> {
|
151
158
|
const subtle = this.subtle;
|
152
159
|
if (this.key !== key || !this.fastAesKey) {
|
153
160
|
this.key = key;
|
154
|
-
this.fastAesKey = new FastAESKey(subtle, key);
|
161
|
+
this.fastAesKey = new FastAESKey(subtle, key, aesMode);
|
155
162
|
}
|
156
163
|
return this.fastAesKey
|
157
164
|
.expandKey()
|
158
|
-
.then((aesKey) => {
|
165
|
+
.then((aesKey: CryptoKey) => {
|
159
166
|
// decrypt using web crypto
|
160
167
|
if (!subtle) {
|
161
168
|
return Promise.reject(new Error('web crypto not initialized'));
|
162
169
|
}
|
163
170
|
this.logOnce('WebCrypto AES decrypt');
|
164
|
-
const crypto = new AESCrypto(subtle, new Uint8Array(iv));
|
171
|
+
const crypto = new AESCrypto(subtle, new Uint8Array(iv), aesMode);
|
165
172
|
return crypto.decrypt(data.buffer, aesKey);
|
166
173
|
})
|
167
174
|
.catch((err) => {
|
@@ -169,19 +176,26 @@ export default class Decrypter {
|
|
169
176
|
`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`,
|
170
177
|
);
|
171
178
|
|
172
|
-
return this.onWebCryptoError(data, key, iv);
|
179
|
+
return this.onWebCryptoError(data, key, iv, aesMode);
|
173
180
|
});
|
174
181
|
}
|
175
182
|
|
176
|
-
private onWebCryptoError(data, key, iv): ArrayBuffer | never {
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
+
private onWebCryptoError(data, key, iv, aesMode): ArrayBuffer | never {
|
184
|
+
const enableSoftwareAES = this.enableSoftwareAES;
|
185
|
+
if (enableSoftwareAES) {
|
186
|
+
this.useSoftware = true;
|
187
|
+
this.logEnabled = true;
|
188
|
+
this.softwareDecrypt(data, key, iv, aesMode);
|
189
|
+
const decryptResult = this.flush();
|
190
|
+
if (decryptResult) {
|
191
|
+
return decryptResult.buffer;
|
192
|
+
}
|
183
193
|
}
|
184
|
-
throw new Error(
|
194
|
+
throw new Error(
|
195
|
+
'WebCrypto' +
|
196
|
+
(enableSoftwareAES ? ' and softwareDecrypt' : '') +
|
197
|
+
': failed to decrypt data',
|
198
|
+
);
|
185
199
|
}
|
186
200
|
|
187
201
|
private getValidChunk(data: Uint8Array): Uint8Array {
|
@@ -1,16 +1,35 @@
|
|
1
|
+
import { DecrypterAesMode } from './decrypter-aes-mode';
|
2
|
+
|
1
3
|
export default class FastAESKey {
|
2
4
|
private subtle: any;
|
3
5
|
private key: ArrayBuffer;
|
6
|
+
private aesMode: DecrypterAesMode;
|
4
7
|
|
5
|
-
constructor(subtle, key) {
|
8
|
+
constructor(subtle, key, aesMode: DecrypterAesMode) {
|
6
9
|
this.subtle = subtle;
|
7
10
|
this.key = key;
|
11
|
+
this.aesMode = aesMode;
|
8
12
|
}
|
9
13
|
|
10
14
|
expandKey() {
|
11
|
-
|
12
|
-
|
13
|
-
'
|
14
|
-
|
15
|
+
const subtleAlgoName = getSubtleAlgoName(this.aesMode);
|
16
|
+
return this.subtle.importKey(
|
17
|
+
'raw',
|
18
|
+
this.key,
|
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}`);
|
15
34
|
}
|
16
35
|
}
|
package/src/demux/audio/adts.ts
CHANGED
@@ -17,6 +17,7 @@ type AudioConfig = {
|
|
17
17
|
samplerate: number;
|
18
18
|
channelCount: number;
|
19
19
|
codec: string;
|
20
|
+
parsedCodec: string;
|
20
21
|
manifestCodec: string;
|
21
22
|
};
|
22
23
|
|
@@ -32,6 +33,7 @@ export function getAudioConfig(
|
|
32
33
|
audioCodec: string,
|
33
34
|
): AudioConfig | void {
|
34
35
|
let adtsObjectType: number;
|
36
|
+
let originalAdtsObjectType: number;
|
35
37
|
let adtsExtensionSamplingIndex: number;
|
36
38
|
let adtsChannelConfig: number;
|
37
39
|
let config: number[];
|
@@ -42,7 +44,8 @@ export function getAudioConfig(
|
|
42
44
|
8000, 7350,
|
43
45
|
];
|
44
46
|
// byte 2
|
45
|
-
adtsObjectType =
|
47
|
+
adtsObjectType = originalAdtsObjectType =
|
48
|
+
((data[offset + 2] & 0xc0) >>> 6) + 1;
|
46
49
|
const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
|
47
50
|
if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
|
48
51
|
const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
|
@@ -61,8 +64,8 @@ export function getAudioConfig(
|
|
61
64
|
logger.log(
|
62
65
|
`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`,
|
63
66
|
);
|
64
|
-
//
|
65
|
-
if (/firefox/i.test(userAgent)) {
|
67
|
+
// Firefox and Pale Moon: freq less than 24kHz = AAC SBR (HE-AAC)
|
68
|
+
if (/firefox|palemoon/i.test(userAgent)) {
|
66
69
|
if (adtsSamplingIndex >= 6) {
|
67
70
|
adtsObjectType = 5;
|
68
71
|
config = new Array(4);
|
@@ -167,6 +170,7 @@ export function getAudioConfig(
|
|
167
170
|
samplerate: adtsSamplingRates[adtsSamplingIndex],
|
168
171
|
channelCount: adtsChannelConfig,
|
169
172
|
codec: 'mp4a.40.' + adtsObjectType,
|
173
|
+
parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
|
170
174
|
manifestCodec,
|
171
175
|
};
|
172
176
|
}
|
@@ -244,8 +248,9 @@ export function initTrackConfig(
|
|
244
248
|
track.channelCount = config.channelCount;
|
245
249
|
track.codec = config.codec;
|
246
250
|
track.manifestCodec = config.manifestCodec;
|
251
|
+
track.parsedCodec = config.parsedCodec;
|
247
252
|
logger.log(
|
248
|
-
`parsed codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`,
|
253
|
+
`parsed codec:${track.parsedCodec}, codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`,
|
249
254
|
);
|
250
255
|
}
|
251
256
|
}
|
package/src/demux/sample-aes.ts
CHANGED
@@ -4,6 +4,7 @@
|
|
4
4
|
|
5
5
|
import { HlsConfig } from '../config';
|
6
6
|
import Decrypter from '../crypt/decrypter';
|
7
|
+
import { DecrypterAesMode } from '../crypt/decrypter-aes-mode';
|
7
8
|
import { HlsEventEmitter } from '../events';
|
8
9
|
import type {
|
9
10
|
AudioSample,
|
@@ -30,6 +31,7 @@ class SampleAesDecrypter {
|
|
30
31
|
encryptedData,
|
31
32
|
this.keyData.key.buffer,
|
32
33
|
this.keyData.iv.buffer,
|
34
|
+
DecrypterAesMode.cbc,
|
33
35
|
);
|
34
36
|
}
|
35
37
|
|
@@ -12,14 +12,13 @@ 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';
|
16
15
|
import { EventEmitter } from 'eventemitter3';
|
17
16
|
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';
|
23
22
|
import type { RationalTimestamp } from '../utils/timescale-conversion';
|
24
23
|
|
25
24
|
export default class TransmuxerInterface {
|
@@ -64,16 +63,9 @@ export default class TransmuxerInterface {
|
|
64
63
|
this.observer.on(Events.FRAG_DECRYPTED, forwardMessage);
|
65
64
|
this.observer.on(Events.ERROR, forwardMessage);
|
66
65
|
|
67
|
-
const
|
68
|
-
|
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
|
-
};
|
66
|
+
const m2tsTypeSupported = getM2TSSupportedAudioTypes(
|
67
|
+
config.preferManagedMediaSource,
|
68
|
+
);
|
77
69
|
|
78
70
|
// navigator.vendor is not always available in Web Worker
|
79
71
|
// refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import Transmuxer, { isPromise } from '../demux/transmuxer';
|
2
2
|
import { Events } from '../events';
|
3
|
-
import {
|
3
|
+
import { enableLogs, type ILogFunction, type ILogger } from '../utils/logger';
|
4
4
|
import { EventEmitter } from 'eventemitter3';
|
5
5
|
import { ErrorDetails, ErrorTypes } from '../errors';
|
6
6
|
import type { RemuxedTrack, RemuxerResult } from '../types/remuxer';
|
@@ -21,7 +21,7 @@ function startWorker(self) {
|
|
21
21
|
observer.on(Events.ERROR, forwardMessage);
|
22
22
|
|
23
23
|
// forward logger events to main thread
|
24
|
-
const forwardWorkerLogs = () => {
|
24
|
+
const forwardWorkerLogs = (logger: ILogger) => {
|
25
25
|
for (const logFn in logger) {
|
26
26
|
const func: ILogFunction = (message?) => {
|
27
27
|
forwardMessage('workerLog', {
|
@@ -46,8 +46,8 @@ function startWorker(self) {
|
|
46
46
|
data.vendor,
|
47
47
|
data.id,
|
48
48
|
);
|
49
|
-
enableLogs(config.debug, data.id);
|
50
|
-
forwardWorkerLogs();
|
49
|
+
const logger = enableLogs(config.debug, data.id);
|
50
|
+
forwardWorkerLogs(logger);
|
51
51
|
forwardMessage('init', null);
|
52
52
|
break;
|
53
53
|
}
|
package/src/demux/transmuxer.ts
CHANGED
@@ -4,18 +4,23 @@ 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
|
7
|
+
import TSDemuxer 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';
|
13
17
|
import type { Demuxer, DemuxerResult, KeyData } from '../types/demuxer';
|
14
18
|
import type { Remuxer } from '../types/remuxer';
|
15
19
|
import type { TransmuxerResult, ChunkMetadata } from '../types/transmuxer';
|
16
20
|
import type { HlsConfig } from '../config';
|
17
21
|
import type { DecryptData } from '../loader/level-key';
|
18
22
|
import type { PlaylistLevelType } from '../types/loader';
|
23
|
+
import type { TypeSupported } from '../utils/codecs';
|
19
24
|
import type { RationalTimestamp } from '../utils/timescale-conversion';
|
20
25
|
import { optionalSelf } from '../utils/global';
|
21
26
|
|
@@ -114,8 +119,10 @@ export default class Transmuxer {
|
|
114
119
|
} = transmuxConfig;
|
115
120
|
|
116
121
|
const keyData = getEncryptionType(uintData, decryptdata);
|
117
|
-
if (keyData && keyData.method
|
122
|
+
if (keyData && isFullSegmentEncryption(keyData.method)) {
|
118
123
|
const decrypter = this.getDecrypter();
|
124
|
+
const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
|
125
|
+
|
119
126
|
// Software decryption is synchronous; webCrypto is not
|
120
127
|
if (decrypter.isSync()) {
|
121
128
|
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
@@ -124,6 +131,7 @@ export default class Transmuxer {
|
|
124
131
|
uintData,
|
125
132
|
keyData.key.buffer,
|
126
133
|
keyData.iv.buffer,
|
134
|
+
aesMode,
|
127
135
|
);
|
128
136
|
// For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
|
129
137
|
const loadingParts = chunkMeta.part > -1;
|
@@ -137,7 +145,12 @@ export default class Transmuxer {
|
|
137
145
|
uintData = new Uint8Array(decryptedData);
|
138
146
|
} else {
|
139
147
|
this.decryptionPromise = decrypter
|
140
|
-
.webCryptoDecrypt(
|
148
|
+
.webCryptoDecrypt(
|
149
|
+
uintData,
|
150
|
+
keyData.key.buffer,
|
151
|
+
keyData.iv.buffer,
|
152
|
+
aesMode,
|
153
|
+
)
|
141
154
|
.then((decryptedData): TransmuxerResult => {
|
142
155
|
// Calling push here is important; if flush() is called while this is still resolving, this ensures that
|
143
156
|
// the decrypted data has been transmuxed
|
package/src/demux/tsdemuxer.ts
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
* highly optimized TS demuxer:
|
3
3
|
* parse PAT, PMT
|
4
4
|
* extract PES packet from audio and video PIDs
|
5
|
-
* extract AVC/H264 NAL units and AAC/ADTS samples from PES packet
|
5
|
+
* extract AVC/H264 (or HEVC/H265) NAL units and AAC/ADTS samples from PES packet
|
6
6
|
* trigger the remuxer upon parsing completion
|
7
7
|
* it also tries to workaround as best as it can audio codec switch (HE-AAC to AAC and vice versa), without having to restart the MediaSource.
|
8
8
|
* it also controls the remuxing process :
|
@@ -12,7 +12,9 @@
|
|
12
12
|
import * as ADTS from './audio/adts';
|
13
13
|
import * as MpegAudio from './audio/mpegaudio';
|
14
14
|
import * as AC3 from './audio/ac3-demuxer';
|
15
|
+
import BaseVideoParser from './video/base-video-parser';
|
15
16
|
import AvcVideoParser from './video/avc-video-parser';
|
17
|
+
import HevcVideoParser from './video/hevc-video-parser';
|
16
18
|
import SampleAesDecrypter from './sample-aes';
|
17
19
|
import { Events } from '../events';
|
18
20
|
import { appendUint8Array, RemuxerTrackIdConfig } from '../utils/mp4-tools';
|
@@ -20,20 +22,21 @@ import { logger } from '../utils/logger';
|
|
20
22
|
import { ErrorTypes, ErrorDetails } from '../errors';
|
21
23
|
import type { HlsConfig } from '../config';
|
22
24
|
import type { HlsEventEmitter } from '../events';
|
25
|
+
import type { TypeSupported } from '../utils/codecs';
|
23
26
|
import {
|
24
|
-
DemuxedVideoTrack,
|
25
|
-
DemuxedAudioTrack,
|
26
|
-
DemuxedTrack,
|
27
|
-
Demuxer,
|
28
|
-
DemuxerResult,
|
29
|
-
VideoSample,
|
30
|
-
DemuxedMetadataTrack,
|
31
|
-
DemuxedUserdataTrack,
|
32
|
-
ElementaryStreamData,
|
33
|
-
KeyData,
|
34
27
|
MetadataSchema,
|
28
|
+
type DemuxedVideoTrack,
|
29
|
+
type DemuxedAudioTrack,
|
30
|
+
type DemuxedTrack,
|
31
|
+
type Demuxer,
|
32
|
+
type DemuxerResult,
|
33
|
+
type VideoSample,
|
34
|
+
type DemuxedMetadataTrack,
|
35
|
+
type DemuxedUserdataTrack,
|
36
|
+
type ElementaryStreamData,
|
37
|
+
type KeyData,
|
35
38
|
} from '../types/demuxer';
|
36
|
-
import { AudioFrame } from '../types/demuxer';
|
39
|
+
import type { AudioFrame } from '../types/demuxer';
|
37
40
|
|
38
41
|
export type ParsedTimestamp = {
|
39
42
|
pts?: number;
|
@@ -48,12 +51,6 @@ export type PES = ParsedTimestamp & {
|
|
48
51
|
export type ParsedVideoSample = ParsedTimestamp &
|
49
52
|
Omit<VideoSample, 'pts' | 'dts'>;
|
50
53
|
|
51
|
-
export interface TypeSupported {
|
52
|
-
mpeg: boolean;
|
53
|
-
mp3: boolean;
|
54
|
-
ac3: boolean;
|
55
|
-
}
|
56
|
-
|
57
54
|
const PACKET_LENGTH = 188;
|
58
55
|
|
59
56
|
class TSDemuxer implements Demuxer {
|
@@ -74,7 +71,7 @@ class TSDemuxer implements Demuxer {
|
|
74
71
|
private _txtTrack?: DemuxedUserdataTrack;
|
75
72
|
private aacOverFlow: AudioFrame | null = null;
|
76
73
|
private remainderData: Uint8Array | null = null;
|
77
|
-
private videoParser:
|
74
|
+
private videoParser: BaseVideoParser | null;
|
78
75
|
|
79
76
|
constructor(
|
80
77
|
observer: HlsEventEmitter,
|
@@ -84,7 +81,7 @@ class TSDemuxer implements Demuxer {
|
|
84
81
|
this.observer = observer;
|
85
82
|
this.config = config;
|
86
83
|
this.typeSupported = typeSupported;
|
87
|
-
this.videoParser =
|
84
|
+
this.videoParser = null;
|
88
85
|
}
|
89
86
|
|
90
87
|
static probe(data: Uint8Array) {
|
@@ -292,13 +289,27 @@ class TSDemuxer implements Demuxer {
|
|
292
289
|
case videoPid:
|
293
290
|
if (stt) {
|
294
291
|
if (videoData && (pes = parsePES(videoData))) {
|
295
|
-
this.videoParser
|
296
|
-
videoTrack
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
292
|
+
if (this.videoParser === null) {
|
293
|
+
switch (videoTrack.segmentCodec) {
|
294
|
+
case 'avc':
|
295
|
+
this.videoParser = new AvcVideoParser();
|
296
|
+
break;
|
297
|
+
case 'hevc':
|
298
|
+
if (__USE_M2TS_ADVANCED_CODECS__) {
|
299
|
+
this.videoParser = new HevcVideoParser();
|
300
|
+
}
|
301
|
+
break;
|
302
|
+
}
|
303
|
+
}
|
304
|
+
if (this.videoParser !== null) {
|
305
|
+
this.videoParser.parsePES(
|
306
|
+
videoTrack,
|
307
|
+
textTrack,
|
308
|
+
pes,
|
309
|
+
false,
|
310
|
+
this._duration,
|
311
|
+
);
|
312
|
+
}
|
302
313
|
}
|
303
314
|
|
304
315
|
videoData = { data: [], size: 0 };
|
@@ -470,14 +481,28 @@ class TSDemuxer implements Demuxer {
|
|
470
481
|
// try to parse last PES packets
|
471
482
|
let pes: PES | null;
|
472
483
|
if (videoData && (pes = parsePES(videoData))) {
|
473
|
-
this.videoParser
|
474
|
-
videoTrack
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
484
|
+
if (this.videoParser === null) {
|
485
|
+
switch (videoTrack.segmentCodec) {
|
486
|
+
case 'avc':
|
487
|
+
this.videoParser = new AvcVideoParser();
|
488
|
+
break;
|
489
|
+
case 'hevc':
|
490
|
+
if (__USE_M2TS_ADVANCED_CODECS__) {
|
491
|
+
this.videoParser = new HevcVideoParser();
|
492
|
+
}
|
493
|
+
break;
|
494
|
+
}
|
495
|
+
}
|
496
|
+
if (this.videoParser !== null) {
|
497
|
+
this.videoParser.parsePES(
|
498
|
+
videoTrack as DemuxedVideoTrack,
|
499
|
+
textTrack as DemuxedUserdataTrack,
|
500
|
+
pes,
|
501
|
+
true,
|
502
|
+
this._duration,
|
503
|
+
);
|
504
|
+
videoTrack.pesData = null;
|
505
|
+
}
|
481
506
|
} else {
|
482
507
|
// either avcData null or PES truncated, keep it for next frag parsing
|
483
508
|
videoTrack.pesData = videoData;
|
@@ -874,8 +899,17 @@ function parsePMT(
|
|
874
899
|
case 0x87:
|
875
900
|
logger.warn('Unsupported EC-3 in M2TS found');
|
876
901
|
break;
|
877
|
-
|
878
|
-
|
902
|
+
|
903
|
+
case 0x24: // ITU-T Rec. H.265 and ISO/IEC 23008-2 (HEVC)
|
904
|
+
if (__USE_M2TS_ADVANCED_CODECS__) {
|
905
|
+
if (result.videoPid === -1) {
|
906
|
+
result.videoPid = pid;
|
907
|
+
result.segmentVideoCodec = 'hevc';
|
908
|
+
logger.log('HEVC in M2TS found');
|
909
|
+
}
|
910
|
+
} else {
|
911
|
+
logger.warn('Unsupported HEVC in M2TS found');
|
912
|
+
}
|
879
913
|
break;
|
880
914
|
|
881
915
|
default:
|