mediabunny 1.0.2 → 1.0.4
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 +4 -4
- package/dist/mediabunny.d.ts +4 -2
- package/dist/modules/codec-data.d.ts +95 -0
- package/dist/modules/codec-data.d.ts.map +1 -0
- package/dist/modules/codec-data.js +1103 -0
- package/dist/modules/codec.d.ts +250 -0
- package/dist/modules/codec.d.ts.map +1 -0
- package/dist/modules/codec.js +1100 -0
- package/dist/modules/conversion.d.ts +158 -0
- package/dist/modules/conversion.d.ts.map +1 -0
- package/dist/modules/conversion.js +866 -0
- package/dist/modules/custom-coder.d.ts +119 -0
- package/dist/modules/custom-coder.d.ts.map +1 -0
- package/dist/modules/custom-coder.js +91 -0
- package/dist/modules/demuxer.d.ts +17 -0
- package/dist/modules/demuxer.d.ts.map +1 -0
- package/dist/modules/demuxer.js +12 -0
- package/dist/modules/index.d.ts +25 -0
- package/dist/modules/index.d.ts.map +1 -0
- package/dist/modules/index.js +24 -0
- package/dist/modules/input-format.d.ts +121 -0
- package/dist/modules/input-format.d.ts.map +1 -0
- package/dist/modules/input-format.js +333 -0
- package/dist/modules/input-track.d.ts +138 -0
- package/dist/modules/input-track.d.ts.map +1 -0
- package/dist/modules/input-track.js +230 -0
- package/dist/modules/input.d.ts +55 -0
- package/dist/modules/input.d.ts.map +1 -0
- package/dist/modules/input.js +104 -0
- package/dist/modules/isobmff/isobmff-boxes.d.ts +202 -0
- package/dist/modules/isobmff/isobmff-boxes.d.ts.map +1 -0
- package/dist/modules/isobmff/isobmff-boxes.js +1111 -0
- package/dist/modules/isobmff/isobmff-demuxer.d.ts +163 -0
- package/dist/modules/isobmff/isobmff-demuxer.d.ts.map +1 -0
- package/dist/modules/isobmff/isobmff-demuxer.js +2080 -0
- package/dist/modules/isobmff/isobmff-misc.d.ts +14 -0
- package/dist/modules/isobmff/isobmff-misc.d.ts.map +1 -0
- package/dist/modules/isobmff/isobmff-misc.js +20 -0
- package/dist/modules/isobmff/isobmff-muxer.d.ts +142 -0
- package/dist/modules/isobmff/isobmff-muxer.d.ts.map +1 -0
- package/dist/modules/isobmff/isobmff-muxer.js +858 -0
- package/dist/modules/isobmff/isobmff-reader.d.ts +36 -0
- package/dist/modules/isobmff/isobmff-reader.d.ts.map +1 -0
- package/dist/modules/isobmff/isobmff-reader.js +105 -0
- package/dist/modules/matroska/ebml.d.ts +156 -0
- package/dist/modules/matroska/ebml.d.ts.map +1 -0
- package/dist/modules/matroska/ebml.js +529 -0
- package/dist/modules/matroska/matroska-demuxer.d.ts +119 -0
- package/dist/modules/matroska/matroska-demuxer.d.ts.map +1 -0
- package/dist/modules/matroska/matroska-demuxer.js +1330 -0
- package/dist/modules/matroska/matroska-misc.d.ts +14 -0
- package/dist/modules/matroska/matroska-misc.d.ts.map +1 -0
- package/dist/modules/matroska/matroska-misc.js +20 -0
- package/dist/modules/matroska/matroska-muxer.d.ts +71 -0
- package/dist/modules/matroska/matroska-muxer.d.ts.map +1 -0
- package/dist/modules/matroska/matroska-muxer.js +729 -0
- package/dist/modules/media-sink.d.ts +272 -0
- package/dist/modules/media-sink.d.ts.map +1 -0
- package/dist/modules/media-sink.js +1264 -0
- package/dist/modules/media-source.d.ts +230 -0
- package/dist/modules/media-source.d.ts.map +1 -0
- package/dist/modules/media-source.js +1095 -0
- package/dist/modules/misc.d.ts +127 -0
- package/dist/modules/misc.d.ts.map +1 -0
- package/dist/modules/misc.js +462 -0
- package/dist/modules/mp3/mp3-demuxer.d.ts +32 -0
- package/dist/modules/mp3/mp3-demuxer.d.ts.map +1 -0
- package/dist/modules/mp3/mp3-demuxer.js +160 -0
- package/dist/modules/mp3/mp3-misc.d.ts +37 -0
- package/dist/modules/mp3/mp3-misc.d.ts.map +1 -0
- package/dist/modules/mp3/mp3-misc.js +122 -0
- package/dist/modules/mp3/mp3-muxer.d.ts +27 -0
- package/dist/modules/mp3/mp3-muxer.d.ts.map +1 -0
- package/dist/modules/mp3/mp3-muxer.js +112 -0
- package/dist/modules/mp3/mp3-reader.d.ts +25 -0
- package/dist/modules/mp3/mp3-reader.d.ts.map +1 -0
- package/dist/modules/mp3/mp3-reader.js +73 -0
- package/dist/modules/mp3/mp3-writer.d.ts +30 -0
- package/dist/modules/mp3/mp3-writer.d.ts.map +1 -0
- package/dist/modules/mp3/mp3-writer.js +72 -0
- package/dist/modules/muxer.d.ts +32 -0
- package/dist/modules/muxer.d.ts.map +1 -0
- package/dist/modules/muxer.js +50 -0
- package/dist/modules/ogg/ogg-demuxer.d.ts +53 -0
- package/dist/modules/ogg/ogg-demuxer.d.ts.map +1 -0
- package/dist/modules/ogg/ogg-demuxer.js +701 -0
- package/dist/modules/ogg/ogg-misc.d.ts +27 -0
- package/dist/modules/ogg/ogg-misc.d.ts.map +1 -0
- package/dist/modules/ogg/ogg-misc.js +78 -0
- package/dist/modules/ogg/ogg-muxer.d.ts +58 -0
- package/dist/modules/ogg/ogg-muxer.d.ts.map +1 -0
- package/dist/modules/ogg/ogg-muxer.js +345 -0
- package/dist/modules/ogg/ogg-reader.d.ts +37 -0
- package/dist/modules/ogg/ogg-reader.d.ts.map +1 -0
- package/dist/modules/ogg/ogg-reader.js +104 -0
- package/dist/modules/output-format.d.ts +285 -0
- package/dist/modules/output-format.d.ts.map +1 -0
- package/dist/modules/output-format.js +385 -0
- package/dist/modules/output.d.ts +134 -0
- package/dist/modules/output.d.ts.map +1 -0
- package/dist/modules/output.js +269 -0
- package/dist/modules/packet.d.ts +86 -0
- package/dist/modules/packet.d.ts.map +1 -0
- package/dist/modules/packet.js +133 -0
- package/dist/modules/pcm.d.ts +12 -0
- package/dist/modules/pcm.d.ts.map +1 -0
- package/dist/modules/pcm.js +85 -0
- package/dist/modules/reader.d.ts +40 -0
- package/dist/modules/reader.d.ts.map +1 -0
- package/dist/modules/reader.js +153 -0
- package/dist/modules/sample.d.ts +186 -0
- package/dist/modules/sample.d.ts.map +1 -0
- package/dist/modules/sample.js +798 -0
- package/dist/modules/source.d.ts +76 -0
- package/dist/modules/source.d.ts.map +1 -0
- package/dist/modules/source.js +183 -0
- package/dist/modules/subtitles.d.ts +37 -0
- package/dist/modules/subtitles.d.ts.map +1 -0
- package/dist/modules/subtitles.js +89 -0
- package/dist/modules/target.d.ts +58 -0
- package/dist/modules/target.d.ts.map +1 -0
- package/dist/modules/target.js +63 -0
- package/dist/modules/wave/riff-reader.d.ts +19 -0
- package/dist/modules/wave/riff-reader.d.ts.map +1 -0
- package/dist/modules/wave/riff-reader.js +38 -0
- package/dist/modules/wave/riff-writer.d.ts +18 -0
- package/dist/modules/wave/riff-writer.d.ts.map +1 -0
- package/dist/modules/wave/riff-writer.js +25 -0
- package/dist/modules/wave/wave-demuxer.d.ts +42 -0
- package/dist/modules/wave/wave-demuxer.d.ts.map +1 -0
- package/dist/modules/wave/wave-demuxer.js +239 -0
- package/dist/modules/wave/wave-muxer.d.ts +27 -0
- package/dist/modules/wave/wave-muxer.d.ts.map +1 -0
- package/dist/modules/wave/wave-muxer.js +108 -0
- package/dist/modules/writer.d.ts +85 -0
- package/dist/modules/writer.d.ts.map +1 -0
- package/dist/modules/writer.js +347 -0
- package/package.json +14 -10
- package/src/codec-data.ts +1394 -0
- package/src/codec.ts +1392 -0
- package/src/conversion.ts +1199 -0
- package/src/custom-coder.ts +162 -0
- package/src/demuxer.ts +22 -0
- package/src/index.ts +148 -0
- package/src/input-format.ts +386 -0
- package/src/input-track.ts +341 -0
- package/src/input.ts +138 -0
- package/src/isobmff/isobmff-boxes.ts +1365 -0
- package/src/isobmff/isobmff-demuxer.ts +2646 -0
- package/src/isobmff/isobmff-misc.ts +29 -0
- package/src/isobmff/isobmff-muxer.ts +1210 -0
- package/src/isobmff/isobmff-reader.ts +137 -0
- package/src/matroska/ebml.ts +595 -0
- package/src/matroska/matroska-demuxer.ts +1677 -0
- package/src/matroska/matroska-misc.ts +29 -0
- package/src/matroska/matroska-muxer.ts +999 -0
- package/src/media-sink.ts +1577 -0
- package/src/media-source.ts +1406 -0
- package/src/misc.ts +584 -0
- package/src/mp3/mp3-demuxer.ts +228 -0
- package/src/mp3/mp3-misc.ts +154 -0
- package/src/mp3/mp3-muxer.ts +153 -0
- package/src/mp3/mp3-reader.ts +93 -0
- package/src/mp3/mp3-writer.ts +112 -0
- package/src/muxer.ts +88 -0
- package/src/ogg/ogg-demuxer.ts +985 -0
- package/src/ogg/ogg-misc.ts +116 -0
- package/src/ogg/ogg-muxer.ts +480 -0
- package/src/ogg/ogg-reader.ts +143 -0
- package/src/output-format.ts +689 -0
- package/src/output.ts +427 -0
- package/src/packet.ts +180 -0
- package/src/pcm.ts +112 -0
- package/src/reader.ts +205 -0
- package/src/sample.ts +1029 -0
- package/src/source.ts +279 -0
- package/src/subtitles.ts +131 -0
- package/src/target.ts +106 -0
- package/src/tsconfig.json +13 -0
- package/src/wave/riff-reader.ts +48 -0
- package/src/wave/riff-writer.ts +30 -0
- package/src/wave/wave-demuxer.ts +317 -0
- package/src/wave/wave-muxer.ts +145 -0
- package/src/writer.ts +461 -0
- /package/dist/{mediabunny.js → bundles/mediabunny.cjs} +0 -0
- /package/dist/{mediabunny.min.js → bundles/mediabunny.min.cjs} +0 -0
- /package/dist/{mediabunny.min.mjs → bundles/mediabunny.min.mjs} +0 -0
- /package/dist/{mediabunny.mjs → bundles/mediabunny.mjs} +0 -0
package/src/misc.ts
ADDED
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2025-present, Vanilagy and contributors
|
|
3
|
+
*
|
|
4
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export function assert(x: unknown): asserts x {
|
|
10
|
+
if (!x) {
|
|
11
|
+
throw new Error('Assertion failed.');
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Represents a clockwise rotation in degrees.
|
|
17
|
+
* @public
|
|
18
|
+
*/
|
|
19
|
+
export type Rotation = 0 | 90 | 180 | 270;
|
|
20
|
+
|
|
21
|
+
export const normalizeRotation = (rotation: number) => {
|
|
22
|
+
const mappedRotation = (rotation % 360 + 360) % 360;
|
|
23
|
+
|
|
24
|
+
if (mappedRotation === 0 || mappedRotation === 90 || mappedRotation === 180 || mappedRotation === 270) {
|
|
25
|
+
return mappedRotation as Rotation;
|
|
26
|
+
} else {
|
|
27
|
+
throw new Error(`Invalid rotation ${rotation}.`);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type TransformationMatrix = [number, number, number, number, number, number, number, number, number];
|
|
32
|
+
|
|
33
|
+
export const last = <T>(arr: T[]) => {
|
|
34
|
+
return arr && arr[arr.length - 1];
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const isU32 = (value: number) => {
|
|
38
|
+
return value >= 0 && value < 2 ** 32;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export class Bitstream {
|
|
42
|
+
/** Current offset in bits. */
|
|
43
|
+
pos = 0;
|
|
44
|
+
|
|
45
|
+
constructor(public bytes: Uint8Array) {}
|
|
46
|
+
|
|
47
|
+
seekToByte(byteOffset: number) {
|
|
48
|
+
this.pos = 8 * byteOffset;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
readBit() {
|
|
52
|
+
const byteIndex = Math.floor(this.pos / 8);
|
|
53
|
+
const byte = this.bytes[byteIndex] ?? 0;
|
|
54
|
+
const bitIndex = 0b111 - (this.pos & 0b111);
|
|
55
|
+
const bit = (byte & (1 << bitIndex)) >> bitIndex;
|
|
56
|
+
|
|
57
|
+
this.pos++;
|
|
58
|
+
return bit;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
readBits(n: number) {
|
|
62
|
+
let result = 0;
|
|
63
|
+
|
|
64
|
+
for (let i = 0; i < n; i++) {
|
|
65
|
+
result <<= 1;
|
|
66
|
+
result |= this.readBit();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
readAlignedByte() {
|
|
73
|
+
// Ensure we're byte-aligned
|
|
74
|
+
if (this.pos % 8 !== 0) {
|
|
75
|
+
throw new Error('Bitstream is not byte-aligned.');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const byteIndex = this.pos / 8;
|
|
79
|
+
const byte = this.bytes[byteIndex] ?? 0;
|
|
80
|
+
|
|
81
|
+
this.pos += 8;
|
|
82
|
+
return byte;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
skipBits(n: number) {
|
|
86
|
+
this.pos += n;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
getBitsLeft() {
|
|
90
|
+
return this.bytes.length * 8 - this.pos;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
clone() {
|
|
94
|
+
const clone = new Bitstream(this.bytes);
|
|
95
|
+
clone.pos = this.pos;
|
|
96
|
+
return clone;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Reads an exponential-Golomb universal code from a Bitstream. */
|
|
101
|
+
export const readExpGolomb = (bitstream: Bitstream) => {
|
|
102
|
+
let leadingZeroBits = 0;
|
|
103
|
+
while (bitstream.readBit() === 0 && leadingZeroBits < 32) {
|
|
104
|
+
leadingZeroBits++;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (leadingZeroBits >= 32) {
|
|
108
|
+
throw new Error('Invalid exponential-Golomb code.');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const result = (1 << leadingZeroBits) - 1 + bitstream.readBits(leadingZeroBits);
|
|
112
|
+
return result;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/** Reads a signed exponential-Golomb universal code from a Bitstream. */
|
|
116
|
+
export const readSignedExpGolomb = (bitstream: Bitstream) => {
|
|
117
|
+
const codeNum = readExpGolomb(bitstream);
|
|
118
|
+
|
|
119
|
+
return ((codeNum & 1) === 0)
|
|
120
|
+
? -(codeNum >> 1)
|
|
121
|
+
: ((codeNum + 1) >> 1);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export const writeBits = (bytes: Uint8Array, start: number, end: number, value: number) => {
|
|
125
|
+
for (let i = start; i < end; i++) {
|
|
126
|
+
const byteIndex = Math.floor(i / 8);
|
|
127
|
+
let byte = bytes[byteIndex]!;
|
|
128
|
+
const bitIndex = 0b111 - (i & 0b111);
|
|
129
|
+
|
|
130
|
+
byte &= ~(1 << bitIndex);
|
|
131
|
+
byte |= ((value & (1 << (end - i - 1))) >> (end - i - 1)) << bitIndex;
|
|
132
|
+
bytes[byteIndex] = byte;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export const toUint8Array = (source: AllowSharedBufferSource): Uint8Array => {
|
|
137
|
+
if (source instanceof Uint8Array) {
|
|
138
|
+
return source;
|
|
139
|
+
} else if (source instanceof ArrayBuffer) {
|
|
140
|
+
return new Uint8Array(source);
|
|
141
|
+
} else {
|
|
142
|
+
return new Uint8Array(source.buffer, source.byteOffset, source.byteLength);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export const toDataView = (source: AllowSharedBufferSource) => {
|
|
147
|
+
if (source instanceof DataView) {
|
|
148
|
+
return source;
|
|
149
|
+
} else if (source instanceof ArrayBuffer) {
|
|
150
|
+
return new DataView(source);
|
|
151
|
+
} else {
|
|
152
|
+
return new DataView(source.buffer, source.byteOffset, source.byteLength);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
export const textEncoder = new TextEncoder();
|
|
157
|
+
|
|
158
|
+
const invertObject = <K extends PropertyKey, V extends PropertyKey>(object: Record<K, V>) => {
|
|
159
|
+
return Object.fromEntries(Object.entries(object).map(([key, value]) => [value, key])) as Record<V, K>;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// For the color space mappings, see Rec. ITU-T H.273.
|
|
163
|
+
|
|
164
|
+
export const COLOR_PRIMARIES_MAP = {
|
|
165
|
+
bt709: 1, // ITU-R BT.709
|
|
166
|
+
bt470bg: 5, // ITU-R BT.470BG
|
|
167
|
+
smpte170m: 6, // ITU-R BT.601 525 - SMPTE 170M
|
|
168
|
+
bt2020: 9, // ITU-R BT.202
|
|
169
|
+
smpte432: 12, // SMPTE EG 432-1
|
|
170
|
+
};
|
|
171
|
+
export const COLOR_PRIMARIES_MAP_INVERSE = invertObject(COLOR_PRIMARIES_MAP);
|
|
172
|
+
|
|
173
|
+
export const TRANSFER_CHARACTERISTICS_MAP = {
|
|
174
|
+
'bt709': 1, // ITU-R BT.709
|
|
175
|
+
'smpte170m': 6, // SMPTE 170M
|
|
176
|
+
'linear': 8, // Linear transfer characteristics
|
|
177
|
+
'iec61966-2-1': 13, // IEC 61966-2-1
|
|
178
|
+
'pg': 16, // Rec. ITU-R BT.2100-2 perceptual quantization (PQ) system
|
|
179
|
+
'hlg': 18, // Rec. ITU-R BT.2100-2 hybrid loggamma (HLG) system
|
|
180
|
+
};
|
|
181
|
+
export const TRANSFER_CHARACTERISTICS_MAP_INVERSE = invertObject(TRANSFER_CHARACTERISTICS_MAP);
|
|
182
|
+
|
|
183
|
+
export const MATRIX_COEFFICIENTS_MAP = {
|
|
184
|
+
'rgb': 0, // Identity
|
|
185
|
+
'bt709': 1, // ITU-R BT.709
|
|
186
|
+
'bt470bg': 5, // ITU-R BT.470BG
|
|
187
|
+
'smpte170m': 6, // SMPTE 170M
|
|
188
|
+
'bt2020-ncl': 9, // ITU-R BT.2020-2 (non-constant luminance)
|
|
189
|
+
};
|
|
190
|
+
export const MATRIX_COEFFICIENTS_MAP_INVERSE = invertObject(MATRIX_COEFFICIENTS_MAP);
|
|
191
|
+
|
|
192
|
+
export type RequiredNonNull<T> = {
|
|
193
|
+
[K in keyof T]-?: NonNullable<T[K]>;
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
export const colorSpaceIsComplete = (
|
|
197
|
+
colorSpace: VideoColorSpaceInit | undefined,
|
|
198
|
+
): colorSpace is RequiredNonNull<VideoColorSpaceInit> => {
|
|
199
|
+
return (
|
|
200
|
+
!!colorSpace
|
|
201
|
+
&& !!colorSpace.primaries
|
|
202
|
+
&& !!colorSpace.transfer
|
|
203
|
+
&& !!colorSpace.matrix
|
|
204
|
+
&& colorSpace.fullRange !== undefined
|
|
205
|
+
);
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
export const isAllowSharedBufferSource = (x: unknown) => {
|
|
209
|
+
return (
|
|
210
|
+
x instanceof ArrayBuffer
|
|
211
|
+
|| (typeof SharedArrayBuffer !== 'undefined' && x instanceof SharedArrayBuffer)
|
|
212
|
+
|| ArrayBuffer.isView(x)
|
|
213
|
+
);
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
export class AsyncMutex {
|
|
217
|
+
currentPromise = Promise.resolve();
|
|
218
|
+
|
|
219
|
+
async acquire() {
|
|
220
|
+
let resolver: () => void;
|
|
221
|
+
const nextPromise = new Promise<void>((resolve) => {
|
|
222
|
+
resolver = resolve;
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
const currentPromiseAlias = this.currentPromise;
|
|
226
|
+
this.currentPromise = nextPromise;
|
|
227
|
+
|
|
228
|
+
await currentPromiseAlias;
|
|
229
|
+
|
|
230
|
+
return resolver!;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export const bytesToHexString = (bytes: Uint8Array) => {
|
|
235
|
+
return [...bytes].map(x => x.toString(16).padStart(2, '0')).join('');
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
export const reverseBitsU32 = (x: number): number => {
|
|
239
|
+
x = ((x >> 1) & 0x55555555) | ((x & 0x55555555) << 1);
|
|
240
|
+
x = ((x >> 2) & 0x33333333) | ((x & 0x33333333) << 2);
|
|
241
|
+
x = ((x >> 4) & 0x0f0f0f0f) | ((x & 0x0f0f0f0f) << 4);
|
|
242
|
+
x = ((x >> 8) & 0x00ff00ff) | ((x & 0x00ff00ff) << 8);
|
|
243
|
+
x = ((x >> 16) & 0x0000ffff) | ((x & 0x0000ffff) << 16);
|
|
244
|
+
return x >>> 0; // Ensure it's treated as an unsigned 32-bit integer
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
/** Returns the smallest index i such that val[i] === key, or -1 if no such index exists. */
|
|
248
|
+
export const binarySearchExact = <T>(arr: T[], key: number, valueGetter: (x: T) => number): number => {
|
|
249
|
+
let low = 0;
|
|
250
|
+
let high = arr.length - 1;
|
|
251
|
+
let ans = -1;
|
|
252
|
+
|
|
253
|
+
while (low <= high) {
|
|
254
|
+
const mid = (low + high) >> 1;
|
|
255
|
+
const midVal = valueGetter(arr[mid]!);
|
|
256
|
+
|
|
257
|
+
if (midVal === key) {
|
|
258
|
+
ans = mid;
|
|
259
|
+
high = mid - 1; // Continue searching left to find the lowest index
|
|
260
|
+
} else if (midVal < key) {
|
|
261
|
+
low = mid + 1;
|
|
262
|
+
} else {
|
|
263
|
+
high = mid - 1;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return ans;
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
/** Returns the largest index i such that val[i] <= key, or -1 if no such index exists. */
|
|
271
|
+
export const binarySearchLessOrEqual = <T>(arr: T[], key: number, valueGetter: (x: T) => number) => {
|
|
272
|
+
let low = 0;
|
|
273
|
+
let high = arr.length - 1;
|
|
274
|
+
let ans = -1;
|
|
275
|
+
|
|
276
|
+
while (low <= high) {
|
|
277
|
+
const mid = (low + (high - low + 1) / 2) | 0;
|
|
278
|
+
const midVal = valueGetter(arr[mid]!);
|
|
279
|
+
|
|
280
|
+
if (midVal <= key) {
|
|
281
|
+
ans = mid;
|
|
282
|
+
low = mid + 1;
|
|
283
|
+
} else {
|
|
284
|
+
high = mid - 1;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return ans;
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
export const promiseWithResolvers = <T = void>() => {
|
|
292
|
+
let resolve: (value: T) => void;
|
|
293
|
+
let reject: (reason: unknown) => void;
|
|
294
|
+
const promise = new Promise<T>((res, rej) => {
|
|
295
|
+
resolve = res;
|
|
296
|
+
reject = rej;
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
return { promise, resolve: resolve!, reject: reject! };
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
export const removeItem = <T>(arr: T[], item: T) => {
|
|
303
|
+
const index = arr.indexOf(item);
|
|
304
|
+
if (index !== -1) {
|
|
305
|
+
arr.splice(index, 1);
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
export const findLast = <T>(arr: T[], predicate: (x: T) => boolean) => {
|
|
310
|
+
for (let i = arr.length - 1; i >= 0; i--) {
|
|
311
|
+
if (predicate(arr[i]!)) {
|
|
312
|
+
return arr[i];
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return undefined;
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
export const findLastIndex = <T>(arr: T[], predicate: (x: T) => boolean) => {
|
|
320
|
+
for (let i = arr.length - 1; i >= 0; i--) {
|
|
321
|
+
if (predicate(arr[i]!)) {
|
|
322
|
+
return i;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return -1;
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Sync or async iterable.
|
|
331
|
+
* @public
|
|
332
|
+
*/
|
|
333
|
+
export type AnyIterable<T> =
|
|
334
|
+
| Iterable<T>
|
|
335
|
+
| AsyncIterable<T>;
|
|
336
|
+
|
|
337
|
+
export const toAsyncIterator = async function* <T>(source: AnyIterable<T>): AsyncGenerator<T, void, unknown> {
|
|
338
|
+
if (Symbol.iterator in source) {
|
|
339
|
+
// @ts-expect-error Trust me
|
|
340
|
+
yield* source[Symbol.iterator]();
|
|
341
|
+
} else {
|
|
342
|
+
// @ts-expect-error Trust me
|
|
343
|
+
yield* source[Symbol.asyncIterator]();
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
export const validateAnyIterable = (iterable: AnyIterable<unknown>) => {
|
|
348
|
+
if (!(Symbol.iterator in iterable) && !(Symbol.asyncIterator in iterable)) {
|
|
349
|
+
throw new TypeError('Argument must be an iterable or async iterable.');
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
export const assertNever = (x: never) => {
|
|
354
|
+
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
355
|
+
throw new Error(`Unexpected value: ${x}`);
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
export const getUint24 = (view: DataView, byteOffset: number, littleEndian: boolean) => {
|
|
359
|
+
const byte1 = view.getUint8(byteOffset);
|
|
360
|
+
const byte2 = view.getUint8(byteOffset + 1);
|
|
361
|
+
const byte3 = view.getUint8(byteOffset + 2);
|
|
362
|
+
|
|
363
|
+
if (littleEndian) {
|
|
364
|
+
return byte1 | (byte2 << 8) | (byte3 << 16);
|
|
365
|
+
} else {
|
|
366
|
+
return (byte1 << 16) | (byte2 << 8) | byte3;
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
export const getInt24 = (view: DataView, byteOffset: number, littleEndian: boolean) => {
|
|
371
|
+
// The left shift pushes the most significant bit into the sign bit region, and the subsequent right shift
|
|
372
|
+
// then correctly interprets the sign bit.
|
|
373
|
+
return getUint24(view, byteOffset, littleEndian) << 8 >> 8;
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
export const setUint24 = (view: DataView, byteOffset: number, value: number, littleEndian: boolean) => {
|
|
377
|
+
// Ensure the value is within 24-bit unsigned range (0 to 16777215)
|
|
378
|
+
value = value >>> 0; // Convert to unsigned 32-bit
|
|
379
|
+
value = value & 0xFFFFFF; // Mask to 24 bits
|
|
380
|
+
|
|
381
|
+
if (littleEndian) {
|
|
382
|
+
view.setUint8(byteOffset, value & 0xFF);
|
|
383
|
+
view.setUint8(byteOffset + 1, (value >>> 8) & 0xFF);
|
|
384
|
+
view.setUint8(byteOffset + 2, (value >>> 16) & 0xFF);
|
|
385
|
+
} else {
|
|
386
|
+
view.setUint8(byteOffset, (value >>> 16) & 0xFF);
|
|
387
|
+
view.setUint8(byteOffset + 1, (value >>> 8) & 0xFF);
|
|
388
|
+
view.setUint8(byteOffset + 2, value & 0xFF);
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
export const setInt24 = (view: DataView, byteOffset: number, value: number, littleEndian: boolean) => {
|
|
393
|
+
// Ensure the value is within 24-bit signed range (-8388608 to 8388607)
|
|
394
|
+
value = clamp(value, -8388608, 8388607);
|
|
395
|
+
|
|
396
|
+
// Convert negative values to their 24-bit representation
|
|
397
|
+
if (value < 0) {
|
|
398
|
+
value = (value + 0x1000000) & 0xFFFFFF;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
setUint24(view, byteOffset, value, littleEndian);
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
export const setInt64 = (view: DataView, byteOffset: number, value: number, littleEndian: boolean) => {
|
|
405
|
+
if (littleEndian) {
|
|
406
|
+
view.setUint32(byteOffset + 0, value, true);
|
|
407
|
+
view.setInt32(byteOffset + 4, Math.floor(value / 2 ** 32), true);
|
|
408
|
+
} else {
|
|
409
|
+
view.setInt32(byteOffset + 0, Math.floor(value / 2 ** 32), true);
|
|
410
|
+
view.setUint32(byteOffset + 4, value, true);
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Calls a function on each value spat out by an async generator. The reason for writing this manually instead of
|
|
416
|
+
* using a generator function is that the generator function queues return() calls - here, we forward them immediately.
|
|
417
|
+
*/
|
|
418
|
+
export const mapAsyncGenerator = <T, U>(
|
|
419
|
+
generator: AsyncGenerator<T, void, unknown>,
|
|
420
|
+
map: (t: T) => U,
|
|
421
|
+
): AsyncGenerator<U, void, unknown> => {
|
|
422
|
+
return {
|
|
423
|
+
async next() {
|
|
424
|
+
const result = await generator.next();
|
|
425
|
+
if (result.done) {
|
|
426
|
+
return { value: undefined, done: true };
|
|
427
|
+
} else {
|
|
428
|
+
return { value: map(result.value), done: false };
|
|
429
|
+
}
|
|
430
|
+
},
|
|
431
|
+
return() {
|
|
432
|
+
return generator.return() as ReturnType<AsyncGenerator<U, void, unknown>['return']>;
|
|
433
|
+
},
|
|
434
|
+
throw(error) {
|
|
435
|
+
return generator.throw(error) as ReturnType<AsyncGenerator<U, void, unknown>['throw']>;
|
|
436
|
+
},
|
|
437
|
+
[Symbol.asyncIterator]() {
|
|
438
|
+
return this;
|
|
439
|
+
},
|
|
440
|
+
};
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
export const clamp = (value: number, min: number, max: number) => {
|
|
444
|
+
return Math.max(min, Math.min(max, value));
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
export const UNDETERMINED_LANGUAGE = 'und';
|
|
448
|
+
|
|
449
|
+
export const roundToPrecision = (value: number, digits: number) => {
|
|
450
|
+
const factor = 10 ** digits;
|
|
451
|
+
return Math.round(value * factor) / factor;
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
export const roundToMultiple = (value: number, multiple: number) => {
|
|
455
|
+
return Math.round(value / multiple) * multiple;
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
export const ilog = (x: number) => {
|
|
459
|
+
let ret = 0;
|
|
460
|
+
while (x) {
|
|
461
|
+
ret++;
|
|
462
|
+
x >>= 1;
|
|
463
|
+
}
|
|
464
|
+
return ret;
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
const ISO_639_2_REGEX = /^[a-z]{3}$/;
|
|
468
|
+
export const isIso639Dash2LanguageCode = (x: string) => {
|
|
469
|
+
return ISO_639_2_REGEX.test(x);
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
// Since the result will be truncated, add a bit of eps to compensate for floating point errors
|
|
473
|
+
export const SECOND_TO_MICROSECOND_FACTOR = 1e6 * (1 + Number.EPSILON);
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Sets all keys K of T to be required.
|
|
477
|
+
* @public
|
|
478
|
+
*/
|
|
479
|
+
export type SetRequired<T, K extends keyof T> = T & Required<Pick<T, K>>;
|
|
480
|
+
|
|
481
|
+
export const mergeObjectsDeeply = <T extends object, S extends object>(a: T, b: S): T & S => {
|
|
482
|
+
const result = { ...a } as T & S;
|
|
483
|
+
|
|
484
|
+
for (const key in b) {
|
|
485
|
+
if (
|
|
486
|
+
typeof a[key as unknown as keyof T] === 'object'
|
|
487
|
+
&& a[key as unknown as keyof T] !== null
|
|
488
|
+
&& typeof b[key] === 'object'
|
|
489
|
+
&& b[key] !== null
|
|
490
|
+
) {
|
|
491
|
+
result[key] = mergeObjectsDeeply(
|
|
492
|
+
a[key as unknown as keyof T] as object,
|
|
493
|
+
b[key],
|
|
494
|
+
) as (T & S)[Extract<keyof S, string>];
|
|
495
|
+
} else {
|
|
496
|
+
result[key] = b[key] as (T & S)[Extract<keyof S, string>];
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return result;
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
export const retriedFetch = async (
|
|
504
|
+
url: string | URL,
|
|
505
|
+
requestInit: RequestInit,
|
|
506
|
+
getRetryDelay: (previousAttempts: number) => number | null,
|
|
507
|
+
) => {
|
|
508
|
+
let attempts = 0;
|
|
509
|
+
|
|
510
|
+
while (true) {
|
|
511
|
+
try {
|
|
512
|
+
return await fetch(url, requestInit);
|
|
513
|
+
} catch (error) {
|
|
514
|
+
console.error('Retrying failed fetch. Error:', error);
|
|
515
|
+
|
|
516
|
+
attempts++;
|
|
517
|
+
|
|
518
|
+
const retryDelayInSeconds = getRetryDelay(attempts);
|
|
519
|
+
if (retryDelayInSeconds === null) {
|
|
520
|
+
throw error;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if (!Number.isFinite(retryDelayInSeconds) || retryDelayInSeconds < 0) {
|
|
524
|
+
throw new TypeError('Retry delay must be a non-negative finite number.');
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (retryDelayInSeconds > 0) {
|
|
528
|
+
await new Promise(resolve => setTimeout(resolve, 1000 * retryDelayInSeconds));
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
export const computeRationalApproximation = (x: number, maxDenominator: number) => {
|
|
535
|
+
// Handle negative numbers
|
|
536
|
+
const sign = x < 0 ? -1 : 1;
|
|
537
|
+
x = Math.abs(x);
|
|
538
|
+
|
|
539
|
+
let prevNumerator = 0, prevDenominator = 1;
|
|
540
|
+
let currNumerator = 1, currDenominator = 0;
|
|
541
|
+
|
|
542
|
+
// Continued fraction algorithm
|
|
543
|
+
let remainder = x;
|
|
544
|
+
|
|
545
|
+
while (true) {
|
|
546
|
+
const integer = Math.floor(remainder);
|
|
547
|
+
|
|
548
|
+
// Calculate next convergent
|
|
549
|
+
const nextNumerator = integer * currNumerator + prevNumerator;
|
|
550
|
+
const nextDenominator = integer * currDenominator + prevDenominator;
|
|
551
|
+
|
|
552
|
+
if (nextDenominator > maxDenominator) {
|
|
553
|
+
return {
|
|
554
|
+
numerator: sign * currNumerator,
|
|
555
|
+
denominator: currDenominator,
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
prevNumerator = currNumerator;
|
|
560
|
+
prevDenominator = currDenominator;
|
|
561
|
+
currNumerator = nextNumerator;
|
|
562
|
+
currDenominator = nextDenominator;
|
|
563
|
+
|
|
564
|
+
remainder = 1 / (remainder - integer);
|
|
565
|
+
|
|
566
|
+
// Guard against precision issues
|
|
567
|
+
if (!isFinite(remainder)) {
|
|
568
|
+
break;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
return {
|
|
573
|
+
numerator: sign * currNumerator,
|
|
574
|
+
denominator: currDenominator,
|
|
575
|
+
};
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
export class CallSerializer {
|
|
579
|
+
currentPromise = Promise.resolve();
|
|
580
|
+
|
|
581
|
+
call(fn: () => Promise<void> | void) {
|
|
582
|
+
return this.currentPromise = this.currentPromise.then(fn);
|
|
583
|
+
}
|
|
584
|
+
}
|