@zenvor/hls.js 1.0.0
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/LICENSE +28 -0
- package/README.md +472 -0
- package/dist/hls-demo.js +26995 -0
- package/dist/hls-demo.js.map +1 -0
- package/dist/hls.d.mts +4204 -0
- package/dist/hls.d.ts +4204 -0
- package/dist/hls.js +40050 -0
- package/dist/hls.js.d.ts +4204 -0
- package/dist/hls.js.map +1 -0
- package/dist/hls.light.js +27145 -0
- package/dist/hls.light.js.map +1 -0
- package/dist/hls.light.min.js +2 -0
- package/dist/hls.light.min.js.map +1 -0
- package/dist/hls.light.mjs +26392 -0
- package/dist/hls.light.mjs.map +1 -0
- package/dist/hls.min.js +2 -0
- package/dist/hls.min.js.map +1 -0
- package/dist/hls.mjs +38956 -0
- package/dist/hls.mjs.map +1 -0
- package/dist/hls.worker.js +2 -0
- package/dist/hls.worker.js.map +1 -0
- package/package.json +143 -0
- package/src/config.ts +794 -0
- package/src/controller/abr-controller.ts +1019 -0
- package/src/controller/algo-data-controller.ts +794 -0
- package/src/controller/audio-stream-controller.ts +1099 -0
- package/src/controller/audio-track-controller.ts +454 -0
- package/src/controller/base-playlist-controller.ts +438 -0
- package/src/controller/base-stream-controller.ts +2526 -0
- package/src/controller/buffer-controller.ts +2015 -0
- package/src/controller/buffer-operation-queue.ts +159 -0
- package/src/controller/cap-level-controller.ts +367 -0
- package/src/controller/cmcd-controller.ts +422 -0
- package/src/controller/content-steering-controller.ts +622 -0
- package/src/controller/eme-controller.ts +1617 -0
- package/src/controller/error-controller.ts +627 -0
- package/src/controller/fps-controller.ts +146 -0
- package/src/controller/fragment-finders.ts +256 -0
- package/src/controller/fragment-tracker.ts +567 -0
- package/src/controller/gap-controller.ts +719 -0
- package/src/controller/id3-track-controller.ts +488 -0
- package/src/controller/interstitial-player.ts +302 -0
- package/src/controller/interstitials-controller.ts +2895 -0
- package/src/controller/interstitials-schedule.ts +698 -0
- package/src/controller/latency-controller.ts +294 -0
- package/src/controller/level-controller.ts +776 -0
- package/src/controller/stream-controller.ts +1597 -0
- package/src/controller/subtitle-stream-controller.ts +508 -0
- package/src/controller/subtitle-track-controller.ts +617 -0
- package/src/controller/timeline-controller.ts +677 -0
- package/src/crypt/aes-crypto.ts +36 -0
- package/src/crypt/aes-decryptor.ts +339 -0
- package/src/crypt/decrypter-aes-mode.ts +4 -0
- package/src/crypt/decrypter.ts +225 -0
- package/src/crypt/fast-aes-key.ts +39 -0
- package/src/define-plugin.d.ts +17 -0
- package/src/demux/audio/aacdemuxer.ts +126 -0
- package/src/demux/audio/ac3-demuxer.ts +170 -0
- package/src/demux/audio/adts.ts +249 -0
- package/src/demux/audio/base-audio-demuxer.ts +205 -0
- package/src/demux/audio/dolby.ts +21 -0
- package/src/demux/audio/mp3demuxer.ts +85 -0
- package/src/demux/audio/mpegaudio.ts +177 -0
- package/src/demux/chunk-cache.ts +42 -0
- package/src/demux/dummy-demuxed-track.ts +13 -0
- package/src/demux/inject-worker.ts +75 -0
- package/src/demux/mp4demuxer.ts +234 -0
- package/src/demux/sample-aes.ts +198 -0
- package/src/demux/transmuxer-interface.ts +449 -0
- package/src/demux/transmuxer-worker.ts +221 -0
- package/src/demux/transmuxer.ts +560 -0
- package/src/demux/tsdemuxer.ts +1256 -0
- package/src/demux/video/avc-video-parser.ts +401 -0
- package/src/demux/video/base-video-parser.ts +198 -0
- package/src/demux/video/exp-golomb.ts +153 -0
- package/src/demux/video/hevc-video-parser.ts +736 -0
- package/src/empty-es.js +5 -0
- package/src/empty.js +3 -0
- package/src/errors.ts +107 -0
- package/src/events.ts +548 -0
- package/src/exports-default.ts +3 -0
- package/src/exports-named.ts +81 -0
- package/src/hls.ts +1613 -0
- package/src/is-supported.ts +54 -0
- package/src/loader/date-range.ts +207 -0
- package/src/loader/fragment-loader.ts +403 -0
- package/src/loader/fragment.ts +487 -0
- package/src/loader/interstitial-asset-list.ts +162 -0
- package/src/loader/interstitial-event.ts +337 -0
- package/src/loader/key-loader.ts +439 -0
- package/src/loader/level-details.ts +203 -0
- package/src/loader/level-key.ts +259 -0
- package/src/loader/load-stats.ts +17 -0
- package/src/loader/m3u8-parser.ts +1072 -0
- package/src/loader/playlist-loader.ts +839 -0
- package/src/polyfills/number.ts +15 -0
- package/src/remux/aac-helper.ts +81 -0
- package/src/remux/mp4-generator.ts +1380 -0
- package/src/remux/mp4-remuxer.ts +1261 -0
- package/src/remux/passthrough-remuxer.ts +434 -0
- package/src/task-loop.ts +130 -0
- package/src/types/algo.ts +44 -0
- package/src/types/buffer.ts +105 -0
- package/src/types/component-api.ts +20 -0
- package/src/types/demuxer.ts +208 -0
- package/src/types/events.ts +574 -0
- package/src/types/fragment-tracker.ts +23 -0
- package/src/types/level.ts +268 -0
- package/src/types/loader.ts +198 -0
- package/src/types/media-playlist.ts +92 -0
- package/src/types/network-details.ts +3 -0
- package/src/types/remuxer.ts +104 -0
- package/src/types/track.ts +12 -0
- package/src/types/transmuxer.ts +46 -0
- package/src/types/tuples.ts +6 -0
- package/src/types/vtt.ts +11 -0
- package/src/utils/arrays.ts +22 -0
- package/src/utils/attr-list.ts +192 -0
- package/src/utils/binary-search.ts +46 -0
- package/src/utils/buffer-helper.ts +173 -0
- package/src/utils/cea-608-parser.ts +1413 -0
- package/src/utils/chunker.ts +41 -0
- package/src/utils/codecs.ts +314 -0
- package/src/utils/cues.ts +96 -0
- package/src/utils/discontinuities.ts +174 -0
- package/src/utils/encryption-methods-util.ts +21 -0
- package/src/utils/error-helper.ts +95 -0
- package/src/utils/event-listener-helper.ts +16 -0
- package/src/utils/ewma-bandwidth-estimator.ts +97 -0
- package/src/utils/ewma.ts +43 -0
- package/src/utils/fetch-loader.ts +331 -0
- package/src/utils/global.ts +2 -0
- package/src/utils/hash.ts +10 -0
- package/src/utils/hdr.ts +67 -0
- package/src/utils/hex.ts +32 -0
- package/src/utils/imsc1-ttml-parser.ts +261 -0
- package/src/utils/keysystem-util.ts +45 -0
- package/src/utils/level-helper.ts +629 -0
- package/src/utils/logger.ts +120 -0
- package/src/utils/media-option-attributes.ts +49 -0
- package/src/utils/mediacapabilities-helper.ts +301 -0
- package/src/utils/mediakeys-helper.ts +210 -0
- package/src/utils/mediasource-helper.ts +37 -0
- package/src/utils/mp4-tools.ts +1473 -0
- package/src/utils/number.ts +3 -0
- package/src/utils/numeric-encoding-utils.ts +26 -0
- package/src/utils/output-filter.ts +46 -0
- package/src/utils/rendition-helper.ts +505 -0
- package/src/utils/safe-json-stringify.ts +22 -0
- package/src/utils/texttrack-utils.ts +164 -0
- package/src/utils/time-ranges.ts +17 -0
- package/src/utils/timescale-conversion.ts +46 -0
- package/src/utils/utf8-utils.ts +18 -0
- package/src/utils/variable-substitution.ts +105 -0
- package/src/utils/vttcue.ts +384 -0
- package/src/utils/vttparser.ts +497 -0
- package/src/utils/webvtt-parser.ts +166 -0
- package/src/utils/xhr-loader.ts +337 -0
- package/src/version.ts +1 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
// PKCS7
|
|
2
|
+
export function removePadding(array: Uint8Array<ArrayBuffer>) {
|
|
3
|
+
const outputBytes = array.byteLength;
|
|
4
|
+
const paddingBytes =
|
|
5
|
+
outputBytes && new DataView(array.buffer).getUint8(outputBytes - 1);
|
|
6
|
+
if (paddingBytes) {
|
|
7
|
+
return array.slice(0, outputBytes - paddingBytes);
|
|
8
|
+
}
|
|
9
|
+
return array;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default class AESDecryptor {
|
|
13
|
+
private rcon: Array<number> = [
|
|
14
|
+
0x0, 0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36,
|
|
15
|
+
];
|
|
16
|
+
private subMix: Array<Uint32Array> = [
|
|
17
|
+
new Uint32Array(256),
|
|
18
|
+
new Uint32Array(256),
|
|
19
|
+
new Uint32Array(256),
|
|
20
|
+
new Uint32Array(256),
|
|
21
|
+
];
|
|
22
|
+
private invSubMix: Array<Uint32Array> = [
|
|
23
|
+
new Uint32Array(256),
|
|
24
|
+
new Uint32Array(256),
|
|
25
|
+
new Uint32Array(256),
|
|
26
|
+
new Uint32Array(256),
|
|
27
|
+
];
|
|
28
|
+
private sBox: Uint32Array = new Uint32Array(256);
|
|
29
|
+
private invSBox: Uint32Array = new Uint32Array(256);
|
|
30
|
+
private key: Uint32Array = new Uint32Array(0);
|
|
31
|
+
|
|
32
|
+
private ksRows: number = 0;
|
|
33
|
+
private keySize: number = 0;
|
|
34
|
+
private keySchedule!: Uint32Array;
|
|
35
|
+
private invKeySchedule!: Uint32Array;
|
|
36
|
+
|
|
37
|
+
constructor() {
|
|
38
|
+
this.initTable();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Using view.getUint32() also swaps the byte order.
|
|
42
|
+
uint8ArrayToUint32Array_(arrayBuffer) {
|
|
43
|
+
const view = new DataView(arrayBuffer);
|
|
44
|
+
const newArray = new Uint32Array(4);
|
|
45
|
+
for (let i = 0; i < 4; i++) {
|
|
46
|
+
newArray[i] = view.getUint32(i * 4);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return newArray;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
initTable() {
|
|
53
|
+
const sBox = this.sBox;
|
|
54
|
+
const invSBox = this.invSBox;
|
|
55
|
+
const subMix = this.subMix;
|
|
56
|
+
const subMix0 = subMix[0];
|
|
57
|
+
const subMix1 = subMix[1];
|
|
58
|
+
const subMix2 = subMix[2];
|
|
59
|
+
const subMix3 = subMix[3];
|
|
60
|
+
const invSubMix = this.invSubMix;
|
|
61
|
+
const invSubMix0 = invSubMix[0];
|
|
62
|
+
const invSubMix1 = invSubMix[1];
|
|
63
|
+
const invSubMix2 = invSubMix[2];
|
|
64
|
+
const invSubMix3 = invSubMix[3];
|
|
65
|
+
|
|
66
|
+
const d = new Uint32Array(256);
|
|
67
|
+
let x = 0;
|
|
68
|
+
let xi = 0;
|
|
69
|
+
let i = 0;
|
|
70
|
+
for (i = 0; i < 256; i++) {
|
|
71
|
+
if (i < 128) {
|
|
72
|
+
d[i] = i << 1;
|
|
73
|
+
} else {
|
|
74
|
+
d[i] = (i << 1) ^ 0x11b;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
for (i = 0; i < 256; i++) {
|
|
79
|
+
let sx = xi ^ (xi << 1) ^ (xi << 2) ^ (xi << 3) ^ (xi << 4);
|
|
80
|
+
sx = (sx >>> 8) ^ (sx & 0xff) ^ 0x63;
|
|
81
|
+
sBox[x] = sx;
|
|
82
|
+
invSBox[sx] = x;
|
|
83
|
+
|
|
84
|
+
// Compute multiplication
|
|
85
|
+
const x2 = d[x];
|
|
86
|
+
const x4 = d[x2];
|
|
87
|
+
const x8 = d[x4];
|
|
88
|
+
|
|
89
|
+
// Compute sub/invSub bytes, mix columns tables
|
|
90
|
+
let t = (d[sx] * 0x101) ^ (sx * 0x1010100);
|
|
91
|
+
subMix0[x] = (t << 24) | (t >>> 8);
|
|
92
|
+
subMix1[x] = (t << 16) | (t >>> 16);
|
|
93
|
+
subMix2[x] = (t << 8) | (t >>> 24);
|
|
94
|
+
subMix3[x] = t;
|
|
95
|
+
|
|
96
|
+
// Compute inv sub bytes, inv mix columns tables
|
|
97
|
+
t = (x8 * 0x1010101) ^ (x4 * 0x10001) ^ (x2 * 0x101) ^ (x * 0x1010100);
|
|
98
|
+
invSubMix0[sx] = (t << 24) | (t >>> 8);
|
|
99
|
+
invSubMix1[sx] = (t << 16) | (t >>> 16);
|
|
100
|
+
invSubMix2[sx] = (t << 8) | (t >>> 24);
|
|
101
|
+
invSubMix3[sx] = t;
|
|
102
|
+
|
|
103
|
+
// Compute next counter
|
|
104
|
+
if (!x) {
|
|
105
|
+
x = xi = 1;
|
|
106
|
+
} else {
|
|
107
|
+
x = x2 ^ d[d[d[x8 ^ x2]]];
|
|
108
|
+
xi ^= d[d[xi]];
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
expandKey(keyBuffer: ArrayBuffer) {
|
|
114
|
+
// convert keyBuffer to Uint32Array
|
|
115
|
+
const key = this.uint8ArrayToUint32Array_(keyBuffer);
|
|
116
|
+
let sameKey = true;
|
|
117
|
+
let offset = 0;
|
|
118
|
+
|
|
119
|
+
while (offset < key.length && sameKey) {
|
|
120
|
+
sameKey = key[offset] === this.key[offset];
|
|
121
|
+
offset++;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (sameKey) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
this.key = key;
|
|
129
|
+
const keySize = (this.keySize = key.length);
|
|
130
|
+
|
|
131
|
+
if (keySize !== 4 && keySize !== 6 && keySize !== 8) {
|
|
132
|
+
throw new Error('Invalid aes key size=' + keySize);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const ksRows = (this.ksRows = (keySize + 6 + 1) * 4);
|
|
136
|
+
let ksRow;
|
|
137
|
+
let invKsRow;
|
|
138
|
+
|
|
139
|
+
const keySchedule = (this.keySchedule = new Uint32Array(ksRows));
|
|
140
|
+
const invKeySchedule = (this.invKeySchedule = new Uint32Array(ksRows));
|
|
141
|
+
const sbox = this.sBox;
|
|
142
|
+
const rcon = this.rcon;
|
|
143
|
+
|
|
144
|
+
const invSubMix = this.invSubMix;
|
|
145
|
+
const invSubMix0 = invSubMix[0];
|
|
146
|
+
const invSubMix1 = invSubMix[1];
|
|
147
|
+
const invSubMix2 = invSubMix[2];
|
|
148
|
+
const invSubMix3 = invSubMix[3];
|
|
149
|
+
|
|
150
|
+
let prev;
|
|
151
|
+
let t;
|
|
152
|
+
|
|
153
|
+
for (ksRow = 0; ksRow < ksRows; ksRow++) {
|
|
154
|
+
if (ksRow < keySize) {
|
|
155
|
+
prev = keySchedule[ksRow] = key[ksRow];
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
t = prev;
|
|
159
|
+
|
|
160
|
+
if (ksRow % keySize === 0) {
|
|
161
|
+
// Rot word
|
|
162
|
+
t = (t << 8) | (t >>> 24);
|
|
163
|
+
|
|
164
|
+
// Sub word
|
|
165
|
+
t =
|
|
166
|
+
(sbox[t >>> 24] << 24) |
|
|
167
|
+
(sbox[(t >>> 16) & 0xff] << 16) |
|
|
168
|
+
(sbox[(t >>> 8) & 0xff] << 8) |
|
|
169
|
+
sbox[t & 0xff];
|
|
170
|
+
|
|
171
|
+
// Mix Rcon
|
|
172
|
+
t ^= rcon[(ksRow / keySize) | 0] << 24;
|
|
173
|
+
} else if (keySize > 6 && ksRow % keySize === 4) {
|
|
174
|
+
// Sub word
|
|
175
|
+
t =
|
|
176
|
+
(sbox[t >>> 24] << 24) |
|
|
177
|
+
(sbox[(t >>> 16) & 0xff] << 16) |
|
|
178
|
+
(sbox[(t >>> 8) & 0xff] << 8) |
|
|
179
|
+
sbox[t & 0xff];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
keySchedule[ksRow] = prev = (keySchedule[ksRow - keySize] ^ t) >>> 0;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
for (invKsRow = 0; invKsRow < ksRows; invKsRow++) {
|
|
186
|
+
ksRow = ksRows - invKsRow;
|
|
187
|
+
if (invKsRow & 3) {
|
|
188
|
+
t = keySchedule[ksRow];
|
|
189
|
+
} else {
|
|
190
|
+
t = keySchedule[ksRow - 4];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (invKsRow < 4 || ksRow <= 4) {
|
|
194
|
+
invKeySchedule[invKsRow] = t;
|
|
195
|
+
} else {
|
|
196
|
+
invKeySchedule[invKsRow] =
|
|
197
|
+
invSubMix0[sbox[t >>> 24]] ^
|
|
198
|
+
invSubMix1[sbox[(t >>> 16) & 0xff]] ^
|
|
199
|
+
invSubMix2[sbox[(t >>> 8) & 0xff]] ^
|
|
200
|
+
invSubMix3[sbox[t & 0xff]];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
invKeySchedule[invKsRow] = invKeySchedule[invKsRow] >>> 0;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Adding this as a method greatly improves performance.
|
|
208
|
+
networkToHostOrderSwap(word) {
|
|
209
|
+
return (
|
|
210
|
+
(word << 24) |
|
|
211
|
+
((word & 0xff00) << 8) |
|
|
212
|
+
((word & 0xff0000) >> 8) |
|
|
213
|
+
(word >>> 24)
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
decrypt(
|
|
218
|
+
inputArrayBuffer: ArrayBufferLike,
|
|
219
|
+
offset: number,
|
|
220
|
+
aesIV: ArrayBuffer,
|
|
221
|
+
) {
|
|
222
|
+
const nRounds = this.keySize + 6;
|
|
223
|
+
const invKeySchedule = this.invKeySchedule;
|
|
224
|
+
const invSBOX = this.invSBox;
|
|
225
|
+
|
|
226
|
+
const invSubMix = this.invSubMix;
|
|
227
|
+
const invSubMix0 = invSubMix[0];
|
|
228
|
+
const invSubMix1 = invSubMix[1];
|
|
229
|
+
const invSubMix2 = invSubMix[2];
|
|
230
|
+
const invSubMix3 = invSubMix[3];
|
|
231
|
+
|
|
232
|
+
const initVector = this.uint8ArrayToUint32Array_(aesIV);
|
|
233
|
+
let initVector0 = initVector[0];
|
|
234
|
+
let initVector1 = initVector[1];
|
|
235
|
+
let initVector2 = initVector[2];
|
|
236
|
+
let initVector3 = initVector[3];
|
|
237
|
+
|
|
238
|
+
const inputInt32 = new Int32Array(inputArrayBuffer);
|
|
239
|
+
const outputInt32 = new Int32Array(inputInt32.length);
|
|
240
|
+
|
|
241
|
+
let t0, t1, t2, t3;
|
|
242
|
+
let s0, s1, s2, s3;
|
|
243
|
+
let inputWords0, inputWords1, inputWords2, inputWords3;
|
|
244
|
+
|
|
245
|
+
let ksRow, i;
|
|
246
|
+
const swapWord = this.networkToHostOrderSwap;
|
|
247
|
+
|
|
248
|
+
while (offset < inputInt32.length) {
|
|
249
|
+
inputWords0 = swapWord(inputInt32[offset]);
|
|
250
|
+
inputWords1 = swapWord(inputInt32[offset + 1]);
|
|
251
|
+
inputWords2 = swapWord(inputInt32[offset + 2]);
|
|
252
|
+
inputWords3 = swapWord(inputInt32[offset + 3]);
|
|
253
|
+
|
|
254
|
+
s0 = inputWords0 ^ invKeySchedule[0];
|
|
255
|
+
s1 = inputWords3 ^ invKeySchedule[1];
|
|
256
|
+
s2 = inputWords2 ^ invKeySchedule[2];
|
|
257
|
+
s3 = inputWords1 ^ invKeySchedule[3];
|
|
258
|
+
|
|
259
|
+
ksRow = 4;
|
|
260
|
+
|
|
261
|
+
// Iterate through the rounds of decryption
|
|
262
|
+
for (i = 1; i < nRounds; i++) {
|
|
263
|
+
t0 =
|
|
264
|
+
invSubMix0[s0 >>> 24] ^
|
|
265
|
+
invSubMix1[(s1 >> 16) & 0xff] ^
|
|
266
|
+
invSubMix2[(s2 >> 8) & 0xff] ^
|
|
267
|
+
invSubMix3[s3 & 0xff] ^
|
|
268
|
+
invKeySchedule[ksRow];
|
|
269
|
+
t1 =
|
|
270
|
+
invSubMix0[s1 >>> 24] ^
|
|
271
|
+
invSubMix1[(s2 >> 16) & 0xff] ^
|
|
272
|
+
invSubMix2[(s3 >> 8) & 0xff] ^
|
|
273
|
+
invSubMix3[s0 & 0xff] ^
|
|
274
|
+
invKeySchedule[ksRow + 1];
|
|
275
|
+
t2 =
|
|
276
|
+
invSubMix0[s2 >>> 24] ^
|
|
277
|
+
invSubMix1[(s3 >> 16) & 0xff] ^
|
|
278
|
+
invSubMix2[(s0 >> 8) & 0xff] ^
|
|
279
|
+
invSubMix3[s1 & 0xff] ^
|
|
280
|
+
invKeySchedule[ksRow + 2];
|
|
281
|
+
t3 =
|
|
282
|
+
invSubMix0[s3 >>> 24] ^
|
|
283
|
+
invSubMix1[(s0 >> 16) & 0xff] ^
|
|
284
|
+
invSubMix2[(s1 >> 8) & 0xff] ^
|
|
285
|
+
invSubMix3[s2 & 0xff] ^
|
|
286
|
+
invKeySchedule[ksRow + 3];
|
|
287
|
+
// Update state
|
|
288
|
+
s0 = t0;
|
|
289
|
+
s1 = t1;
|
|
290
|
+
s2 = t2;
|
|
291
|
+
s3 = t3;
|
|
292
|
+
|
|
293
|
+
ksRow = ksRow + 4;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Shift rows, sub bytes, add round key
|
|
297
|
+
t0 =
|
|
298
|
+
(invSBOX[s0 >>> 24] << 24) ^
|
|
299
|
+
(invSBOX[(s1 >> 16) & 0xff] << 16) ^
|
|
300
|
+
(invSBOX[(s2 >> 8) & 0xff] << 8) ^
|
|
301
|
+
invSBOX[s3 & 0xff] ^
|
|
302
|
+
invKeySchedule[ksRow];
|
|
303
|
+
t1 =
|
|
304
|
+
(invSBOX[s1 >>> 24] << 24) ^
|
|
305
|
+
(invSBOX[(s2 >> 16) & 0xff] << 16) ^
|
|
306
|
+
(invSBOX[(s3 >> 8) & 0xff] << 8) ^
|
|
307
|
+
invSBOX[s0 & 0xff] ^
|
|
308
|
+
invKeySchedule[ksRow + 1];
|
|
309
|
+
t2 =
|
|
310
|
+
(invSBOX[s2 >>> 24] << 24) ^
|
|
311
|
+
(invSBOX[(s3 >> 16) & 0xff] << 16) ^
|
|
312
|
+
(invSBOX[(s0 >> 8) & 0xff] << 8) ^
|
|
313
|
+
invSBOX[s1 & 0xff] ^
|
|
314
|
+
invKeySchedule[ksRow + 2];
|
|
315
|
+
t3 =
|
|
316
|
+
(invSBOX[s3 >>> 24] << 24) ^
|
|
317
|
+
(invSBOX[(s0 >> 16) & 0xff] << 16) ^
|
|
318
|
+
(invSBOX[(s1 >> 8) & 0xff] << 8) ^
|
|
319
|
+
invSBOX[s2 & 0xff] ^
|
|
320
|
+
invKeySchedule[ksRow + 3];
|
|
321
|
+
|
|
322
|
+
// Write
|
|
323
|
+
outputInt32[offset] = swapWord(t0 ^ initVector0);
|
|
324
|
+
outputInt32[offset + 1] = swapWord(t3 ^ initVector1);
|
|
325
|
+
outputInt32[offset + 2] = swapWord(t2 ^ initVector2);
|
|
326
|
+
outputInt32[offset + 3] = swapWord(t1 ^ initVector3);
|
|
327
|
+
|
|
328
|
+
// reset initVector to last 4 unsigned int
|
|
329
|
+
initVector0 = inputWords0;
|
|
330
|
+
initVector1 = inputWords1;
|
|
331
|
+
initVector2 = inputWords2;
|
|
332
|
+
initVector3 = inputWords3;
|
|
333
|
+
|
|
334
|
+
offset = offset + 4;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return outputInt32.buffer;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import AESCrypto from './aes-crypto';
|
|
2
|
+
import AESDecryptor, { removePadding } from './aes-decryptor';
|
|
3
|
+
import { DecrypterAesMode } from './decrypter-aes-mode';
|
|
4
|
+
import FastAESKey from './fast-aes-key';
|
|
5
|
+
import { logger } from '../utils/logger';
|
|
6
|
+
import { appendUint8Array } from '../utils/mp4-tools';
|
|
7
|
+
import type { HlsConfig } from '../config';
|
|
8
|
+
|
|
9
|
+
const CHUNK_SIZE = 16; // 16 bytes, 128 bits
|
|
10
|
+
|
|
11
|
+
export default class Decrypter {
|
|
12
|
+
private logEnabled: boolean = true;
|
|
13
|
+
private removePKCS7Padding: boolean;
|
|
14
|
+
private subtle: SubtleCrypto | null = null;
|
|
15
|
+
private softwareDecrypter: AESDecryptor | null = null;
|
|
16
|
+
private key: ArrayBuffer | null = null;
|
|
17
|
+
private fastAesKey: FastAESKey | null = null;
|
|
18
|
+
private remainderData: Uint8Array<ArrayBuffer> | null = null;
|
|
19
|
+
private currentIV: ArrayBuffer | null = null;
|
|
20
|
+
private currentResult: ArrayBuffer | null = null;
|
|
21
|
+
private useSoftware: boolean;
|
|
22
|
+
private enableSoftwareAES: boolean;
|
|
23
|
+
|
|
24
|
+
constructor(config: HlsConfig, { removePKCS7Padding = true } = {}) {
|
|
25
|
+
this.enableSoftwareAES = config.enableSoftwareAES;
|
|
26
|
+
this.removePKCS7Padding = removePKCS7Padding;
|
|
27
|
+
// built in decryptor expects PKCS7 padding
|
|
28
|
+
if (removePKCS7Padding) {
|
|
29
|
+
try {
|
|
30
|
+
const browserCrypto = self.crypto;
|
|
31
|
+
if (browserCrypto) {
|
|
32
|
+
this.subtle =
|
|
33
|
+
browserCrypto.subtle ||
|
|
34
|
+
((browserCrypto as any).webkitSubtle as SubtleCrypto);
|
|
35
|
+
}
|
|
36
|
+
} catch (e) {
|
|
37
|
+
/* no-op */
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
this.useSoftware = !this.subtle;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
destroy() {
|
|
44
|
+
this.subtle = null;
|
|
45
|
+
this.softwareDecrypter = null;
|
|
46
|
+
this.key = null;
|
|
47
|
+
this.fastAesKey = null;
|
|
48
|
+
this.remainderData = null;
|
|
49
|
+
this.currentIV = null;
|
|
50
|
+
this.currentResult = null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public isSync() {
|
|
54
|
+
return this.useSoftware;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public flush(): Uint8Array<ArrayBuffer> | null {
|
|
58
|
+
const { currentResult, remainderData } = this;
|
|
59
|
+
if (!currentResult || remainderData) {
|
|
60
|
+
this.reset();
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
const data = new Uint8Array(currentResult);
|
|
64
|
+
this.reset();
|
|
65
|
+
if (this.removePKCS7Padding) {
|
|
66
|
+
return removePadding(data);
|
|
67
|
+
}
|
|
68
|
+
return data;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public reset() {
|
|
72
|
+
this.currentResult = null;
|
|
73
|
+
this.currentIV = null;
|
|
74
|
+
this.remainderData = null;
|
|
75
|
+
if (this.softwareDecrypter) {
|
|
76
|
+
this.softwareDecrypter = null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public decrypt(
|
|
81
|
+
data: Uint8Array | ArrayBuffer,
|
|
82
|
+
key: ArrayBuffer,
|
|
83
|
+
iv: ArrayBuffer,
|
|
84
|
+
aesMode: DecrypterAesMode,
|
|
85
|
+
): Promise<ArrayBuffer> {
|
|
86
|
+
if (this.useSoftware) {
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
88
|
+
const dataView = ArrayBuffer.isView(data) ? data : new Uint8Array(data);
|
|
89
|
+
this.softwareDecrypt(dataView, key, iv, aesMode);
|
|
90
|
+
const decryptResult = this.flush();
|
|
91
|
+
if (decryptResult) {
|
|
92
|
+
resolve(decryptResult.buffer);
|
|
93
|
+
} else {
|
|
94
|
+
reject(new Error('[softwareDecrypt] Failed to decrypt data'));
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return this.webCryptoDecrypt(new Uint8Array(data), key, iv, aesMode);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
|
102
|
+
// data is handled in the flush() call
|
|
103
|
+
public softwareDecrypt(
|
|
104
|
+
data: Uint8Array,
|
|
105
|
+
key: ArrayBuffer,
|
|
106
|
+
iv: ArrayBuffer,
|
|
107
|
+
aesMode: DecrypterAesMode,
|
|
108
|
+
): ArrayBuffer | null {
|
|
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
|
+
}
|
|
114
|
+
this.logOnce('JS AES decrypt');
|
|
115
|
+
// The output is staggered during progressive parsing - the current result is cached, and emitted on the next call
|
|
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
|
|
117
|
+
// the end on flush(), but by that time we have already received all bytes for the segment.
|
|
118
|
+
// Progressive decryption does not work with WebCrypto
|
|
119
|
+
|
|
120
|
+
if (remainderData) {
|
|
121
|
+
data = appendUint8Array(remainderData, data);
|
|
122
|
+
this.remainderData = null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Byte length must be a multiple of 16 (AES-128 = 128 bit blocks = 16 bytes)
|
|
126
|
+
const currentChunk = this.getValidChunk(data);
|
|
127
|
+
if (!currentChunk.length) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (currentIV) {
|
|
132
|
+
iv = currentIV;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
let softwareDecrypter = this.softwareDecrypter;
|
|
136
|
+
if (!softwareDecrypter) {
|
|
137
|
+
softwareDecrypter = this.softwareDecrypter = new AESDecryptor();
|
|
138
|
+
}
|
|
139
|
+
softwareDecrypter.expandKey(key);
|
|
140
|
+
|
|
141
|
+
const result = currentResult;
|
|
142
|
+
|
|
143
|
+
this.currentResult = softwareDecrypter.decrypt(currentChunk.buffer, 0, iv);
|
|
144
|
+
this.currentIV = currentChunk.slice(-16).buffer;
|
|
145
|
+
|
|
146
|
+
if (!result) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
public webCryptoDecrypt(
|
|
153
|
+
data: Uint8Array<ArrayBuffer>,
|
|
154
|
+
key: ArrayBuffer,
|
|
155
|
+
iv: ArrayBuffer,
|
|
156
|
+
aesMode: DecrypterAesMode,
|
|
157
|
+
): Promise<ArrayBuffer> {
|
|
158
|
+
if (this.key !== key || !this.fastAesKey) {
|
|
159
|
+
if (!this.subtle) {
|
|
160
|
+
return Promise.resolve(this.onWebCryptoError(data, key, iv, aesMode));
|
|
161
|
+
}
|
|
162
|
+
this.key = key;
|
|
163
|
+
this.fastAesKey = new FastAESKey(this.subtle, key, aesMode);
|
|
164
|
+
}
|
|
165
|
+
return this.fastAesKey
|
|
166
|
+
.expandKey()
|
|
167
|
+
.then((aesKey: CryptoKey) => {
|
|
168
|
+
// decrypt using web crypto
|
|
169
|
+
if (!this.subtle) {
|
|
170
|
+
return Promise.reject(new Error('web crypto not initialized'));
|
|
171
|
+
}
|
|
172
|
+
this.logOnce('WebCrypto AES decrypt');
|
|
173
|
+
const crypto = new AESCrypto(this.subtle, new Uint8Array(iv), aesMode);
|
|
174
|
+
return crypto.decrypt(data.buffer, aesKey);
|
|
175
|
+
})
|
|
176
|
+
.catch((err) => {
|
|
177
|
+
logger.warn(
|
|
178
|
+
`[decrypter]: WebCrypto Error, disable WebCrypto API, ${err.name}: ${err.message}`,
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
return this.onWebCryptoError(data, key, iv, aesMode);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private onWebCryptoError(
|
|
186
|
+
data: Uint8Array,
|
|
187
|
+
key: ArrayBuffer,
|
|
188
|
+
iv: ArrayBuffer,
|
|
189
|
+
aesMode: DecrypterAesMode,
|
|
190
|
+
): ArrayBuffer | never {
|
|
191
|
+
const enableSoftwareAES = this.enableSoftwareAES;
|
|
192
|
+
if (enableSoftwareAES) {
|
|
193
|
+
this.useSoftware = true;
|
|
194
|
+
this.logEnabled = true;
|
|
195
|
+
this.softwareDecrypt(data, key, iv, aesMode);
|
|
196
|
+
const decryptResult = this.flush();
|
|
197
|
+
if (decryptResult) {
|
|
198
|
+
return decryptResult.buffer;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
throw new Error(
|
|
202
|
+
'WebCrypto' +
|
|
203
|
+
(enableSoftwareAES ? ' and softwareDecrypt' : '') +
|
|
204
|
+
': failed to decrypt data',
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private getValidChunk(data: Uint8Array): Uint8Array {
|
|
209
|
+
let currentChunk = data;
|
|
210
|
+
const splitPoint = data.length - (data.length % CHUNK_SIZE);
|
|
211
|
+
if (splitPoint !== data.length) {
|
|
212
|
+
currentChunk = data.slice(0, splitPoint);
|
|
213
|
+
this.remainderData = data.slice(splitPoint);
|
|
214
|
+
}
|
|
215
|
+
return currentChunk;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private logOnce(msg: string) {
|
|
219
|
+
if (!this.logEnabled) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
logger.log(`[decrypter]: ${msg}`);
|
|
223
|
+
this.logEnabled = false;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { DecrypterAesMode } from './decrypter-aes-mode';
|
|
2
|
+
|
|
3
|
+
export default class FastAESKey {
|
|
4
|
+
private subtle: SubtleCrypto;
|
|
5
|
+
private key: ArrayBuffer;
|
|
6
|
+
private aesMode: DecrypterAesMode;
|
|
7
|
+
|
|
8
|
+
constructor(
|
|
9
|
+
subtle: SubtleCrypto,
|
|
10
|
+
key: ArrayBuffer,
|
|
11
|
+
aesMode: DecrypterAesMode,
|
|
12
|
+
) {
|
|
13
|
+
this.subtle = subtle;
|
|
14
|
+
this.key = key;
|
|
15
|
+
this.aesMode = aesMode;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
expandKey() {
|
|
19
|
+
const subtleAlgoName = getSubtleAlgoName(this.aesMode);
|
|
20
|
+
return this.subtle.importKey(
|
|
21
|
+
'raw',
|
|
22
|
+
this.key,
|
|
23
|
+
{ name: subtleAlgoName },
|
|
24
|
+
false,
|
|
25
|
+
['encrypt', 'decrypt'],
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getSubtleAlgoName(aesMode: DecrypterAesMode) {
|
|
31
|
+
switch (aesMode) {
|
|
32
|
+
case DecrypterAesMode.cbc:
|
|
33
|
+
return 'AES-CBC';
|
|
34
|
+
case DecrypterAesMode.ctr:
|
|
35
|
+
return 'AES-CTR';
|
|
36
|
+
default:
|
|
37
|
+
throw new Error(`[FastAESKey] invalid aes mode ${aesMode}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
declare const __VERSION__: string;
|
|
2
|
+
|
|
3
|
+
// Dynamic Modules
|
|
4
|
+
declare const __USE_ALT_AUDIO__: boolean;
|
|
5
|
+
declare const __USE_EME_DRM__: boolean;
|
|
6
|
+
declare const __USE_SUBTITLES__: boolean;
|
|
7
|
+
declare const __USE_CMCD__: boolean;
|
|
8
|
+
declare const __USE_CONTENT_STEERING__: boolean;
|
|
9
|
+
declare const __USE_VARIABLE_SUBSTITUTION__: boolean;
|
|
10
|
+
declare const __USE_M2TS_ADVANCED_CODECS__: boolean;
|
|
11
|
+
declare const __USE_MEDIA_CAPABILITIES__: boolean;
|
|
12
|
+
declare const __USE_INTERSTITIALS__: boolean;
|
|
13
|
+
|
|
14
|
+
// __IN_WORKER__ is provided from a closure call around the final UMD bundle.
|
|
15
|
+
declare const __IN_WORKER__: boolean;
|
|
16
|
+
// __HLS_WORKER_BUNDLE__ is the name of the closure around the final UMD bundle.
|
|
17
|
+
declare const __HLS_WORKER_BUNDLE__: Function;
|