audio-snip 0.1.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Stepan Mikhailiuk
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # audio-snip
2
+
3
+ Decode a segment of a remote audio file via HTTP Range requests — without downloading the whole thing.
4
+
5
+ A 50 MB podcast, but you only need seconds 30–60? `audio-snip` fetches ~500 KB instead of 50 MB.
6
+
7
+ ## Demo
8
+
9
+ [Live demo](https://stepancar.github.io/audio-snip/) — pick a test file, choose a time range, see exactly which byte ranges were fetched.
10
+
11
+ ## Before / After
12
+
13
+ ### Before — vanilla fetch + AudioContext
14
+
15
+ You have to download the **entire file**, then decode, then slice:
16
+
17
+ ```ts
18
+ const resp = await fetch(url); // downloads all 50 MB
19
+ const buf = await resp.arrayBuffer();
20
+ const full = await audioCtx.decodeAudioData(buf);
21
+
22
+ // manual trimming
23
+ const start = Math.round(30 * full.sampleRate);
24
+ const end = Math.round(60 * full.sampleRate);
25
+ const trimmed = audioCtx.createBuffer(full.numberOfChannels, end - start, full.sampleRate);
26
+ for (let ch = 0; ch < full.numberOfChannels; ch++) {
27
+ trimmed.copyToChannel(full.getChannelData(ch).subarray(start, end), ch);
28
+ }
29
+ ```
30
+
31
+ ### After — audio-snip
32
+
33
+ ```ts
34
+ import { audioSnip } from 'audio-snip';
35
+ import { Mp3Plugin } from 'audio-snip/mp3';
36
+ import { Mp4Plugin } from 'audio-snip/mp4';
37
+
38
+ audioSnip.register(new Mp3Plugin());
39
+ audioSnip.register(new Mp4Plugin());
40
+
41
+ const buffer = await audioSnip.decodeAudioDataSegment(audioCtx, url, 30, 60);
42
+ // AudioBuffer with exactly 30 seconds of audio
43
+ // only a few hundred KB were fetched
44
+ ```
45
+
46
+ ## How it works
47
+
48
+ 1. Fetch file header (first ~128 KB) to parse codec metadata
49
+ 2. Use metadata (Xing TOC for MP3, stbl tables for MP4) to map time → byte offset
50
+ 3. Fetch only the needed byte range via HTTP `Range` request
51
+ 4. Decode and trim to exact sample boundaries
52
+
53
+ ## Supported formats
54
+
55
+ | Format | Plugin | Notes |
56
+ |--------|--------|-------|
57
+ | MP3 CBR/VBR | `Mp3Plugin` | Xing/VBRI TOC, LAME gapless, ID3v2 |
58
+ | M4A/MP4/AAC | `Mp4Plugin` | Uses mp4box.js, ADTS wrapping, video+audio files |
59
+
60
+ ## Install
61
+
62
+ ```sh
63
+ npm install audio-snip
64
+ ```
65
+
66
+ ## License
67
+
68
+ MIT
package/dist/core.d.ts ADDED
@@ -0,0 +1,25 @@
1
+ export interface AudioFileInfo {
2
+ duration: number | null;
3
+ sampleRate: number;
4
+ channels: number;
5
+ bitrate: number;
6
+ codec: string;
7
+ isVbr: boolean;
8
+ encoderDelay: number;
9
+ }
10
+ export declare abstract class BasePlugin {
11
+ abstract canHandle(url: string): boolean;
12
+ abstract getInfo(url: string): Promise<AudioFileInfo>;
13
+ abstract decode(ctx: BaseAudioContext, url: string, startTime: number, endTime: number): Promise<AudioBuffer>;
14
+ }
15
+ export declare function fetchRange(url: string, start: number, end: number): Promise<Uint8Array>;
16
+ export declare function fetchContentLength(url: string): Promise<number | null>;
17
+ export declare function trimBySamples(ctx: BaseAudioContext, buffer: AudioBuffer, startSample: number, endSample: number): AudioBuffer;
18
+ declare class AudioSnip {
19
+ private plugins;
20
+ register(plugin: BasePlugin): void;
21
+ private resolve;
22
+ decodeAudioDataSegment(ctx: BaseAudioContext, url: string, startTime: number, endTime: number): Promise<AudioBuffer>;
23
+ }
24
+ export declare const audioSnip: AudioSnip;
25
+ export {};
package/dist/core.js ADDED
@@ -0,0 +1,73 @@
1
+ // ─── Types ───────────────────────────────────────────────────────────────────
2
+ export class BasePlugin {
3
+ }
4
+ // ─── Shared utilities ────────────────────────────────────────────────────────
5
+ export async function fetchRange(url, start, end) {
6
+ const resp = await fetch(url, {
7
+ headers: { Range: `bytes=${start}-${end}` },
8
+ });
9
+ if (!resp.ok && resp.status !== 206) {
10
+ throw new Error(`HTTP ${resp.status} fetching range ${start}-${end}`);
11
+ }
12
+ const full = new Uint8Array(await resp.arrayBuffer());
13
+ // If server ignored Range header and returned full file (200 instead of 206),
14
+ // slice to the requested range manually.
15
+ if (resp.status === 200 && full.length > end - start + 1) {
16
+ return full.slice(start, end + 1);
17
+ }
18
+ return full;
19
+ }
20
+ export async function fetchContentLength(url) {
21
+ // Try HEAD first
22
+ const headResp = await fetch(url, { method: 'HEAD' });
23
+ const cl = headResp.headers.get('content-length');
24
+ if (cl)
25
+ return parseInt(cl, 10);
26
+ // Fallback: GET with Range 0-0 and parse Content-Range
27
+ const rangeResp = await fetch(url, {
28
+ headers: { Range: 'bytes=0-0' },
29
+ });
30
+ const cr = rangeResp.headers.get('content-range');
31
+ if (cr) {
32
+ const match = cr.match(/\/(\d+)/);
33
+ if (match)
34
+ return parseInt(match[1], 10);
35
+ }
36
+ return null;
37
+ }
38
+ export function trimBySamples(ctx, buffer, startSample, endSample) {
39
+ const start = Math.max(0, Math.round(startSample));
40
+ const end = Math.min(buffer.length, Math.round(endSample));
41
+ const length = end - start;
42
+ if (length <= 0) {
43
+ throw new Error(`Invalid trim range: startSample=${startSample}, endSample=${endSample}, bufferLength=${buffer.length}`);
44
+ }
45
+ const trimmed = ctx.createBuffer(buffer.numberOfChannels, length, buffer.sampleRate);
46
+ for (let ch = 0; ch < buffer.numberOfChannels; ch++) {
47
+ const src = buffer.getChannelData(ch);
48
+ trimmed.copyToChannel(src.subarray(start, end), ch);
49
+ }
50
+ return trimmed;
51
+ }
52
+ // ─── Plugin registry / singleton ─────────────────────────────────────────────
53
+ class AudioSnip {
54
+ constructor() {
55
+ this.plugins = [];
56
+ }
57
+ register(plugin) {
58
+ this.plugins.push(plugin);
59
+ }
60
+ resolve(url) {
61
+ for (const p of this.plugins) {
62
+ if (p.canHandle(url))
63
+ return p;
64
+ }
65
+ throw new Error(`No plugin registered for URL: ${url}`);
66
+ }
67
+ async decodeAudioDataSegment(ctx, url, startTime, endTime) {
68
+ const plugin = this.resolve(url);
69
+ return plugin.decode(ctx, url, startTime, endTime);
70
+ }
71
+ }
72
+ export const audioSnip = new AudioSnip();
73
+ //# sourceMappingURL=core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.js","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export { audioSnip, BasePlugin, fetchRange, fetchContentLength, trimBySamples, } from './core.js';
2
+ export type { AudioFileInfo } from './core.js';
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { audioSnip, BasePlugin, fetchRange, fetchContentLength, trimBySamples, } from './core.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,UAAU,EACV,UAAU,EACV,kBAAkB,EAClB,aAAa,GACd,MAAM,WAAW,CAAC"}
@@ -0,0 +1,13 @@
1
+ import { BasePlugin, AudioFileInfo } from '../core.js';
2
+ export interface Mp3PluginOptions {
3
+ paddingFrames?: number;
4
+ }
5
+ export declare class Mp3Plugin extends BasePlugin {
6
+ private paddingFrames;
7
+ constructor(options?: Mp3PluginOptions);
8
+ canHandle(url: string): boolean;
9
+ getInfo(url: string): Promise<AudioFileInfo>;
10
+ decode(ctx: BaseAudioContext, url: string, startTime: number, endTime: number): Promise<AudioBuffer>;
11
+ private fetchHeader;
12
+ private computeDuration;
13
+ }
@@ -0,0 +1,366 @@
1
+ import { BasePlugin, fetchRange, fetchContentLength, trimBySamples, } from '../core.js';
2
+ // ─── Constants ───────────────────────────────────────────────────────────────
3
+ const HEAD_SIZE = 128 * 1024; // 128 KB initial fetch
4
+ // MPEG bitrate tables [version][layer][index]
5
+ const BITRATE_TABLE = {
6
+ 'V1L1': [0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0],
7
+ 'V1L2': [0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0],
8
+ 'V1L3': [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0],
9
+ 'V2L1': [0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0],
10
+ 'V2L2': [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0],
11
+ 'V2L3': [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0],
12
+ };
13
+ const SAMPLE_RATE_TABLE = [
14
+ [44100, 48000, 32000, 0], // MPEG1
15
+ [22050, 24000, 16000, 0], // MPEG2
16
+ [11025, 12000, 8000, 0], // MPEG2.5
17
+ ];
18
+ const SAMPLES_PER_FRAME = {
19
+ 'V1L1': 384,
20
+ 'V1L2': 1152,
21
+ 'V1L3': 1152,
22
+ 'V2L1': 384,
23
+ 'V2L2': 1152,
24
+ 'V2L3': 576,
25
+ };
26
+ function parseFrameHeader(data, offset) {
27
+ if (offset + 4 > data.length)
28
+ return null;
29
+ const b0 = data[offset];
30
+ const b1 = data[offset + 1];
31
+ const b2 = data[offset + 2];
32
+ const b3 = data[offset + 3];
33
+ // Sync: 11 bits set
34
+ if (b0 !== 0xFF || (b1 & 0xE0) !== 0xE0)
35
+ return null;
36
+ const versionBits = (b1 >> 3) & 0x03;
37
+ const layerBits = (b1 >> 1) & 0x03;
38
+ const bitrateIdx = (b2 >> 4) & 0x0F;
39
+ const srIdx = (b2 >> 2) & 0x03;
40
+ const padding = ((b2 >> 1) & 0x01) === 1;
41
+ const channelMode = (b3 >> 6) & 0x03;
42
+ // Reject reserved values
43
+ if (versionBits === 1 || layerBits === 0 || bitrateIdx === 0 || bitrateIdx === 15 || srIdx === 3) {
44
+ return null;
45
+ }
46
+ const mpegVersion = versionBits === 3 ? 1 : versionBits === 2 ? 2 : 2.5;
47
+ const layer = layerBits === 3 ? 1 : layerBits === 2 ? 2 : 3;
48
+ const vKey = mpegVersion === 1 ? 'V1' : 'V2';
49
+ const lKey = `L${layer}`;
50
+ const key = `${vKey}${lKey}`;
51
+ const bitrate = BITRATE_TABLE[key]?.[bitrateIdx];
52
+ if (!bitrate)
53
+ return null;
54
+ const srTableIdx = mpegVersion === 1 ? 0 : mpegVersion === 2 ? 1 : 2;
55
+ const sampleRate = SAMPLE_RATE_TABLE[srTableIdx][srIdx];
56
+ if (!sampleRate)
57
+ return null;
58
+ const samplesPerFrame = SAMPLES_PER_FRAME[key];
59
+ const channels = channelMode === 3 ? 1 : 2;
60
+ let frameSize;
61
+ if (layer === 1) {
62
+ frameSize = Math.floor((12 * bitrate * 1000) / sampleRate + (padding ? 1 : 0)) * 4;
63
+ }
64
+ else {
65
+ const slotSize = 1;
66
+ const divisor = mpegVersion === 1 ? sampleRate : sampleRate * 2;
67
+ frameSize = Math.floor((samplesPerFrame * bitrate * 1000) / (8 * divisor)) * 8 / 8 + (padding ? slotSize : 0);
68
+ // Simplified: frameSize = floor(samplesPerFrame/8 * bitrate*1000 / sampleRate) + padding
69
+ frameSize = Math.floor((samplesPerFrame * (bitrate * 1000) / 8) / sampleRate) + (padding ? slotSize : 0);
70
+ }
71
+ // Side information size for Layer 3
72
+ let sideInfoSize = 0;
73
+ if (layer === 3) {
74
+ if (mpegVersion === 1) {
75
+ sideInfoSize = channels === 1 ? 17 : 32;
76
+ }
77
+ else {
78
+ sideInfoSize = channels === 1 ? 9 : 17;
79
+ }
80
+ }
81
+ return {
82
+ mpegVersion,
83
+ layer,
84
+ bitrate,
85
+ sampleRate,
86
+ channels,
87
+ padding,
88
+ samplesPerFrame,
89
+ frameSize,
90
+ sideInfoSize,
91
+ };
92
+ }
93
+ // ─── ID3v2 tag size ──────────────────────────────────────────────────────────
94
+ function id3v2Size(data) {
95
+ if (data.length < 10)
96
+ return 0;
97
+ if (data[0] !== 0x49 || data[1] !== 0x44 || data[2] !== 0x33)
98
+ return 0; // "ID3"
99
+ // Syncsafe integer at bytes 6-9
100
+ const size = ((data[6] & 0x7F) << 21) |
101
+ ((data[7] & 0x7F) << 14) |
102
+ ((data[8] & 0x7F) << 7) |
103
+ (data[9] & 0x7F);
104
+ return size + 10; // 10-byte header not included in size field
105
+ }
106
+ // ─── Find first frame sync after given offset ───────────────────────────────
107
+ function findFrameSync(data, start) {
108
+ for (let i = start; i < data.length - 4; i++) {
109
+ if (data[i] === 0xFF && (data[i + 1] & 0xE0) === 0xE0) {
110
+ const hdr = parseFrameHeader(data, i);
111
+ if (hdr && hdr.frameSize > 0) {
112
+ // Validate next frame exists
113
+ const next = i + hdr.frameSize;
114
+ if (next + 2 <= data.length) {
115
+ if (data[next] === 0xFF && (data[next + 1] & 0xE0) === 0xE0) {
116
+ return i;
117
+ }
118
+ }
119
+ else {
120
+ // Near end of data, trust single sync
121
+ return i;
122
+ }
123
+ }
124
+ }
125
+ }
126
+ return -1;
127
+ }
128
+ function parseXingVbri(data, frameOffset, header) {
129
+ // Xing/Info header is after frame header (4 bytes) + side info
130
+ const xingOffset = frameOffset + 4 + header.sideInfoSize;
131
+ if (xingOffset + 4 > data.length)
132
+ return null;
133
+ const tag = String.fromCharCode(data[xingOffset], data[xingOffset + 1], data[xingOffset + 2], data[xingOffset + 3]);
134
+ if (tag === 'Xing' || tag === 'Info') {
135
+ const flags = readU32BE(data, xingOffset + 4);
136
+ let pos = xingOffset + 8;
137
+ let totalFrames = null;
138
+ let totalBytes = null;
139
+ let toc = null;
140
+ if (flags & 1) {
141
+ totalFrames = readU32BE(data, pos);
142
+ pos += 4;
143
+ }
144
+ if (flags & 2) {
145
+ totalBytes = readU32BE(data, pos);
146
+ pos += 4;
147
+ }
148
+ if (flags & 4) {
149
+ toc = data.slice(pos, pos + 100);
150
+ pos += 100;
151
+ }
152
+ // flags & 8 → quality indicator, skip
153
+ if (flags & 8) {
154
+ pos += 4;
155
+ }
156
+ // LAME gapless info at xingOffset + 141 (relative to Xing tag start)
157
+ // The LAME header starts 120 bytes after the Xing tag position
158
+ let encoderDelay = 0;
159
+ let encoderPadding = 0;
160
+ const lameOffset = xingOffset + 120 + 21; // LAME encoder delays are at Xing+141
161
+ if (lameOffset + 3 <= data.length) {
162
+ // Verify LAME tag exists (9 char encoder string at xingOffset+120)
163
+ const lameTag = String.fromCharCode(data[xingOffset + 120], data[xingOffset + 121], data[xingOffset + 122], data[xingOffset + 123]);
164
+ if (lameTag === 'LAME' || lameTag === 'Lavf' || lameTag === 'Lavc') {
165
+ // Encoder delay: 12 bits at byte 141, encoder padding: 12 bits at byte 142-143
166
+ const delayByte1 = data[xingOffset + 141];
167
+ const delayByte2 = data[xingOffset + 142];
168
+ const delayByte3 = data[xingOffset + 143];
169
+ encoderDelay = (delayByte1 << 4) | (delayByte2 >> 4);
170
+ encoderPadding = ((delayByte2 & 0x0F) << 8) | delayByte3;
171
+ }
172
+ }
173
+ return {
174
+ isVbr: tag === 'Xing',
175
+ totalFrames,
176
+ totalBytes,
177
+ toc,
178
+ encoderDelay,
179
+ encoderPadding,
180
+ };
181
+ }
182
+ // Try VBRI (Fraunhofer) — always at offset 36 from frame start
183
+ const vbriOffset = frameOffset + 36;
184
+ if (vbriOffset + 4 <= data.length) {
185
+ const vbriTag = String.fromCharCode(data[vbriOffset], data[vbriOffset + 1], data[vbriOffset + 2], data[vbriOffset + 3]);
186
+ if (vbriTag === 'VBRI') {
187
+ const totalBytes = readU32BE(data, vbriOffset + 10);
188
+ const totalFrames = readU32BE(data, vbriOffset + 14);
189
+ return {
190
+ isVbr: true,
191
+ totalFrames,
192
+ totalBytes,
193
+ toc: null,
194
+ encoderDelay: 0,
195
+ encoderPadding: 0,
196
+ };
197
+ }
198
+ }
199
+ return null;
200
+ }
201
+ function readU32BE(data, offset) {
202
+ return (((data[offset] << 24) >>> 0) +
203
+ (data[offset + 1] << 16) +
204
+ (data[offset + 2] << 8) +
205
+ data[offset + 3]);
206
+ }
207
+ // ─── TOC-based byte interpolation for VBR ────────────────────────────────────
208
+ function tocInterpolate(toc, fraction, totalBytes) {
209
+ // fraction is 0..1 representing position in file
210
+ const scaledPos = fraction * 100;
211
+ const idx = Math.min(99, Math.floor(scaledPos));
212
+ const idxFrac = scaledPos - idx;
213
+ const lower = toc[idx];
214
+ const upper = idx < 99 ? toc[idx + 1] : 256;
215
+ const interpolated = lower + idxFrac * (upper - lower);
216
+ return Math.floor((interpolated / 256) * totalBytes);
217
+ }
218
+ export class Mp3Plugin extends BasePlugin {
219
+ constructor(options = {}) {
220
+ super();
221
+ this.paddingFrames = options.paddingFrames ?? 8;
222
+ }
223
+ canHandle(url) {
224
+ try {
225
+ const path = new URL(url, 'https://dummy').pathname.toLowerCase();
226
+ return path.endsWith('.mp3');
227
+ }
228
+ catch {
229
+ return url.toLowerCase().endsWith('.mp3');
230
+ }
231
+ }
232
+ async getInfo(url) {
233
+ const { header, vbrInfo, fileSize, audioStart } = await this.fetchHeader(url);
234
+ const duration = this.computeDuration(header, vbrInfo, fileSize, audioStart);
235
+ return {
236
+ duration,
237
+ sampleRate: header.sampleRate,
238
+ channels: header.channels,
239
+ bitrate: header.bitrate,
240
+ codec: `MPEG${header.mpegVersion} Layer ${header.layer}`,
241
+ isVbr: vbrInfo?.isVbr ?? false,
242
+ encoderDelay: vbrInfo?.encoderDelay ?? 0,
243
+ };
244
+ }
245
+ async decode(ctx, url, startTime, endTime) {
246
+ const { header, vbrInfo, audioStart, fileSize } = await this.fetchHeader(url);
247
+ const sampleRate = header.sampleRate;
248
+ const encoderDelay = vbrInfo?.encoderDelay ?? 0;
249
+ const encoderPadding = vbrInfo?.encoderPadding ?? 0;
250
+ const isVbr = vbrInfo?.isVbr ?? false;
251
+ const duration = this.computeDuration(header, vbrInfo, fileSize, audioStart);
252
+ // Compute byte range for the requested time segment
253
+ let startByte;
254
+ let endByte;
255
+ // For VBR+TOC, map time to byte position via TOC
256
+ if (isVbr && vbrInfo?.toc && vbrInfo.totalBytes && duration) {
257
+ const startFrac = Math.max(0, startTime / duration);
258
+ const endFrac = Math.min(1, endTime / duration);
259
+ startByte = audioStart + tocInterpolate(vbrInfo.toc, startFrac, vbrInfo.totalBytes);
260
+ endByte = audioStart + tocInterpolate(vbrInfo.toc, endFrac, vbrInfo.totalBytes);
261
+ }
262
+ else {
263
+ // CBR or VBR-without-TOC fallback: linear byte estimate
264
+ if (isVbr && (!vbrInfo?.toc)) {
265
+ console.warn('audio-snip: VBR file without TOC, using CBR byte estimate for Range request');
266
+ }
267
+ const bytesPerSecond = (header.bitrate * 1000) / 8;
268
+ startByte = audioStart + Math.floor(startTime * bytesPerSecond);
269
+ endByte = audioStart + Math.ceil(endTime * bytesPerSecond);
270
+ }
271
+ // Snap to frame boundaries and add padding
272
+ const paddingBytes = this.paddingFrames * header.frameSize;
273
+ const fetchStart = Math.max(audioStart, startByte - paddingBytes);
274
+ // Add extra frames at end for safety
275
+ const fetchEnd = Math.min((fileSize ?? endByte + paddingBytes * 2) - 1, endByte + paddingBytes * 2);
276
+ // Fetch the segment
277
+ const chunk = await fetchRange(url, fetchStart, fetchEnd);
278
+ // Find first valid frame in fetched chunk
279
+ const firstSync = findFrameSync(chunk, 0);
280
+ if (firstSync === -1) {
281
+ throw new Error('No valid MP3 frame found in fetched range');
282
+ }
283
+ // Walk frames to count samples up to the actual startByte offset
284
+ // This gives us exact sample count for trimming, even for VBR
285
+ const offsetInChunk = startByte - fetchStart;
286
+ let samplesBefore = 0;
287
+ let pos = firstSync;
288
+ while (pos < chunk.length - 4) {
289
+ const fh = parseFrameHeader(chunk, pos);
290
+ if (!fh || fh.frameSize <= 0)
291
+ break;
292
+ if (pos >= offsetInChunk)
293
+ break;
294
+ samplesBefore += fh.samplesPerFrame;
295
+ pos += fh.frameSize;
296
+ }
297
+ // Decode the entire fetched chunk
298
+ const audioData = new Uint8Array(chunk.subarray(firstSync)).buffer;
299
+ const decoded = await ctx.decodeAudioData(audioData.slice(0));
300
+ // Use decoded buffer's sampleRate — AudioContext may resample (e.g. 44100→48000)
301
+ const decodedRate = decoded.sampleRate;
302
+ const resampleRatio = decodedRate / sampleRate;
303
+ // Scale samplesBefore and encoderDelay to decoded rate
304
+ const trimStartSamples = Math.round((samplesBefore - encoderDelay) * resampleRatio);
305
+ const wantedSamples = Math.round((endTime - startTime) * decodedRate);
306
+ const startSample = Math.max(0, trimStartSamples);
307
+ let endSample = startSample + wantedSamples;
308
+ // If endTime >= duration, also trim encoder padding from end
309
+ if (duration && endTime >= duration - 0.01 && encoderPadding > 0) {
310
+ const scaledPadding = Math.round(encoderPadding * resampleRatio);
311
+ endSample = Math.min(endSample, decoded.length - scaledPadding);
312
+ }
313
+ endSample = Math.min(endSample, decoded.length);
314
+ return trimBySamples(ctx, decoded, startSample, endSample);
315
+ }
316
+ // ─── Internal helpers ────────────────────────────────────────────────────
317
+ async fetchHeader(url) {
318
+ const fileSize = await fetchContentLength(url);
319
+ // First pass: fetch initial chunk
320
+ let headData = await fetchRange(url, 0, HEAD_SIZE - 1);
321
+ // Check for ID3v2 tag
322
+ let audioStart = 0;
323
+ const id3Size = id3v2Size(headData);
324
+ if (id3Size > 0) {
325
+ audioStart = id3Size;
326
+ // If ID3 tag is larger than our initial fetch, re-fetch from after it
327
+ if (id3Size >= HEAD_SIZE) {
328
+ headData = await fetchRange(url, id3Size, id3Size + HEAD_SIZE - 1);
329
+ audioStart = id3Size;
330
+ }
331
+ }
332
+ // Find first frame sync
333
+ const searchStart = id3Size >= HEAD_SIZE ? 0 : audioStart;
334
+ const syncOffset = findFrameSync(headData, searchStart);
335
+ if (syncOffset === -1) {
336
+ throw new Error('No valid MP3 frame sync found');
337
+ }
338
+ const header = parseFrameHeader(headData, syncOffset);
339
+ const absoluteFrameOffset = id3Size >= HEAD_SIZE ? id3Size + syncOffset : syncOffset;
340
+ // Parse Xing/VBRI
341
+ const vbrInfo = parseXingVbri(headData, syncOffset, header);
342
+ // If there's a Xing/Info frame, the actual audio starts after it
343
+ const firstFrameOffset = vbrInfo
344
+ ? absoluteFrameOffset + header.frameSize
345
+ : absoluteFrameOffset;
346
+ return {
347
+ header,
348
+ vbrInfo,
349
+ audioStart: firstFrameOffset,
350
+ firstFrameOffset,
351
+ fileSize,
352
+ };
353
+ }
354
+ computeDuration(header, vbrInfo, fileSize, audioStart) {
355
+ if (vbrInfo?.totalFrames) {
356
+ const totalSamples = vbrInfo.totalFrames * header.samplesPerFrame;
357
+ return totalSamples / header.sampleRate;
358
+ }
359
+ if (fileSize) {
360
+ const audioBytes = fileSize - audioStart;
361
+ return audioBytes / ((header.bitrate * 1000) / 8);
362
+ }
363
+ return null;
364
+ }
365
+ }
366
+ //# sourceMappingURL=mp3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mp3.js","sourceRoot":"","sources":["../../src/plugins/mp3.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import { BasePlugin, AudioFileInfo } from '../core.js';
2
+ export declare class Mp4Plugin extends BasePlugin {
3
+ canHandle(url: string): boolean;
4
+ getInfo(url: string): Promise<AudioFileInfo>;
5
+ decode(ctx: BaseAudioContext, url: string, startTime: number, endTime: number): Promise<AudioBuffer>;
6
+ /**
7
+ * Read sample data from pre-fetched byte ranges.
8
+ */
9
+ private readSampleFromRanges;
10
+ /**
11
+ * Collapse sample byte ranges into minimal contiguous fetch ranges.
12
+ * Adjacent or overlapping ranges are merged to reduce HTTP requests.
13
+ */
14
+ private collapseRanges;
15
+ private loadMoov;
16
+ private loadMoovForDecode;
17
+ private streamToMP4Box;
18
+ }
@@ -0,0 +1,232 @@
1
+ import { BasePlugin, fetchRange, fetchContentLength, trimBySamples, } from '../core.js';
2
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
3
+ import MP4BoxFactory from 'mp4box';
4
+ const MP4BoxLib = MP4BoxFactory;
5
+ // ─── ADTS header construction ────────────────────────────────────────────────
6
+ function makeAdtsHeader(frameLength, audioObjectType, samplingFrequencyIndex, channelConfiguration) {
7
+ const header = new Uint8Array(7);
8
+ const fullLength = frameLength + 7;
9
+ // Syncword (12 bits), ID (1 bit = 0 for MPEG-4), Layer (2 bits = 00), Protection absent (1 bit = 1)
10
+ header[0] = 0xFF;
11
+ header[1] = 0xF1;
12
+ // Profile (2 bits, audioObjectType - 1), Sampling freq idx (4 bits), Private (1 bit = 0), Channel config high (1 bit)
13
+ header[2] =
14
+ ((audioObjectType - 1) << 6) |
15
+ (samplingFrequencyIndex << 2) |
16
+ (0 << 1) |
17
+ ((channelConfiguration >> 2) & 0x01);
18
+ // Channel config low (2 bits), Original (1 bit = 0), Home (1 bit = 0), copyright_id (1 bit = 0), copyright_start (1 bit = 0), frame length high (2 bits)
19
+ header[3] =
20
+ ((channelConfiguration & 0x03) << 6) |
21
+ ((fullLength >> 11) & 0x03);
22
+ // frame length mid (8 bits)
23
+ header[4] = (fullLength >> 3) & 0xFF;
24
+ // frame length low (3 bits), buffer fullness high (5 bits = 0x1F)
25
+ header[5] = ((fullLength & 0x07) << 5) | 0x1F;
26
+ // buffer fullness low (6 bits = 0x3F), number of AAC frames - 1 (2 bits = 0)
27
+ header[6] = 0xFC;
28
+ return header;
29
+ }
30
+ // Sampling frequency index table
31
+ const SAMPLING_FREQ_TABLE = [
32
+ 96000, 88200, 64000, 48000, 44100, 32000,
33
+ 24000, 22050, 16000, 12000, 11025, 8000, 7350,
34
+ ];
35
+ function getSamplingFrequencyIndex(sampleRate) {
36
+ const idx = SAMPLING_FREQ_TABLE.indexOf(sampleRate);
37
+ return idx >= 0 ? idx : 4; // default to 44100
38
+ }
39
+ // ─── Mp4Plugin ───────────────────────────────────────────────────────────────
40
+ export class Mp4Plugin extends BasePlugin {
41
+ canHandle(url) {
42
+ try {
43
+ const path = new URL(url, 'https://dummy').pathname.toLowerCase();
44
+ return (path.endsWith('.m4a') ||
45
+ path.endsWith('.mp4') ||
46
+ path.endsWith('.aac') ||
47
+ path.endsWith('.m4b'));
48
+ }
49
+ catch {
50
+ const lower = url.toLowerCase();
51
+ return lower.endsWith('.m4a') || lower.endsWith('.mp4') || lower.endsWith('.aac') || lower.endsWith('.m4b');
52
+ }
53
+ }
54
+ async getInfo(url) {
55
+ const { info, audioTrack } = await this.loadMoov(url);
56
+ const duration = info.duration / info.timescale;
57
+ return {
58
+ duration,
59
+ sampleRate: audioTrack.audio.sample_rate,
60
+ channels: audioTrack.audio.channel_count,
61
+ bitrate: audioTrack.bitrate,
62
+ codec: audioTrack.codec,
63
+ isVbr: true, // AAC is always VBR conceptually
64
+ encoderDelay: 0,
65
+ };
66
+ }
67
+ async decode(ctx, url, startTime, endTime) {
68
+ // Step 1: Load moov atom only (a few MB) to get metadata + sample table
69
+ const { audioTrack, mp4 } = await this.loadMoovForDecode(url);
70
+ const sampleRate = audioTrack.audio.sample_rate;
71
+ const timescale = audioTrack.timescale;
72
+ // Step 2: Use sample table to find which samples fall in [startTime, endTime]
73
+ const allSamples = mp4.getTrackSamplesInfo(audioTrack.id);
74
+ if (allSamples.length === 0) {
75
+ throw new Error('No audio samples in track');
76
+ }
77
+ const startTS = startTime * timescale;
78
+ const endTS = endTime * timescale;
79
+ let firstIdx = 0;
80
+ let lastIdx = allSamples.length - 1;
81
+ for (let i = 0; i < allSamples.length; i++) {
82
+ if (allSamples[i].cts + allSamples[i].duration > startTS) {
83
+ firstIdx = Math.max(0, i - 1); // one extra for decoder priming
84
+ break;
85
+ }
86
+ }
87
+ for (let i = allSamples.length - 1; i >= 0; i--) {
88
+ if (allSamples[i].cts < endTS) {
89
+ lastIdx = Math.min(allSamples.length - 1, i + 1); // one extra at end
90
+ break;
91
+ }
92
+ }
93
+ const neededSamples = allSamples.slice(firstIdx, lastIdx + 1);
94
+ // Step 3: Compute minimal byte ranges from sample offsets
95
+ // Collapse contiguous/overlapping samples into merged ranges
96
+ const ranges = this.collapseRanges(neededSamples);
97
+ // Step 4: Fetch only the byte ranges containing needed samples
98
+ // We read sample data directly from the fetched bytes — no onSamples needed.
99
+ const rangeData = new Map(); // range.start → data
100
+ for (const range of ranges) {
101
+ const data = await fetchRange(url, range.start, range.end);
102
+ rangeData.set(range.start, data);
103
+ }
104
+ // Step 5: Extract raw AAC frame data for each needed sample
105
+ // by reading directly from fetched ranges using sample offset/size
106
+ const firstSampleInfo = neededSamples[0];
107
+ const aacConfig = firstSampleInfo.description?.aacDecoderConfigDescriptor;
108
+ const audioObjectType = aacConfig?.audioObjectType ?? 2;
109
+ const samplingFreqIndex = aacConfig?.samplingFrequencyIndex ?? getSamplingFrequencyIndex(sampleRate);
110
+ const channelConfig = aacConfig?.channelConfiguration ?? audioTrack.audio.channel_count;
111
+ const adtsChunks = [];
112
+ for (const si of neededSamples) {
113
+ // Find which fetched range contains this sample
114
+ const rawData = this.readSampleFromRanges(si.offset, si.size, ranges, rangeData);
115
+ if (!rawData)
116
+ continue; // skip if data not available (shouldn't happen)
117
+ const adtsHeader = makeAdtsHeader(rawData.length, audioObjectType, samplingFreqIndex, channelConfig);
118
+ const frame = new Uint8Array(adtsHeader.length + rawData.length);
119
+ frame.set(adtsHeader, 0);
120
+ frame.set(rawData, adtsHeader.length);
121
+ adtsChunks.push(frame);
122
+ }
123
+ if (adtsChunks.length === 0) {
124
+ throw new Error('No audio samples could be read from fetched ranges');
125
+ }
126
+ const totalLen = adtsChunks.reduce((acc, c) => acc + c.length, 0);
127
+ const adtsStream = new Uint8Array(totalLen);
128
+ let offset = 0;
129
+ for (const chunk of adtsChunks) {
130
+ adtsStream.set(chunk, offset);
131
+ offset += chunk.length;
132
+ }
133
+ // Step 6: Decode and trim
134
+ const decoded = await ctx.decodeAudioData(adtsStream.buffer.slice(0));
135
+ const decodedRate = decoded.sampleRate;
136
+ const rangeStartTime = neededSamples[0].cts / timescale;
137
+ const trimStartSeconds = startTime - rangeStartTime;
138
+ const trimStartSamples = Math.round(trimStartSeconds * decodedRate);
139
+ const wantedSamples = Math.round((endTime - startTime) * decodedRate);
140
+ const startSample = Math.max(0, trimStartSamples);
141
+ const endSample = Math.min(decoded.length, startSample + wantedSamples);
142
+ return trimBySamples(ctx, decoded, startSample, endSample);
143
+ }
144
+ /**
145
+ * Read sample data from pre-fetched byte ranges.
146
+ */
147
+ readSampleFromRanges(sampleOffset, sampleSize, ranges, rangeData) {
148
+ for (const range of ranges) {
149
+ if (sampleOffset >= range.start && sampleOffset + sampleSize - 1 <= range.end) {
150
+ const data = rangeData.get(range.start);
151
+ if (!data)
152
+ return null;
153
+ const localOffset = sampleOffset - range.start;
154
+ return data.subarray(localOffset, localOffset + sampleSize);
155
+ }
156
+ }
157
+ return null;
158
+ }
159
+ /**
160
+ * Collapse sample byte ranges into minimal contiguous fetch ranges.
161
+ * Adjacent or overlapping ranges are merged to reduce HTTP requests.
162
+ */
163
+ collapseRanges(samples) {
164
+ if (samples.length === 0)
165
+ return [];
166
+ const sorted = [...samples].sort((a, b) => a.offset - b.offset);
167
+ const ranges = [];
168
+ let cur = { start: sorted[0].offset, end: sorted[0].offset + sorted[0].size - 1 };
169
+ for (let i = 1; i < sorted.length; i++) {
170
+ const sampleStart = sorted[i].offset;
171
+ const sampleEnd = sampleStart + sorted[i].size - 1;
172
+ // Merge if gap is < 64KB (cheaper to over-fetch than make another request)
173
+ if (sampleStart <= cur.end + 65536) {
174
+ cur.end = Math.max(cur.end, sampleEnd);
175
+ }
176
+ else {
177
+ ranges.push(cur);
178
+ cur = { start: sampleStart, end: sampleEnd };
179
+ }
180
+ }
181
+ ranges.push(cur);
182
+ return ranges;
183
+ }
184
+ // ─── Internal helpers ────────────────────────────────────────────────────
185
+ async loadMoov(url) {
186
+ const { info, audioTrack } = await this.loadMoovForDecode(url);
187
+ return { info, audioTrack };
188
+ }
189
+ async loadMoovForDecode(url) {
190
+ const MP4Box = MP4BoxLib;
191
+ const mp4 = MP4Box.createFile();
192
+ const fileSize = await fetchContentLength(url);
193
+ const info = await new Promise((resolve, reject) => {
194
+ mp4.onReady = resolve;
195
+ mp4.onError = reject;
196
+ this.streamToMP4Box(mp4, url, fileSize).catch(reject);
197
+ });
198
+ const audioTrack = info.tracks.find((t) => t.type === 'audio');
199
+ if (!audioTrack)
200
+ throw new Error('No audio track found in MP4');
201
+ return { info, audioTrack, mp4 };
202
+ }
203
+ async streamToMP4Box(mp4, url, fileSize) {
204
+ const chunkSize = 256 * 1024;
205
+ const totalSize = fileSize ?? 10 * 1024 * 1024; // fallback 10MB
206
+ let offset = 0;
207
+ while (offset < totalSize) {
208
+ const end = Math.min(offset + chunkSize - 1, totalSize - 1);
209
+ const data = await fetchRange(url, offset, end);
210
+ const ab = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
211
+ ab.fileStart = offset;
212
+ mp4.appendBuffer(ab);
213
+ offset = end + 1;
214
+ // Check if we have enough info - mp4box will call onReady
215
+ // For large files, we may need to stop early once we have what we need
216
+ if (offset > 2 * 1024 * 1024 && offset < totalSize - chunkSize) {
217
+ // For the moov-at-end case, try the tail
218
+ break;
219
+ }
220
+ }
221
+ // If moov is at the end, fetch the tail
222
+ if (fileSize && offset < fileSize) {
223
+ const tailStart = Math.max(offset, fileSize - 2 * 1024 * 1024);
224
+ const tailData = await fetchRange(url, tailStart, fileSize - 1);
225
+ const ab = tailData.buffer.slice(tailData.byteOffset, tailData.byteOffset + tailData.byteLength);
226
+ ab.fileStart = tailStart;
227
+ mp4.appendBuffer(ab);
228
+ }
229
+ mp4.flush();
230
+ }
231
+ }
232
+ //# sourceMappingURL=mp4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mp4.js","sourceRoot":"","sources":["../../src/plugins/mp4.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "audio-snip",
3
+ "version": "0.1.0",
4
+ "description": "Decode audio segments from remote files via HTTP Range requests",
5
+ "license": "MIT",
6
+ "author": "Stepan Mikhailiuk",
7
+ "repository": "stepancar/audio-snip",
8
+ "type": "module",
9
+ "files": ["dist", "LICENSE", "README.md"],
10
+ "exports": {
11
+ ".": {
12
+ "import": "./dist/index.js",
13
+ "types": "./dist/index.d.ts"
14
+ },
15
+ "./mp3": {
16
+ "import": "./dist/plugins/mp3.js",
17
+ "types": "./dist/plugins/mp3.d.ts"
18
+ },
19
+ "./mp4": {
20
+ "import": "./dist/plugins/mp4.js",
21
+ "types": "./dist/plugins/mp4.d.ts"
22
+ }
23
+ },
24
+ "scripts": {
25
+ "build": "tsc -p tsconfig.json",
26
+ "build:demo": "vite build --config demo/vite.config.ts",
27
+ "test": "vitest run",
28
+ "test:watch": "vitest"
29
+ },
30
+ "dependencies": {
31
+ "mp4box": "^0.5.2"
32
+ },
33
+ "devDependencies": {
34
+ "typescript": "^5",
35
+ "vitest": "^2",
36
+ "@vitest/browser": "^2",
37
+ "playwright": "^1",
38
+ "vite": "^5"
39
+ }
40
+ }