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 +21 -0
- package/README.md +68 -0
- package/dist/core.d.ts +25 -0
- package/dist/core.js +73 -0
- package/dist/core.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/plugins/mp3.d.ts +13 -0
- package/dist/plugins/mp3.js +366 -0
- package/dist/plugins/mp3.js.map +1 -0
- package/dist/plugins/mp4.d.ts +18 -0
- package/dist/plugins/mp4.js +232 -0
- package/dist/plugins/mp4.js.map +1 -0
- package/package.json +40 -0
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
|
package/dist/core.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.js","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -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":"AAAA,OAAO,EACL,UAAU,EAEV,UAAU,EACV,kBAAkB,EAClB,aAAa,GACd,MAAM,YAAY,CAAC;AA6EpB,iEAAiE;AACjE,OAAO,aAAa,MAAM,QAAQ,CAAC;AAEnC,MAAM,SAAS,GAAG,aAAwD,CAAC;AAE3E,gFAAgF;AAEhF,SAAS,cAAc,CACrB,WAAmB,EACnB,eAAuB,EACvB,sBAA8B,EAC9B,oBAA4B;IAE5B,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC;IACjC,MAAM,UAAU,GAAG,WAAW,GAAG,CAAC,CAAC;IAEnC,oGAAoG;IACpG,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IACjB,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IACjB,sHAAsH;IACtH,MAAM,CAAC,CAAC,CAAC;QACP,CAAC,CAAC,eAAe,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;YAC5B,CAAC,sBAAsB,IAAI,CAAC,CAAC;YAC7B,CAAC,CAAC,IAAI,CAAC,CAAC;YACR,CAAC,CAAC,oBAAoB,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IACvC,yJAAyJ;IACzJ,MAAM,CAAC,CAAC,CAAC;QACP,CAAC,CAAC,oBAAoB,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;YACpC,CAAC,CAAC,UAAU,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;IAC9B,4BAA4B;IAC5B,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC;IACrC,kEAAkE;IAClE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC;IAC9C,6EAA6E;IAC7E,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAEjB,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,iCAAiC;AACjC,MAAM,mBAAmB,GAAG;IAC1B,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK;IACxC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI;CAC9C,CAAC;AAEF,SAAS,yBAAyB,CAAC,UAAkB;IACnD,MAAM,GAAG,GAAG,mBAAmB,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACpD,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,mBAAmB;AAChD,CAAC;AAED,gFAAgF;AAEhF,MAAM,OAAO,SAAU,SAAQ,UAAU;IACvC,SAAS,CAAC,GAAW;QACnB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;YAClE,OAAO,CACL,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;gBACrB,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;gBACrB,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;gBACrB,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CACtB,CAAC;QACJ,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,KAAK,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;YAChC,OAAO,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC9G,CAAC;IACH,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,GAAW;QACvB,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QACtD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC;QAEhD,OAAO;YACL,QAAQ;YACR,UAAU,EAAE,UAAU,CAAC,KAAM,CAAC,WAAW;YACzC,QAAQ,EAAE,UAAU,CAAC,KAAM,CAAC,aAAa;YACzC,OAAO,EAAE,UAAU,CAAC,OAAO;YAC3B,KAAK,EAAE,UAAU,CAAC,KAAK;YACvB,KAAK,EAAE,IAAI,EAAE,iCAAiC;YAC9C,YAAY,EAAE,CAAC;SAChB,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,MAAM,CACV,GAAqB,EACrB,GAAW,EACX,SAAiB,EACjB,OAAe;QAEf,wEAAwE;QACxE,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC;QAE9D,MAAM,UAAU,GAAG,UAAU,CAAC,KAAM,CAAC,WAAW,CAAC;QACjD,MAAM,SAAS,GAAG,UAAU,CAAC,SAAS,CAAC;QAEvC,8EAA8E;QAC9E,MAAM,UAAU,GAAG,GAAG,CAAC,mBAAmB,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QAC1D,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;QAC/C,CAAC;QAED,MAAM,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;QACtC,MAAM,KAAK,GAAG,OAAO,GAAG,SAAS,CAAC;QAElC,IAAI,QAAQ,GAAG,CAAC,CAAC;QACjB,IAAI,OAAO,GAAG,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC;QAEpC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC3C,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,QAAQ,GAAG,OAAO,EAAE,CAAC;gBACzD,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,gCAAgC;gBAC/D,MAAM;YACR,CAAC;QACH,CAAC;QACD,KAAK,IAAI,CAAC,GAAG,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAChD,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,KAAK,EAAE,CAAC;gBAC9B,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,mBAAmB;gBACrE,MAAM;YACR,CAAC;QACH,CAAC;QAED,MAAM,aAAa,GAAG,UAAU,CAAC,KAAK,CAAC,QAAQ,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC;QAE9D,0DAA0D;QAC1D,6DAA6D;QAC7D,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC;QAElD,+DAA+D;QAC/D,6EAA6E;QAC7E,MAAM,SAAS,GAAG,IAAI,GAAG,EAAsB,CAAC,CAAC,qBAAqB;QACtE,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;YAC3D,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QACnC,CAAC;QAED,4DAA4D;QAC5D,mEAAmE;QACnE,MAAM,eAAe,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;QACzC,MAAM,SAAS,GAAG,eAAe,CAAC,WAAW,EAAE,0BAA0B,CAAC;QAC1E,MAAM,eAAe,GAAG,SAAS,EAAE,eAAe,IAAI,CAAC,CAAC;QACxD,MAAM,iBAAiB,GAAG,SAAS,EAAE,sBAAsB,IAAI,yBAAyB,CAAC,UAAU,CAAC,CAAC;QACrG,MAAM,aAAa,GAAG,SAAS,EAAE,oBAAoB,IAAI,UAAU,CAAC,KAAM,CAAC,aAAa,CAAC;QAEzF,MAAM,UAAU,GAAiB,EAAE,CAAC;QACpC,KAAK,MAAM,EAAE,IAAI,aAAa,EAAE,CAAC;YAC/B,gDAAgD;YAChD,MAAM,OAAO,GAAG,IAAI,CAAC,oBAAoB,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;YACjF,IAAI,CAAC,OAAO;gBAAE,SAAS,CAAC,gDAAgD;YAExE,MAAM,UAAU,GAAG,cAAc,CAC/B,OAAO,CAAC,MAAM,EACd,eAAe,EACf,iBAAiB,EACjB,aAAa,CACd,CAAC;YACF,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,UAAU,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;YACjE,KAAK,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;YACzB,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;YACtC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzB,CAAC;QAED,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;QACxE,CAAC;QAED,MAAM,QAAQ,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAClE,MAAM,UAAU,GAAG,IAAI,UAAU,CAAC,QAAQ,CAAC,CAAC;QAC5C,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,KAAK,MAAM,KAAK,IAAI,UAAU,EAAE,CAAC;YAC/B,UAAU,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;YAC9B,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC;QACzB,CAAC;QAED,0BAA0B;QAC1B,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,eAAe,CAAC,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAgB,CAAC,CAAC;QAErF,MAAM,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;QACvC,MAAM,cAAc,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,SAAS,CAAC;QACxD,MAAM,gBAAgB,GAAG,SAAS,GAAG,cAAc,CAAC;QACpD,MAAM,gBAAgB,GAAG,IAAI,CAAC,KAAK,CAAC,gBAAgB,GAAG,WAAW,CAAC,CAAC;QACpE,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,SAAS,CAAC,GAAG,WAAW,CAAC,CAAC;QAEtE,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC;QAClD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,WAAW,GAAG,aAAa,CAAC,CAAC;QAExE,OAAO,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,WAAW,EAAE,SAAS,CAAC,CAAC;IAC7D,CAAC;IAED;;OAEG;IACK,oBAAoB,CAC1B,YAAoB,EACpB,UAAkB,EAClB,MAAwC,EACxC,SAAkC;QAElC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,IAAI,YAAY,IAAI,KAAK,CAAC,KAAK,IAAI,YAAY,GAAG,UAAU,GAAG,CAAC,IAAI,KAAK,CAAC,GAAG,EAAE,CAAC;gBAC9E,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;gBACxC,IAAI,CAAC,IAAI;oBAAE,OAAO,IAAI,CAAC;gBACvB,MAAM,WAAW,GAAG,YAAY,GAAG,KAAK,CAAC,KAAK,CAAC;gBAC/C,OAAO,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,WAAW,GAAG,UAAU,CAAC,CAAC;YAC9D,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;OAGG;IACK,cAAc,CACpB,OAAwB;QAExB,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAEpC,MAAM,MAAM,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC;QAChE,MAAM,MAAM,GAAqC,EAAE,CAAC;QACpD,IAAI,GAAG,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;QAElF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACvC,MAAM,WAAW,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;YACrC,MAAM,SAAS,GAAG,WAAW,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC;YACnD,2EAA2E;YAC3E,IAAI,WAAW,IAAI,GAAG,CAAC,GAAG,GAAG,KAAK,EAAE,CAAC;gBACnC,GAAG,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;YACzC,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBACjB,GAAG,GAAG,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC;YAC/C,CAAC;QACH,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACjB,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,4EAA4E;IAEpE,KAAK,CAAC,QAAQ,CAAC,GAAW;QAChC,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC;QAC/D,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;IAC9B,CAAC;IAEO,KAAK,CAAC,iBAAiB,CAAC,GAAW;QACzC,MAAM,MAAM,GAAG,SAAS,CAAC;QACzB,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;QAChC,MAAM,QAAQ,GAAG,MAAM,kBAAkB,CAAC,GAAG,CAAC,CAAC;QAE/C,MAAM,IAAI,GAAG,MAAM,IAAI,OAAO,CAAU,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC1D,GAAG,CAAC,OAAO,GAAG,OAAO,CAAC;YACtB,GAAG,CAAC,OAAO,GAAG,MAAM,CAAC;YACrB,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;QAEH,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC;QAC/D,IAAI,CAAC,UAAU;YAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;QAEhE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC;IACnC,CAAC;IAEO,KAAK,CAAC,cAAc,CAC1B,GAAe,EACf,GAAW,EACX,QAAuB;QAEvB,MAAM,SAAS,GAAG,GAAG,GAAG,IAAI,CAAC;QAC7B,MAAM,SAAS,GAAG,QAAQ,IAAI,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,gBAAgB;QAChE,IAAI,MAAM,GAAG,CAAC,CAAC;QAEf,OAAO,MAAM,GAAG,SAAS,EAAE,CAAC;YAC1B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,SAAS,GAAG,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,CAAC;YAC5D,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC;YAChD,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAC1B,IAAI,CAAC,UAAU,EACf,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CACM,CAAC;YAC1C,EAAE,CAAC,SAAS,GAAG,MAAM,CAAC;YACtB,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;YAErB,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC;YAEjB,0DAA0D;YAC1D,uEAAuE;YACvE,IAAI,MAAM,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,IAAI,MAAM,GAAG,SAAS,GAAG,SAAS,EAAE,CAAC;gBAC/D,yCAAyC;gBACzC,MAAM;YACR,CAAC;QACH,CAAC;QAED,wCAAwC;QACxC,IAAI,QAAQ,IAAI,MAAM,GAAG,QAAQ,EAAE,CAAC;YAClC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC;YAC/D,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,GAAG,EAAE,SAAS,EAAE,QAAQ,GAAG,CAAC,CAAC,CAAC;YAChE,MAAM,EAAE,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,CAC9B,QAAQ,CAAC,UAAU,EACnB,QAAQ,CAAC,UAAU,GAAG,QAAQ,CAAC,UAAU,CACF,CAAC;YAC1C,EAAE,CAAC,SAAS,GAAG,SAAS,CAAC;YACzB,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;QACvB,CAAC;QAED,GAAG,CAAC,KAAK,EAAE,CAAC;IACd,CAAC;CAEF"}
|
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
|
+
}
|