@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.
Files changed (61) hide show
  1. package/dist/Meframe.d.ts +2 -2
  2. package/dist/Meframe.d.ts.map +1 -1
  3. package/dist/Meframe.js +3 -2
  4. package/dist/Meframe.js.map +1 -1
  5. package/dist/cache/CacheManager.d.ts +12 -17
  6. package/dist/cache/CacheManager.d.ts.map +1 -1
  7. package/dist/cache/CacheManager.js +18 -280
  8. package/dist/cache/CacheManager.js.map +1 -1
  9. package/dist/cache/l1/AudioL1Cache.d.ts +36 -19
  10. package/dist/cache/l1/AudioL1Cache.d.ts.map +1 -1
  11. package/dist/cache/l1/AudioL1Cache.js +182 -282
  12. package/dist/cache/l1/AudioL1Cache.js.map +1 -1
  13. package/dist/controllers/PlaybackController.d.ts +4 -2
  14. package/dist/controllers/PlaybackController.d.ts.map +1 -1
  15. package/dist/controllers/PlaybackController.js +43 -13
  16. package/dist/controllers/PlaybackController.js.map +1 -1
  17. package/dist/model/types.d.ts +0 -4
  18. package/dist/model/types.d.ts.map +1 -1
  19. package/dist/model/types.js.map +1 -1
  20. package/dist/orchestrator/ExportScheduler.d.ts +6 -0
  21. package/dist/orchestrator/ExportScheduler.d.ts.map +1 -1
  22. package/dist/orchestrator/ExportScheduler.js +45 -66
  23. package/dist/orchestrator/ExportScheduler.js.map +1 -1
  24. package/dist/orchestrator/GlobalAudioSession.d.ts +35 -28
  25. package/dist/orchestrator/GlobalAudioSession.d.ts.map +1 -1
  26. package/dist/orchestrator/GlobalAudioSession.js +212 -421
  27. package/dist/orchestrator/GlobalAudioSession.js.map +1 -1
  28. package/dist/orchestrator/OnDemandVideoSession.d.ts +3 -3
  29. package/dist/orchestrator/OnDemandVideoSession.d.ts.map +1 -1
  30. package/dist/orchestrator/OnDemandVideoSession.js +4 -4
  31. package/dist/orchestrator/OnDemandVideoSession.js.map +1 -1
  32. package/dist/orchestrator/Orchestrator.d.ts +1 -2
  33. package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
  34. package/dist/orchestrator/Orchestrator.js +34 -48
  35. package/dist/orchestrator/Orchestrator.js.map +1 -1
  36. package/dist/orchestrator/VideoClipSession.d.ts +0 -2
  37. package/dist/orchestrator/VideoClipSession.d.ts.map +1 -1
  38. package/dist/orchestrator/VideoClipSession.js +0 -49
  39. package/dist/orchestrator/VideoClipSession.js.map +1 -1
  40. package/dist/stages/compose/OfflineAudioMixer.d.ts.map +1 -1
  41. package/dist/stages/compose/OfflineAudioMixer.js +13 -18
  42. package/dist/stages/compose/OfflineAudioMixer.js.map +1 -1
  43. package/dist/stages/decode/AudioChunkDecoder.js +169 -0
  44. package/dist/stages/decode/AudioChunkDecoder.js.map +1 -0
  45. package/dist/stages/demux/MP3FrameParser.js +186 -0
  46. package/dist/stages/demux/MP3FrameParser.js.map +1 -0
  47. package/dist/stages/load/ResourceLoader.d.ts +20 -9
  48. package/dist/stages/load/ResourceLoader.d.ts.map +1 -1
  49. package/dist/stages/load/ResourceLoader.js +92 -135
  50. package/dist/stages/load/ResourceLoader.js.map +1 -1
  51. package/dist/stages/load/types.d.ts +1 -0
  52. package/dist/stages/load/types.d.ts.map +1 -1
  53. package/dist/utils/audio-data.d.ts +16 -0
  54. package/dist/utils/audio-data.d.ts.map +1 -0
  55. package/dist/utils/audio-data.js +111 -0
  56. package/dist/utils/audio-data.js.map +1 -0
  57. package/package.json +1 -1
  58. package/dist/cache/resource/ImageBitmapCache.d.ts +0 -65
  59. package/dist/cache/resource/ImageBitmapCache.d.ts.map +0 -1
  60. package/dist/cache/resource/ImageBitmapCache.js +0 -101
  61. 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,EAAiB,KAAK,gBAAgB,EAAiB,MAAM,aAAa,CAAC;AAClF,OAAO,KAAK,EAAE,mBAAmB,EAAE,QAAQ,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAUpF,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,gBAAgB,CAA+B;IACvD,OAAO,CAAC,cAAc,CAAqB;IAG3C,OAAO,CAAC,mBAAmB,CAAQ;IACnC,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,cAAc,CAAqB;IAE3C,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAK;gBAElC,OAAO,EAAE,qBAAqB;IAUpC,QAAQ,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAatD,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAU5C,eAAe,IAAI,IAAI;IAmCvB,OAAO,CAAC,mBAAmB;IAqB3B,OAAO,CAAC,WAAW;IAqCnB,OAAO,CAAC,uBAAuB;IAU/B,OAAO,CAAC,YAAY;IAQpB;;;OAGG;YACW,SAAS;IAqDvB;;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;;OAEG;YACW,oBAAoB;IA8DlC;;;OAGG;YACW,gBAAgB;IAM9B;;;;;;;;OAQG;YACW,eAAe;IA+B7B;;OAEG;YACW,SAAS;IAUvB;;;OAGG;IAuBH;;;OAGG;YACW,mBAAmB;IAqBjC;;OAEG;YACW,qBAAqB;IAyBnC,OAAO,CAAC,mBAAmB;IAgBrB,KAAK,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAoF9E,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;CAKhB"}
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"}