@meframe/core 0.0.31 → 0.0.32
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/dist/Meframe.d.ts +2 -2
- package/dist/Meframe.d.ts.map +1 -1
- package/dist/Meframe.js +3 -2
- package/dist/Meframe.js.map +1 -1
- package/dist/cache/CacheManager.d.ts +12 -17
- package/dist/cache/CacheManager.d.ts.map +1 -1
- package/dist/cache/CacheManager.js +18 -280
- package/dist/cache/CacheManager.js.map +1 -1
- package/dist/cache/l1/AudioL1Cache.d.ts +36 -19
- package/dist/cache/l1/AudioL1Cache.d.ts.map +1 -1
- package/dist/cache/l1/AudioL1Cache.js +182 -282
- package/dist/cache/l1/AudioL1Cache.js.map +1 -1
- package/dist/controllers/PlaybackController.d.ts +4 -2
- package/dist/controllers/PlaybackController.d.ts.map +1 -1
- package/dist/controllers/PlaybackController.js +43 -13
- package/dist/controllers/PlaybackController.js.map +1 -1
- package/dist/model/types.d.ts +0 -4
- package/dist/model/types.d.ts.map +1 -1
- package/dist/model/types.js.map +1 -1
- package/dist/orchestrator/ExportScheduler.d.ts +6 -0
- package/dist/orchestrator/ExportScheduler.d.ts.map +1 -1
- package/dist/orchestrator/ExportScheduler.js +45 -66
- package/dist/orchestrator/ExportScheduler.js.map +1 -1
- package/dist/orchestrator/GlobalAudioSession.d.ts +35 -28
- package/dist/orchestrator/GlobalAudioSession.d.ts.map +1 -1
- package/dist/orchestrator/GlobalAudioSession.js +212 -421
- package/dist/orchestrator/GlobalAudioSession.js.map +1 -1
- package/dist/orchestrator/OnDemandVideoSession.d.ts +3 -3
- package/dist/orchestrator/OnDemandVideoSession.d.ts.map +1 -1
- package/dist/orchestrator/OnDemandVideoSession.js +4 -4
- package/dist/orchestrator/OnDemandVideoSession.js.map +1 -1
- package/dist/orchestrator/Orchestrator.d.ts +1 -2
- package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
- package/dist/orchestrator/Orchestrator.js +34 -48
- package/dist/orchestrator/Orchestrator.js.map +1 -1
- package/dist/orchestrator/VideoClipSession.d.ts +0 -2
- package/dist/orchestrator/VideoClipSession.d.ts.map +1 -1
- package/dist/orchestrator/VideoClipSession.js +0 -49
- package/dist/orchestrator/VideoClipSession.js.map +1 -1
- package/dist/stages/compose/OfflineAudioMixer.d.ts.map +1 -1
- package/dist/stages/compose/OfflineAudioMixer.js +13 -18
- package/dist/stages/compose/OfflineAudioMixer.js.map +1 -1
- package/dist/stages/decode/AudioChunkDecoder.js +169 -0
- package/dist/stages/decode/AudioChunkDecoder.js.map +1 -0
- package/dist/stages/demux/MP3FrameParser.js +186 -0
- package/dist/stages/demux/MP3FrameParser.js.map +1 -0
- package/dist/stages/load/ResourceLoader.d.ts +20 -9
- package/dist/stages/load/ResourceLoader.d.ts.map +1 -1
- package/dist/stages/load/ResourceLoader.js +92 -135
- package/dist/stages/load/ResourceLoader.js.map +1 -1
- package/dist/stages/load/types.d.ts +1 -0
- package/dist/stages/load/types.d.ts.map +1 -1
- package/dist/utils/audio-data.d.ts +16 -0
- package/dist/utils/audio-data.d.ts.map +1 -0
- package/dist/utils/audio-data.js +111 -0
- package/dist/utils/audio-data.js.map +1 -0
- package/package.json +1 -1
- package/dist/cache/resource/ImageBitmapCache.d.ts +0 -65
- package/dist/cache/resource/ImageBitmapCache.d.ts.map +0 -1
- package/dist/cache/resource/ImageBitmapCache.js +0 -101
- package/dist/cache/resource/ImageBitmapCache.js.map +0 -1
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { BaseDecoder } from "./BaseDecoder.js";
|
|
2
|
+
class AudioChunkDecoder extends BaseDecoder {
|
|
3
|
+
// Default values
|
|
4
|
+
static DEFAULT_HIGH_WATER_MARK = 20;
|
|
5
|
+
static DEFAULT_DECODE_QUEUE_THRESHOLD = 16;
|
|
6
|
+
// Exposed properties
|
|
7
|
+
trackId;
|
|
8
|
+
// Backpressure configuration
|
|
9
|
+
highWaterMark;
|
|
10
|
+
decodeQueueThreshold;
|
|
11
|
+
// Buffering support for delayed configuration
|
|
12
|
+
bufferedChunks = [];
|
|
13
|
+
isProcessingBuffer = false;
|
|
14
|
+
constructor(trackId, config) {
|
|
15
|
+
super(config);
|
|
16
|
+
this.trackId = trackId;
|
|
17
|
+
this.highWaterMark = config?.backpressure?.highWaterMark ?? AudioChunkDecoder.DEFAULT_HIGH_WATER_MARK;
|
|
18
|
+
this.decodeQueueThreshold = AudioChunkDecoder.DEFAULT_DECODE_QUEUE_THRESHOLD;
|
|
19
|
+
}
|
|
20
|
+
// Computed properties
|
|
21
|
+
get isConfigured() {
|
|
22
|
+
return this.isReady;
|
|
23
|
+
}
|
|
24
|
+
get state() {
|
|
25
|
+
return this.decoder?.state || "unconfigured";
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Update configuration - can be called before or after initialization
|
|
29
|
+
*/
|
|
30
|
+
async updateConfig(config) {
|
|
31
|
+
if (!this.isReady && config.codec) {
|
|
32
|
+
await this.configure(config);
|
|
33
|
+
await this.processBufferedChunks();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// Override createStream to handle buffering
|
|
38
|
+
createStream() {
|
|
39
|
+
return new TransformStream(
|
|
40
|
+
{
|
|
41
|
+
start: async (controller) => {
|
|
42
|
+
this.controller = controller;
|
|
43
|
+
if (this.config?.codec && !this.isReady) {
|
|
44
|
+
await this.initialize();
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
transform: async (chunk) => {
|
|
48
|
+
if (!this.isReady) {
|
|
49
|
+
this.bufferedChunks.push(chunk);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (this.isProcessingBuffer) {
|
|
53
|
+
this.bufferedChunks.push(chunk);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
await this.processChunk(chunk);
|
|
57
|
+
},
|
|
58
|
+
flush: async () => {
|
|
59
|
+
if (this.isReady) {
|
|
60
|
+
await this.flush();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
highWaterMark: this.highWaterMark,
|
|
66
|
+
size: () => 1
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Process a single chunk (extracted from transform for reuse)
|
|
72
|
+
*/
|
|
73
|
+
async processChunk(chunk) {
|
|
74
|
+
if (!this.decoder) {
|
|
75
|
+
throw new Error("Decoder not initialized");
|
|
76
|
+
}
|
|
77
|
+
if (this.decoder.state !== "configured") {
|
|
78
|
+
console.error("[AudioChunkDecoder] Decoder in unexpected state:", this.decoder.state);
|
|
79
|
+
throw new Error(`Decoder not configured, state: ${this.decoder.state}`);
|
|
80
|
+
}
|
|
81
|
+
if (this.decoder.decodeQueueSize >= this.decodeQueueThreshold) {
|
|
82
|
+
await new Promise((resolve) => {
|
|
83
|
+
const check = () => {
|
|
84
|
+
if (!this.decoder || this.decoder.decodeQueueSize < this.decodeQueueThreshold - 1) {
|
|
85
|
+
resolve();
|
|
86
|
+
} else {
|
|
87
|
+
setTimeout(check, 20);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
check();
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
this.decode(chunk);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.error(`[AudioChunkDecoder] decode error:`, error);
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Implement abstract methods
|
|
101
|
+
async isConfigSupported(config) {
|
|
102
|
+
const result = await AudioDecoder.isConfigSupported({
|
|
103
|
+
codec: config.codec,
|
|
104
|
+
sampleRate: config.sampleRate,
|
|
105
|
+
numberOfChannels: config.numberOfChannels
|
|
106
|
+
});
|
|
107
|
+
return { supported: result.supported ?? false };
|
|
108
|
+
}
|
|
109
|
+
createDecoder(init) {
|
|
110
|
+
return new AudioDecoder(init);
|
|
111
|
+
}
|
|
112
|
+
getDecoderType() {
|
|
113
|
+
return "Audio";
|
|
114
|
+
}
|
|
115
|
+
async configureDecoder(config) {
|
|
116
|
+
if (!this.decoder) return;
|
|
117
|
+
await this.decoder.configure({
|
|
118
|
+
codec: config.codec,
|
|
119
|
+
sampleRate: config.sampleRate,
|
|
120
|
+
numberOfChannels: config.numberOfChannels,
|
|
121
|
+
...config.description && { description: config.description }
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
decode(chunk) {
|
|
125
|
+
this.decoder?.decode(chunk);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Configure the decoder with codec info (can be called after creation)
|
|
129
|
+
*/
|
|
130
|
+
async configure(config) {
|
|
131
|
+
if (this.isReady) {
|
|
132
|
+
await this.reconfigure(config);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
this.config = config;
|
|
136
|
+
await this.initialize();
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Process any buffered chunks after configuration
|
|
140
|
+
*/
|
|
141
|
+
async processBufferedChunks() {
|
|
142
|
+
if (!this.isReady || this.bufferedChunks.length === 0) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
this.isProcessingBuffer = true;
|
|
146
|
+
const chunks = [...this.bufferedChunks];
|
|
147
|
+
this.bufferedChunks = [];
|
|
148
|
+
for (const chunk of chunks) {
|
|
149
|
+
try {
|
|
150
|
+
await this.processChunk(chunk);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
console.error("[AudioChunkDecoder] Error processing buffered chunk:", error);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
this.isProcessingBuffer = false;
|
|
156
|
+
if (this.bufferedChunks.length > 0) {
|
|
157
|
+
await this.processBufferedChunks();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// Override close to clean up buffered chunks
|
|
161
|
+
async close() {
|
|
162
|
+
this.bufferedChunks = [];
|
|
163
|
+
await super.close();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
export {
|
|
167
|
+
AudioChunkDecoder
|
|
168
|
+
};
|
|
169
|
+
//# sourceMappingURL=AudioChunkDecoder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AudioChunkDecoder.js","sources":["../../../src/stages/decode/AudioChunkDecoder.ts"],"sourcesContent":["import { AudioDecoderConfig } from './types';\nimport { BaseDecoder } from './BaseDecoder';\n\n/**\n * Audio decoder with streaming support\n * Extends BaseDecoder for common WebCodecs operations\n */\nexport class AudioChunkDecoder extends BaseDecoder<\n AudioDecoder,\n AudioDecoderConfig,\n EncodedAudioChunk,\n AudioData\n> {\n // Default values\n private static readonly DEFAULT_HIGH_WATER_MARK = 20;\n private static readonly DEFAULT_DECODE_QUEUE_THRESHOLD = 16;\n\n // Exposed properties\n readonly trackId: string;\n\n // Backpressure configuration\n protected readonly highWaterMark: number;\n protected readonly decodeQueueThreshold: number;\n\n // Buffering support for delayed configuration\n private bufferedChunks: EncodedAudioChunk[] = [];\n private isProcessingBuffer: boolean = false;\n\n constructor(trackId: string, config?: Partial<AudioDecoderConfig>) {\n // Initialize with empty config, will be configured later\n super(config as AudioDecoderConfig);\n\n this.trackId = trackId;\n\n // Set backpressure configuration\n this.highWaterMark =\n config?.backpressure?.highWaterMark ?? AudioChunkDecoder.DEFAULT_HIGH_WATER_MARK;\n this.decodeQueueThreshold = AudioChunkDecoder.DEFAULT_DECODE_QUEUE_THRESHOLD;\n }\n\n // Computed properties\n get isConfigured(): boolean {\n return this.isReady;\n }\n\n get state(): string {\n return this.decoder?.state || 'unconfigured';\n }\n\n /**\n * Update configuration - can be called before or after initialization\n */\n async updateConfig(config: Partial<AudioDecoderConfig>): Promise<void> {\n // If decoder is not ready and we have codec info, configure it\n if (!this.isReady && config.codec) {\n await this.configure(config as AudioDecoderConfig);\n await this.processBufferedChunks();\n return;\n }\n\n // Note: AudioDecoder doesn't have many runtime-configurable options\n // Backpressure settings are readonly in this implementation\n // if (config.backpressure) {\n // console.warn('Backpressure settings cannot be changed at runtime');\n // }\n }\n\n // Override createStream to handle buffering\n override createStream(): TransformStream<EncodedAudioChunk, AudioData> {\n return new TransformStream<EncodedAudioChunk, AudioData>(\n {\n start: async (controller) => {\n this.controller = controller;\n // Don't initialize if no codec config yet\n if (this.config?.codec && !this.isReady) {\n await this.initialize();\n }\n },\n\n transform: async (chunk) => {\n // If not configured yet, buffer the chunk\n if (!this.isReady) {\n this.bufferedChunks.push(chunk);\n return; // Don't process yet\n }\n\n // If we're processing buffered chunks, add to buffer to maintain order\n if (this.isProcessingBuffer) {\n this.bufferedChunks.push(chunk);\n return;\n }\n\n // Normal processing\n await this.processChunk(chunk);\n },\n\n flush: async () => {\n if (this.isReady) {\n await this.flush();\n }\n },\n },\n {\n highWaterMark: this.highWaterMark,\n size: () => 1,\n }\n );\n }\n\n /**\n * Process a single chunk (extracted from transform for reuse)\n */\n private async processChunk(chunk: EncodedAudioChunk): Promise<void> {\n if (!this.decoder) {\n throw new Error('Decoder not initialized');\n }\n\n if (this.decoder.state !== 'configured') {\n console.error('[AudioChunkDecoder] Decoder in unexpected state:', this.decoder.state);\n throw new Error(`Decoder not configured, state: ${this.decoder.state}`);\n }\n\n // Check decoder queue pressure\n if (this.decoder.decodeQueueSize >= this.decodeQueueThreshold) {\n await new Promise<void>((resolve) => {\n const check = () => {\n if (!this.decoder || this.decoder.decodeQueueSize < this.decodeQueueThreshold - 1) {\n resolve();\n } else {\n setTimeout(check, 20);\n }\n };\n check();\n });\n }\n\n // Decode the chunk\n try {\n this.decode(chunk);\n } catch (error) {\n console.error(`[AudioChunkDecoder] decode error:`, error);\n throw error; // Re-throw to propagate\n }\n }\n\n // Implement abstract methods\n protected async isConfigSupported(config: AudioDecoderConfig): Promise<{ supported: boolean }> {\n const result = await AudioDecoder.isConfigSupported({\n codec: config.codec,\n sampleRate: config.sampleRate,\n numberOfChannels: config.numberOfChannels,\n });\n return { supported: result.supported ?? false };\n }\n\n protected createDecoder(init: {\n output: (data: AudioData) => void;\n error: (error: DOMException) => void;\n }): AudioDecoder {\n return new AudioDecoder(init);\n }\n\n protected getDecoderType(): string {\n return 'Audio';\n }\n\n protected async configureDecoder(config: AudioDecoderConfig): Promise<void> {\n if (!this.decoder) return;\n\n await this.decoder.configure({\n codec: config.codec,\n sampleRate: config.sampleRate,\n numberOfChannels: config.numberOfChannels,\n ...(config.description && { description: config.description }),\n });\n }\n\n protected decode(chunk: EncodedAudioChunk): void {\n this.decoder?.decode(chunk);\n }\n\n /**\n * Configure the decoder with codec info (can be called after creation)\n */\n async configure(config: AudioDecoderConfig): Promise<void> {\n if (this.isReady) {\n // If already configured, reconfigure\n await this.reconfigure(config);\n return;\n }\n\n this.config = config as any;\n\n // Initialize decoder with new config\n await this.initialize();\n }\n\n /**\n * Process any buffered chunks after configuration\n */\n async processBufferedChunks(): Promise<void> {\n if (!this.isReady || this.bufferedChunks.length === 0) {\n return;\n }\n\n this.isProcessingBuffer = true;\n\n // Process buffered chunks in order\n const chunks = [...this.bufferedChunks];\n this.bufferedChunks = [];\n\n for (const chunk of chunks) {\n try {\n await this.processChunk(chunk);\n } catch (error) {\n console.error('[AudioChunkDecoder] Error processing buffered chunk:', error);\n }\n }\n\n this.isProcessingBuffer = false;\n\n // Process any new chunks that arrived while processing buffer\n if (this.bufferedChunks.length > 0) {\n await this.processBufferedChunks();\n }\n }\n\n // Override close to clean up buffered chunks\n override async close(): Promise<void> {\n // Clear buffered chunks\n this.bufferedChunks = [];\n\n // Call parent close\n await super.close();\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
const BITRATE_TABLE = {
|
|
2
|
+
"1-3": [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0],
|
|
3
|
+
"1-2": [0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0],
|
|
4
|
+
"1-1": [0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0],
|
|
5
|
+
"2-3": [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0],
|
|
6
|
+
"2-2": [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0],
|
|
7
|
+
"2-1": [0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0]
|
|
8
|
+
};
|
|
9
|
+
const SAMPLE_RATE_TABLE = {
|
|
10
|
+
1: [44100, 48e3, 32e3],
|
|
11
|
+
2: [22050, 24e3, 16e3],
|
|
12
|
+
2.5: [11025, 12e3, 8e3]
|
|
13
|
+
};
|
|
14
|
+
class MP3FrameParser {
|
|
15
|
+
buffer = new Uint8Array(0);
|
|
16
|
+
config = null;
|
|
17
|
+
timestampUs = 0;
|
|
18
|
+
push(chunk) {
|
|
19
|
+
this.appendToBuffer(chunk);
|
|
20
|
+
this.skipId3Headers();
|
|
21
|
+
const frames = [];
|
|
22
|
+
let configEmitted;
|
|
23
|
+
let offset = 0;
|
|
24
|
+
while (offset <= this.buffer.length - 4) {
|
|
25
|
+
const header = this.parseHeader(this.buffer, offset);
|
|
26
|
+
if (!header) {
|
|
27
|
+
offset += 1;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (offset + header.frameSize > this.buffer.length) {
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
const frameBytes = this.buffer.slice(offset, offset + header.frameSize);
|
|
34
|
+
const durationUs = Math.round(header.samplesPerFrame * 1e6 / header.sampleRate);
|
|
35
|
+
if (!this.config) {
|
|
36
|
+
this.config = {
|
|
37
|
+
codec: "mp3",
|
|
38
|
+
sampleRate: header.sampleRate,
|
|
39
|
+
channels: header.channels,
|
|
40
|
+
bitrateKbps: header.bitrateKbps,
|
|
41
|
+
samplesPerFrame: header.samplesPerFrame
|
|
42
|
+
};
|
|
43
|
+
configEmitted = this.config;
|
|
44
|
+
}
|
|
45
|
+
frames.push({
|
|
46
|
+
data: frameBytes,
|
|
47
|
+
timestampUs: this.timestampUs,
|
|
48
|
+
durationUs
|
|
49
|
+
});
|
|
50
|
+
this.timestampUs += durationUs;
|
|
51
|
+
offset += header.frameSize;
|
|
52
|
+
}
|
|
53
|
+
if (offset > 0) {
|
|
54
|
+
this.buffer = this.buffer.slice(offset);
|
|
55
|
+
}
|
|
56
|
+
return { frames, config: configEmitted };
|
|
57
|
+
}
|
|
58
|
+
flush() {
|
|
59
|
+
const { frames } = this.push(new Uint8Array(0));
|
|
60
|
+
const remainder = this.buffer;
|
|
61
|
+
this.buffer = new Uint8Array(0);
|
|
62
|
+
if (remainder.length) {
|
|
63
|
+
console.warn("[MP3FrameParser] Remaining unparsed bytes:", remainder.length);
|
|
64
|
+
}
|
|
65
|
+
return frames;
|
|
66
|
+
}
|
|
67
|
+
appendToBuffer(chunk) {
|
|
68
|
+
if (chunk.length === 0) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const combined = new Uint8Array(this.buffer.length + chunk.length);
|
|
72
|
+
combined.set(this.buffer, 0);
|
|
73
|
+
combined.set(chunk, this.buffer.length);
|
|
74
|
+
this.buffer = combined;
|
|
75
|
+
}
|
|
76
|
+
skipId3Headers() {
|
|
77
|
+
let offset = 0;
|
|
78
|
+
while (this.buffer.length - offset >= 10) {
|
|
79
|
+
if (this.buffer[offset] === 73 && this.buffer[offset + 1] === 68 && this.buffer[offset + 2] === 51) {
|
|
80
|
+
const size = this.readSynchsafeInteger(this.buffer.subarray(offset + 6, offset + 10));
|
|
81
|
+
const total = size + 10;
|
|
82
|
+
if (offset + total <= this.buffer.length) {
|
|
83
|
+
offset += total;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
if (offset > 0) {
|
|
90
|
+
this.buffer = this.buffer.slice(offset);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
parseHeader(buffer, offset) {
|
|
94
|
+
if (offset + 3 >= buffer.length) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
if (buffer[offset] !== 255 || ((buffer[offset + 1] ?? 0) & 224) !== 224) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
const versionBits = (buffer[offset + 1] ?? 0) >> 3 & 3;
|
|
101
|
+
const layerBits = (buffer[offset + 1] ?? 0) >> 1 & 3;
|
|
102
|
+
const bitrateIndex = (buffer[offset + 2] ?? 0) >> 4 & 15;
|
|
103
|
+
const sampleRateIndex = (buffer[offset + 2] ?? 0) >> 2 & 3;
|
|
104
|
+
const paddingBit = (buffer[offset + 2] ?? 0) >> 1 & 1;
|
|
105
|
+
const channelMode = (buffer[offset + 3] ?? 0) >> 6 & 3;
|
|
106
|
+
const version = this.getVersion(versionBits);
|
|
107
|
+
const layer = this.getLayer(layerBits);
|
|
108
|
+
if (!version || !layer) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
const sampleRate = SAMPLE_RATE_TABLE[version]?.[sampleRateIndex] ?? null;
|
|
112
|
+
if (!sampleRate) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
const bitrateKey = `${version === 1 ? 1 : 2}-${layer}`;
|
|
116
|
+
const bitrateKbps = BITRATE_TABLE[bitrateKey]?.[bitrateIndex] ?? null;
|
|
117
|
+
if (bitrateKbps === null) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
const samplesPerFrame = this.getSamplesPerFrame(version, layer);
|
|
121
|
+
const frameSize = this.calculateFrameSize(layer, bitrateKbps, sampleRate, paddingBit);
|
|
122
|
+
if (!frameSize || frameSize < 24) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
const channels = channelMode === 3 ? 1 : 2;
|
|
126
|
+
return {
|
|
127
|
+
frameSize,
|
|
128
|
+
sampleRate,
|
|
129
|
+
channels,
|
|
130
|
+
bitrateKbps: bitrateKbps || null,
|
|
131
|
+
samplesPerFrame
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
getVersion(bits) {
|
|
135
|
+
switch (bits) {
|
|
136
|
+
case 3:
|
|
137
|
+
return 1;
|
|
138
|
+
case 2:
|
|
139
|
+
return 2;
|
|
140
|
+
case 0:
|
|
141
|
+
return 2.5;
|
|
142
|
+
default:
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
getLayer(bits) {
|
|
147
|
+
switch (bits) {
|
|
148
|
+
case 3:
|
|
149
|
+
return 1;
|
|
150
|
+
case 2:
|
|
151
|
+
return 2;
|
|
152
|
+
case 1:
|
|
153
|
+
return 3;
|
|
154
|
+
default:
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
getSamplesPerFrame(version, layer) {
|
|
159
|
+
if (layer === 1) {
|
|
160
|
+
return 384;
|
|
161
|
+
}
|
|
162
|
+
if (layer === 2) {
|
|
163
|
+
return 1152;
|
|
164
|
+
}
|
|
165
|
+
return version === 1 ? 1152 : 576;
|
|
166
|
+
}
|
|
167
|
+
calculateFrameSize(layer, bitrateKbps, sampleRate, padding) {
|
|
168
|
+
if (bitrateKbps <= 0) {
|
|
169
|
+
return 0;
|
|
170
|
+
}
|
|
171
|
+
if (layer === 1) {
|
|
172
|
+
return (12 * bitrateKbps * 1e3 / sampleRate + padding) * 4;
|
|
173
|
+
}
|
|
174
|
+
return Math.floor(144 * bitrateKbps * 1e3 / sampleRate + padding);
|
|
175
|
+
}
|
|
176
|
+
readSynchsafeInteger(bytes) {
|
|
177
|
+
if (bytes.length !== 4) {
|
|
178
|
+
return 0;
|
|
179
|
+
}
|
|
180
|
+
return ((bytes[0] ?? 0) & 127) << 21 | ((bytes[1] ?? 0) & 127) << 14 | ((bytes[2] ?? 0) & 127) << 7 | (bytes[3] ?? 0) & 127;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
export {
|
|
184
|
+
MP3FrameParser
|
|
185
|
+
};
|
|
186
|
+
//# sourceMappingURL=MP3FrameParser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MP3FrameParser.js","sources":["../../../src/stages/demux/MP3FrameParser.ts"],"sourcesContent":["const BITRATE_TABLE: Record<string, number[]> = {\n '1-3': [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0],\n '1-2': [0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0],\n '1-1': [0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0],\n '2-3': [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0],\n '2-2': [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0],\n '2-1': [0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0],\n};\n\nconst SAMPLE_RATE_TABLE: Record<number, number[]> = {\n 1: [44100, 48000, 32000],\n 2: [22050, 24000, 16000],\n 2.5: [11025, 12000, 8000],\n};\n\nexport interface MP3Config {\n codec: 'mp3';\n sampleRate: number;\n channels: number;\n bitrateKbps: number | null;\n samplesPerFrame: number;\n}\n\nexport interface MP3Frame {\n data: Uint8Array;\n timestampUs: number;\n durationUs: number;\n}\n\ninterface ParseResult {\n frames: MP3Frame[];\n config?: MP3Config;\n}\n\nexport class MP3FrameParser {\n private buffer = new Uint8Array(0);\n private config: MP3Config | null = null;\n private timestampUs = 0;\n\n push(chunk: Uint8Array): ParseResult {\n this.appendToBuffer(chunk);\n this.skipId3Headers();\n\n const frames: MP3Frame[] = [];\n let configEmitted: MP3Config | undefined;\n\n let offset = 0;\n while (offset <= this.buffer.length - 4) {\n const header = this.parseHeader(this.buffer, offset);\n if (!header) {\n offset += 1;\n continue;\n }\n\n if (offset + header.frameSize > this.buffer.length) {\n break;\n }\n\n const frameBytes = this.buffer.slice(offset, offset + header.frameSize);\n const durationUs = Math.round((header.samplesPerFrame * 1_000_000) / header.sampleRate);\n\n if (!this.config) {\n this.config = {\n codec: 'mp3',\n sampleRate: header.sampleRate,\n channels: header.channels,\n bitrateKbps: header.bitrateKbps,\n samplesPerFrame: header.samplesPerFrame,\n };\n configEmitted = this.config;\n }\n\n frames.push({\n data: frameBytes,\n timestampUs: this.timestampUs,\n durationUs,\n });\n\n this.timestampUs += durationUs;\n offset += header.frameSize;\n }\n\n if (offset > 0) {\n this.buffer = this.buffer.slice(offset);\n }\n\n return { frames, config: configEmitted };\n }\n\n flush(): MP3Frame[] {\n const { frames } = this.push(new Uint8Array(0));\n const remainder = this.buffer;\n this.buffer = new Uint8Array(0);\n if (remainder.length) {\n console.warn('[MP3FrameParser] Remaining unparsed bytes:', remainder.length);\n }\n return frames;\n }\n\n private appendToBuffer(chunk: Uint8Array): void {\n if (chunk.length === 0) {\n return;\n }\n const combined = new Uint8Array(this.buffer.length + chunk.length);\n combined.set(this.buffer, 0);\n combined.set(chunk, this.buffer.length);\n this.buffer = combined;\n }\n\n private skipId3Headers(): void {\n let offset = 0;\n while (this.buffer.length - offset >= 10) {\n if (\n this.buffer[offset] === 0x49 &&\n this.buffer[offset + 1] === 0x44 &&\n this.buffer[offset + 2] === 0x33\n ) {\n const size = this.readSynchsafeInteger(this.buffer.subarray(offset + 6, offset + 10));\n const total = size + 10;\n if (offset + total <= this.buffer.length) {\n offset += total;\n continue;\n }\n }\n break;\n }\n if (offset > 0) {\n this.buffer = this.buffer.slice(offset);\n }\n }\n\n private parseHeader(\n buffer: Uint8Array,\n offset: number\n ): {\n frameSize: number;\n sampleRate: number;\n channels: number;\n bitrateKbps: number | null;\n samplesPerFrame: number;\n } | null {\n if (offset + 3 >= buffer.length) {\n return null;\n }\n if (buffer[offset] !== 0xff || ((buffer[offset + 1] ?? 0) & 0xe0) !== 0xe0) {\n return null;\n }\n\n const versionBits = ((buffer[offset + 1] ?? 0) >> 3) & 0x03;\n const layerBits = ((buffer[offset + 1] ?? 0) >> 1) & 0x03;\n const bitrateIndex = ((buffer[offset + 2] ?? 0) >> 4) & 0x0f;\n const sampleRateIndex = ((buffer[offset + 2] ?? 0) >> 2) & 0x03;\n const paddingBit = ((buffer[offset + 2] ?? 0) >> 1) & 0x01;\n const channelMode = ((buffer[offset + 3] ?? 0) >> 6) & 0x03;\n\n const version = this.getVersion(versionBits);\n const layer = this.getLayer(layerBits);\n if (!version || !layer) {\n return null;\n }\n\n const sampleRate = SAMPLE_RATE_TABLE[version]?.[sampleRateIndex] ?? null;\n if (!sampleRate) {\n return null;\n }\n\n const bitrateKey = `${version === 1 ? 1 : 2}-${layer}`;\n const bitrateKbps = BITRATE_TABLE[bitrateKey]?.[bitrateIndex] ?? null;\n if (bitrateKbps === null) {\n return null;\n }\n\n const samplesPerFrame = this.getSamplesPerFrame(version, layer);\n const frameSize = this.calculateFrameSize(layer, bitrateKbps, sampleRate, paddingBit);\n if (!frameSize || frameSize < 24) {\n return null;\n }\n\n const channels = channelMode === 3 ? 1 : 2;\n\n return {\n frameSize,\n sampleRate,\n channels,\n bitrateKbps: bitrateKbps || null,\n samplesPerFrame,\n };\n }\n\n private getVersion(bits: number): 1 | 2 | 2.5 | null {\n switch (bits) {\n case 0b11:\n return 1;\n case 0b10:\n return 2;\n case 0b00:\n return 2.5;\n default:\n return null;\n }\n }\n\n private getLayer(bits: number): 1 | 2 | 3 | null {\n switch (bits) {\n case 0b11:\n return 1;\n case 0b10:\n return 2;\n case 0b01:\n return 3;\n default:\n return null;\n }\n }\n\n private getSamplesPerFrame(version: 1 | 2 | 2.5, layer: 1 | 2 | 3): number {\n if (layer === 1) {\n return 384;\n }\n if (layer === 2) {\n return 1152;\n }\n return version === 1 ? 1152 : 576;\n }\n\n private calculateFrameSize(\n layer: 1 | 2 | 3,\n bitrateKbps: number,\n sampleRate: number,\n padding: number\n ): number {\n if (bitrateKbps <= 0) {\n return 0;\n }\n\n if (layer === 1) {\n return ((12 * bitrateKbps * 1000) / sampleRate + padding) * 4;\n }\n\n return Math.floor((144 * bitrateKbps * 1000) / sampleRate + padding);\n }\n\n private readSynchsafeInteger(bytes: Uint8Array): number {\n if (bytes.length !== 4) {\n return 0;\n }\n return (\n (((bytes[0] ?? 0) & 0x7f) << 21) |\n (((bytes[1] ?? 0) & 0x7f) << 14) |\n (((bytes[2] ?? 0) & 0x7f) << 7) |\n ((bytes[3] ?? 0) & 0x7f)\n );\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CompositionModel } from '../../model';
|
|
1
|
+
import { Resource, CompositionModel } from '../../model';
|
|
2
2
|
import { ResourceLoadOptions, LoadTask, ResourceLoaderOptions } from './types';
|
|
3
3
|
|
|
4
4
|
export declare class ResourceConflictError extends Error {
|
|
@@ -13,19 +13,15 @@ export declare class ResourceLoader {
|
|
|
13
13
|
private eventBus?;
|
|
14
14
|
private onStateChange?;
|
|
15
15
|
private blobCache;
|
|
16
|
-
private pendingTransfers;
|
|
17
16
|
private parsingIndexes;
|
|
18
17
|
private isPreloadingEnabled;
|
|
19
18
|
private preloadQueue;
|
|
20
|
-
private activePreloads;
|
|
21
|
-
private readonly IDLE_PRELOAD_CONCURRENCY;
|
|
22
19
|
constructor(options: ResourceLoaderOptions);
|
|
23
20
|
setModel(model: CompositionModel): Promise<void>;
|
|
24
21
|
setPreloadingEnabled(enabled: boolean): void;
|
|
25
22
|
startPreloading(): void;
|
|
26
23
|
private processPreloadQueue;
|
|
27
24
|
private enqueueLoad;
|
|
28
|
-
private registerPendingTransfer;
|
|
29
25
|
private processQueue;
|
|
30
26
|
/**
|
|
31
27
|
* Start loading a resource
|
|
@@ -54,6 +50,12 @@ export declare class ResourceLoader {
|
|
|
54
50
|
* Write resource stream to OPFS
|
|
55
51
|
*/
|
|
56
52
|
private writeToOPFS;
|
|
53
|
+
/**
|
|
54
|
+
* Load and parse audio file (MP3/WAV) in main thread
|
|
55
|
+
* Extract EncodedAudioChunk and cache to AudioSampleCache
|
|
56
|
+
* Aligned with video audio track extraction (unified architecture)
|
|
57
|
+
*/
|
|
58
|
+
private loadAndParseAudioFile;
|
|
57
59
|
/**
|
|
58
60
|
* Parse moov from stream and cache index + audio samples + decode first frame
|
|
59
61
|
*/
|
|
@@ -63,6 +65,7 @@ export declare class ResourceLoader {
|
|
|
63
65
|
* Just download the content - state management is handled by startLoad()
|
|
64
66
|
*/
|
|
65
67
|
private loadTextResource;
|
|
68
|
+
loadImage(resource: Resource): Promise<ImageBitmap>;
|
|
66
69
|
/**
|
|
67
70
|
* Load image resource: fetch blob → create ImageBitmap → cache in CacheManager
|
|
68
71
|
* Note: Images don't need streaming (typically < 5MB)
|
|
@@ -77,10 +80,6 @@ export declare class ResourceLoader {
|
|
|
77
80
|
* Fetch resource as blob (for images, json, etc.)
|
|
78
81
|
*/
|
|
79
82
|
private fetchBlob;
|
|
80
|
-
/**
|
|
81
|
-
* Transfer ImageBitmap to VideoComposeWorker
|
|
82
|
-
* Legacy: Not used in window cache architecture (images accessed via CacheManager)
|
|
83
|
-
*/
|
|
84
83
|
/**
|
|
85
84
|
* Transfer cached image to a session
|
|
86
85
|
* Creates new ImageBitmap from cached Blob and transfers to worker
|
|
@@ -91,6 +90,18 @@ export declare class ResourceLoader {
|
|
|
91
90
|
*/
|
|
92
91
|
private transferToDemuxWorker;
|
|
93
92
|
private updateResourceState;
|
|
93
|
+
/**
|
|
94
|
+
* Fetch a resource and wait for loading + parsing to complete
|
|
95
|
+
*
|
|
96
|
+
* Returns a Promise that resolves when:
|
|
97
|
+
* - Resource is fully loaded, parsed, and cached (state='ready')
|
|
98
|
+
* - Or rejects if loading/parsing fails
|
|
99
|
+
*
|
|
100
|
+
* Promise lifecycle:
|
|
101
|
+
* 1. enqueueLoad() creates LoadTask with promise/resolve/reject (or reuses existing)
|
|
102
|
+
* 2. processQueue() → startLoad() executes async in background
|
|
103
|
+
* 3. startLoad() completes → finally → completeTask() → task.resolve()/reject()
|
|
104
|
+
*/
|
|
94
105
|
fetch(resourceId?: string, options?: ResourceLoadOptions): Promise<void>;
|
|
95
106
|
cancel(resourceId: string): void;
|
|
96
107
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ResourceLoader.d.ts","sourceRoot":"","sources":["../../../src/stages/load/ResourceLoader.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"ResourceLoader.d.ts","sourceRoot":"","sources":["../../../src/stages/load/ResourceLoader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,gBAAgB,EAAiB,MAAM,aAAa,CAAC;AAClF,OAAO,KAAK,EAAE,mBAAmB,EAAE,QAAQ,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAWpF,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,KAAK,CAAC,CAAmB;IACjC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,QAAQ,CAAC,CAA4B;IAC7C,OAAO,CAAC,aAAa,CAAC,CAAyD;IAC/E,OAAO,CAAC,SAAS,CAA2B;IAC5C,OAAO,CAAC,cAAc,CAAqB;IAG3C,OAAO,CAAC,mBAAmB,CAAQ;IACnC,OAAO,CAAC,YAAY,CAAgB;gBAExB,OAAO,EAAE,qBAAqB;IAYpC,QAAQ,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAatD,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAO5C,eAAe,IAAI,IAAI;IA0BvB,OAAO,CAAC,mBAAmB;IAe3B,OAAO,CAAC,WAAW;IAmBnB,OAAO,CAAC,YAAY;IAQpB;;;OAGG;YACW,SAAS;IAsDvB;;OAEG;IACG,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAqC1D;;;OAGG;YACW,oBAAoB;IAclC;;;;;;;OAOG;YACW,iBAAiB;IA6C/B;;OAEG;YACW,WAAW;IAIzB;;;;OAIG;YACW,qBAAqB;IA8CnC;;OAEG;YACW,oBAAoB;IA8DlC;;;OAGG;YACW,gBAAgB;IAMxB,SAAS,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,WAAW,CAAC;IAazD;;;;;;;;OAQG;YACW,eAAe;IAe7B;;OAEG;YACW,SAAS;IAUvB;;;OAGG;YACW,mBAAmB;IAsBjC;;OAEG;YACW,qBAAqB;IAyBnC,OAAO,CAAC,mBAAmB;IAgB3B;;;;;;;;;;;OAWG;IACG,KAAK,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAwB9E,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAKhC;;OAEG;IACH,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAI9C,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAOzB,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAqB9E,IAAI,WAAW,IAAI,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAEvC;IAED,IAAI,SAAS,IAAI,QAAQ,EAAE,CAE1B;IAED,OAAO,IAAI,IAAI;CAIhB"}
|