@meframe/core 0.3.3 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +4 -4
  2. package/dist/event/events.d.ts +4 -2
  3. package/dist/event/events.d.ts.map +1 -1
  4. package/dist/event/events.js.map +1 -1
  5. package/dist/index.d.ts +2 -2
  6. package/dist/index.js +1 -1
  7. package/dist/index.js.map +1 -1
  8. package/dist/node_modules/.pnpm/mp4-muxer@5.2.2/node_modules/mp4-muxer/build/mp4-muxer.js.map +1 -0
  9. package/dist/{medeo-fe/node_modules → node_modules}/.pnpm/mp4box@0.5.4/node_modules/mp4box/dist/mp4box.all.js +2 -2
  10. package/dist/node_modules/.pnpm/mp4box@0.5.4/node_modules/mp4box/dist/mp4box.all.js.map +1 -0
  11. package/dist/orchestrator/ExportScheduler.d.ts +2 -3
  12. package/dist/orchestrator/ExportScheduler.d.ts.map +1 -1
  13. package/dist/orchestrator/ExportScheduler.js +2 -3
  14. package/dist/orchestrator/ExportScheduler.js.map +1 -1
  15. package/dist/orchestrator/Orchestrator.d.ts +2 -1
  16. package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
  17. package/dist/orchestrator/Orchestrator.js +26 -16
  18. package/dist/orchestrator/Orchestrator.js.map +1 -1
  19. package/dist/orchestrator/VideoClipSession.d.ts +1 -1
  20. package/dist/orchestrator/VideoClipSession.d.ts.map +1 -1
  21. package/dist/orchestrator/VideoClipSession.js +2 -2
  22. package/dist/orchestrator/VideoClipSession.js.map +1 -1
  23. package/dist/orchestrator/{OnDemandVideoSession.d.ts → VideoWindowDecodeSession.d.ts} +4 -4
  24. package/dist/orchestrator/VideoWindowDecodeSession.d.ts.map +1 -0
  25. package/dist/orchestrator/{OnDemandVideoSession.js → VideoWindowDecodeSession.js} +10 -10
  26. package/dist/orchestrator/VideoWindowDecodeSession.js.map +1 -0
  27. package/dist/stages/decode/index.d.ts +0 -1
  28. package/dist/stages/decode/index.d.ts.map +1 -1
  29. package/dist/{utils/video-decoder-helpers.d.ts → stages/decode/video-decoder.d.ts} +1 -1
  30. package/dist/stages/decode/video-decoder.d.ts.map +1 -0
  31. package/dist/{utils/video-decoder-helpers.js → stages/decode/video-decoder.js} +2 -2
  32. package/dist/stages/decode/video-decoder.js.map +1 -0
  33. package/dist/stages/mux/MP4Muxer.js +1 -1
  34. package/dist/utils/mp4box.js +1 -1
  35. package/dist/worker/WorkerPool.d.ts.map +1 -1
  36. package/dist/worker/WorkerPool.js +1 -5
  37. package/dist/worker/WorkerPool.js.map +1 -1
  38. package/dist/worker/types.d.ts +1 -1
  39. package/dist/worker/types.d.ts.map +1 -1
  40. package/dist/worker/types.js.map +1 -1
  41. package/dist/worker/worker-event-whitelist.d.ts.map +1 -1
  42. package/dist/worker/worker-manifest.d.ts +2 -2
  43. package/dist/worker/worker-manifest.js.map +1 -1
  44. package/dist/workers/WorkerChannel.DQK8rAab.js.map +1 -1
  45. package/dist/workers/worker-manifest.json +0 -4
  46. package/package.json +1 -1
  47. package/dist/medeo-fe/node_modules/.pnpm/mp4-muxer@5.2.2/node_modules/mp4-muxer/build/mp4-muxer.js.map +0 -1
  48. package/dist/medeo-fe/node_modules/.pnpm/mp4box@0.5.4/node_modules/mp4box/dist/mp4box.all.js.map +0 -1
  49. package/dist/orchestrator/OnDemandVideoSession.d.ts.map +0 -1
  50. package/dist/orchestrator/OnDemandVideoSession.js.map +0 -1
  51. package/dist/stages/decode/VideoChunkDecoder.d.ts +0 -49
  52. package/dist/stages/decode/VideoChunkDecoder.d.ts.map +0 -1
  53. package/dist/utils/video-decoder-helpers.d.ts.map +0 -1
  54. package/dist/utils/video-decoder-helpers.js.map +0 -1
  55. package/dist/workers/BaseDecoder.CB5XmTpS.js +0 -137
  56. package/dist/workers/BaseDecoder.CB5XmTpS.js.map +0 -1
  57. package/dist/workers/MP4Demuxer.DfWiwyjB.js +0 -7396
  58. package/dist/workers/MP4Demuxer.DfWiwyjB.js.map +0 -1
  59. package/dist/workers/stages/decode/audio-decode.worker.-DGlQrJD.js +0 -320
  60. package/dist/workers/stages/decode/audio-decode.worker.-DGlQrJD.js.map +0 -1
  61. package/dist/workers/stages/decode/video-decode.worker.BnWVUkng.js +0 -334
  62. package/dist/workers/stages/decode/video-decode.worker.BnWVUkng.js.map +0 -1
  63. package/dist/workers/stages/demux/audio-demux.worker.D-_LoVqW.js +0 -502
  64. package/dist/workers/stages/demux/audio-demux.worker.D-_LoVqW.js.map +0 -1
  65. package/dist/workers/stages/demux/video-demux.worker.BWDrLGni.js +0 -210
  66. package/dist/workers/stages/demux/video-demux.worker.BWDrLGni.js.map +0 -1
  67. /package/dist/{medeo-fe/node_modules → node_modules}/.pnpm/mp4-muxer@5.2.2/node_modules/mp4-muxer/build/mp4-muxer.js +0 -0
@@ -1,320 +0,0 @@
1
- import { W as WorkerChannel, a as WorkerMessageType, b as WorkerState } from "../../WorkerChannel.DQK8rAab.js";
2
- import { B as BaseDecoder } from "../../BaseDecoder.CB5XmTpS.js";
3
- class AudioChunkDecoder extends BaseDecoder {
4
- // Default values
5
- static DEFAULT_HIGH_WATER_MARK = 20;
6
- static DEFAULT_DECODE_QUEUE_THRESHOLD = 16;
7
- // Exposed properties
8
- trackId;
9
- // Backpressure configuration
10
- highWaterMark;
11
- decodeQueueThreshold;
12
- // Buffering support for delayed configuration
13
- bufferedChunks = [];
14
- isProcessingBuffer = false;
15
- constructor(trackId, config) {
16
- super(config);
17
- this.trackId = trackId;
18
- this.highWaterMark = config?.backpressure?.highWaterMark ?? AudioChunkDecoder.DEFAULT_HIGH_WATER_MARK;
19
- this.decodeQueueThreshold = AudioChunkDecoder.DEFAULT_DECODE_QUEUE_THRESHOLD;
20
- }
21
- // Computed properties
22
- get isConfigured() {
23
- return this.isReady;
24
- }
25
- get state() {
26
- return this.decoder?.state || "unconfigured";
27
- }
28
- /**
29
- * Update configuration - can be called before or after initialization
30
- */
31
- async updateConfig(config) {
32
- if (!this.isReady && config.codec) {
33
- await this.configure(config);
34
- await this.processBufferedChunks();
35
- return;
36
- }
37
- }
38
- // Override createStream to handle buffering
39
- createStream() {
40
- return new TransformStream(
41
- {
42
- start: async (controller) => {
43
- this.controller = controller;
44
- if (this.config?.codec && !this.isReady) {
45
- await this.initialize();
46
- }
47
- },
48
- transform: async (chunk) => {
49
- if (!this.isReady) {
50
- this.bufferedChunks.push(chunk);
51
- return;
52
- }
53
- if (this.isProcessingBuffer) {
54
- this.bufferedChunks.push(chunk);
55
- return;
56
- }
57
- await this.processChunk(chunk);
58
- },
59
- flush: async () => {
60
- if (this.isReady) {
61
- await this.flush();
62
- }
63
- }
64
- },
65
- {
66
- highWaterMark: this.highWaterMark,
67
- size: () => 1
68
- }
69
- );
70
- }
71
- /**
72
- * Process a single chunk (extracted from transform for reuse)
73
- */
74
- async processChunk(chunk) {
75
- if (!this.decoder) {
76
- throw new Error("Decoder not initialized");
77
- }
78
- if (this.decoder.state !== "configured") {
79
- console.error("[AudioChunkDecoder] Decoder in unexpected state:", this.decoder.state);
80
- throw new Error(`Decoder not configured, state: ${this.decoder.state}`);
81
- }
82
- this.decode(chunk);
83
- }
84
- // Implement abstract methods
85
- async isConfigSupported(config) {
86
- const result = await AudioDecoder.isConfigSupported({
87
- codec: config.codec,
88
- sampleRate: config.sampleRate,
89
- numberOfChannels: config.numberOfChannels
90
- });
91
- return { supported: result.supported ?? false };
92
- }
93
- createDecoder(init) {
94
- return new AudioDecoder(init);
95
- }
96
- getDecoderType() {
97
- return "Audio";
98
- }
99
- async configureDecoder(config) {
100
- if (!this.decoder) return;
101
- await this.decoder.configure({
102
- codec: config.codec,
103
- sampleRate: config.sampleRate,
104
- numberOfChannels: config.numberOfChannels,
105
- ...config.description && { description: config.description }
106
- });
107
- }
108
- decode(chunk) {
109
- this.decoder?.decode(chunk);
110
- }
111
- /**
112
- * Configure the decoder with codec info (can be called after creation)
113
- */
114
- async configure(config) {
115
- if (this.isReady) {
116
- await this.reconfigure(config);
117
- return;
118
- }
119
- this.config = config;
120
- await this.initialize();
121
- }
122
- /**
123
- * Process any buffered chunks after configuration
124
- */
125
- async processBufferedChunks() {
126
- if (!this.isReady || this.bufferedChunks.length === 0) {
127
- return;
128
- }
129
- this.isProcessingBuffer = true;
130
- const chunks = [...this.bufferedChunks];
131
- this.bufferedChunks = [];
132
- for (const chunk of chunks) {
133
- try {
134
- await this.processChunk(chunk);
135
- } catch (error) {
136
- console.error("[AudioChunkDecoder] Error processing buffered chunk:", error);
137
- }
138
- }
139
- this.isProcessingBuffer = false;
140
- if (this.bufferedChunks.length > 0) {
141
- await this.processBufferedChunks();
142
- }
143
- }
144
- // Override close to clean up buffered chunks
145
- async close() {
146
- this.bufferedChunks = [];
147
- await super.close();
148
- }
149
- }
150
- const normalizeDescription = (desc) => {
151
- if (!desc) return void 0;
152
- if (desc instanceof ArrayBuffer) return desc;
153
- const view = desc;
154
- return view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength);
155
- };
156
- class AudioDecodeWorker {
157
- channel;
158
- decoder = null;
159
- clipId = "";
160
- trackId = "";
161
- defaultConfig = {};
162
- trackMetadata = null;
163
- upstreamPort = null;
164
- constructor() {
165
- this.channel = new WorkerChannel(self, {
166
- name: "AudioDecodeWorker",
167
- timeout: 3e4
168
- });
169
- this.setupHandlers();
170
- }
171
- setupHandlers() {
172
- this.channel.registerHandler("configure", this.handleConfigure.bind(this));
173
- this.channel.registerHandler("connect", this.handleConnect.bind(this));
174
- this.channel.registerHandler("flush", this.handleFlush.bind(this));
175
- this.channel.registerHandler("reset", this.handleReset.bind(this));
176
- this.channel.registerHandler("get_stats", this.handleGetStats.bind(this));
177
- this.channel.registerHandler(WorkerMessageType.Dispose, this.handleDispose.bind(this));
178
- }
179
- async handleConnect(payload) {
180
- const { port, direction, sessionId, trackId } = payload;
181
- if (direction === "upstream") {
182
- this.upstreamPort = port;
183
- this.clipId = sessionId || "default";
184
- this.trackId = trackId || sessionId || "default";
185
- const channel = new WorkerChannel(port, {
186
- name: "Demux-AudioDecode",
187
- timeout: 3e4
188
- });
189
- channel.receiveStream((stream, metadata) => {
190
- this.handleReceiveStream(stream, {
191
- ...metadata,
192
- clipStartUs: payload.clipStartUs,
193
- clipDurationUs: payload.clipDurationUs
194
- });
195
- });
196
- channel.registerHandler("configure", this.handleConfigure.bind(this));
197
- }
198
- return { success: true };
199
- }
200
- async handleConfigure(payload) {
201
- const { sessionId, streamType, codec, sampleRate, numberOfChannels, description, config } = payload;
202
- if (sessionId && streamType === "audio") {
203
- Object.assign(this.defaultConfig, {
204
- codec,
205
- sampleRate,
206
- numberOfChannels,
207
- description: normalizeDescription(description)
208
- });
209
- if (this.decoder) {
210
- await this.decoder.updateConfig({
211
- codec,
212
- sampleRate,
213
- numberOfChannels,
214
- description: normalizeDescription(description)
215
- });
216
- }
217
- return { success: true };
218
- }
219
- if (config?.audio) {
220
- Object.assign(this.defaultConfig, config.audio);
221
- if (this.decoder) {
222
- await this.decoder.updateConfig(config.audio);
223
- }
224
- }
225
- this.channel.state = WorkerState.Ready;
226
- return { success: true };
227
- }
228
- async handleReceiveStream(stream, metadata) {
229
- const sessionId = metadata?.sessionId || this.clipId;
230
- const trackId = metadata?.trackId || this.trackId;
231
- if (!this.decoder) {
232
- const decoderConfig = {
233
- ...this.defaultConfig,
234
- codec: metadata?.codec ?? this.defaultConfig.codec,
235
- sampleRate: metadata?.sampleRate ?? this.defaultConfig.sampleRate,
236
- numberOfChannels: metadata?.numberOfChannels ?? this.defaultConfig.numberOfChannels,
237
- description: normalizeDescription(metadata?.description) ?? this.defaultConfig.description
238
- };
239
- this.decoder = new AudioChunkDecoder(trackId, decoderConfig);
240
- }
241
- this.trackMetadata = {
242
- clipId: this.clipId,
243
- config: this.extractTrackConfig(metadata?.runtimeConfig),
244
- sampleRate: metadata?.sampleRate,
245
- numberOfChannels: metadata?.numberOfChannels,
246
- type: metadata?.trackType ?? "other"
247
- };
248
- const transform = this.decoder.createStream();
249
- this.channel.sendStream(transform.readable, {
250
- streamType: "audio",
251
- sessionId,
252
- trackId,
253
- clipStartUs: metadata?.clipStartUs ?? 0,
254
- clipDurationUs: metadata?.clipDurationUs ?? 0,
255
- trackMetadata: this.trackMetadata
256
- });
257
- stream.pipeTo(transform.writable).catch((error) => console.error("[AudioDecodeWorker] Audio stream pipe error:", error));
258
- }
259
- extractTrackConfig(config) {
260
- return {
261
- startTimeUs: config?.startTimeUs ?? 0,
262
- durationUs: config?.durationUs,
263
- volume: config?.volume ?? 1,
264
- fadeIn: config?.fadeIn,
265
- fadeOut: config?.fadeOut,
266
- effects: config?.effects ?? [],
267
- duckingTag: config?.duckingTag
268
- };
269
- }
270
- async handleFlush() {
271
- if (this.decoder) {
272
- await this.decoder.flush();
273
- }
274
- return { success: true };
275
- }
276
- async handleReset() {
277
- if (this.decoder) {
278
- await this.decoder.reset();
279
- }
280
- this.channel.notify("reset_complete", {
281
- type: "audio"
282
- });
283
- return { success: true };
284
- }
285
- async handleGetStats() {
286
- if (!this.decoder) {
287
- return {};
288
- }
289
- return {
290
- audio: {
291
- clipId: this.clipId,
292
- trackId: this.trackId,
293
- configured: this.decoder.isConfigured,
294
- queueSize: this.decoder.queueSize,
295
- state: this.decoder.state
296
- }
297
- };
298
- }
299
- async handleDispose() {
300
- if (this.decoder) {
301
- await this.decoder.close();
302
- this.decoder = null;
303
- }
304
- this.upstreamPort?.close();
305
- this.upstreamPort = null;
306
- this.trackMetadata = null;
307
- this.channel.state = WorkerState.Disposed;
308
- return { success: true };
309
- }
310
- }
311
- const worker = new AudioDecodeWorker();
312
- self.addEventListener("beforeunload", () => {
313
- worker["handleDispose"]();
314
- });
315
- const audioDecode_worker = null;
316
- export {
317
- AudioDecodeWorker,
318
- audioDecode_worker as default
319
- };
320
- //# sourceMappingURL=audio-decode.worker.-DGlQrJD.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"audio-decode.worker.-DGlQrJD.js","sources":["../../../../src/stages/decode/AudioChunkDecoder.ts","../../../../src/stages/decode/audio-decode.worker.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 this.decode(chunk);\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","import { WorkerChannel } from '../../worker/WorkerChannel';\nimport { WorkerMessageType, WorkerState } from '../../worker/types';\nimport { AudioChunkDecoder } from './AudioChunkDecoder';\nimport { AudioDecoderConfig } from './types';\nimport type { AudioTrackConfig } from '../compose/types';\n\ninterface TrackMetadata {\n clipId: string;\n config: AudioTrackConfig;\n sampleRate?: number;\n numberOfChannels?: number;\n type: 'bgm' | 'voice' | 'sfx' | 'other';\n}\n\nconst normalizeDescription = (desc?: ArrayBuffer | ArrayBufferView): ArrayBuffer | undefined => {\n if (!desc) return undefined;\n\n if (desc instanceof ArrayBuffer) return desc;\n\n const view = desc as ArrayBufferView;\n return view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength) as ArrayBuffer;\n};\n\n/**\n * AudioDecodeWorker (Clip Local) - Decodes audio for a single clip\n * Receives encoded audio chunks from AudioDemuxWorker and outputs decoded audio data\n *\n * Pipeline: AudioDemuxWorker → AudioDecodeWorker → Main Thread (OfflineAudioContext)\n *\n * Features:\n * - Single clip, single AudioDecoder instance\n * - Outputs AudioData stream to main thread for mixing\n * - Stream-based processing with backpressure\n * - Lifecycle tied to clip pipeline\n */\nexport class AudioDecodeWorker {\n private channel: WorkerChannel;\n private decoder: AudioChunkDecoder | null = null;\n private clipId: string = '';\n private trackId: string = '';\n\n private defaultConfig: Partial<AudioDecoderConfig> = {};\n private trackMetadata: TrackMetadata | null = null;\n\n private upstreamPort: MessagePort | null = null;\n\n constructor() {\n this.channel = new WorkerChannel(self as any, {\n name: 'AudioDecodeWorker',\n timeout: 30000,\n });\n\n this.setupHandlers();\n }\n\n private setupHandlers(): void {\n this.channel.registerHandler('configure', this.handleConfigure.bind(this));\n this.channel.registerHandler('connect', this.handleConnect.bind(this));\n this.channel.registerHandler('flush', this.handleFlush.bind(this));\n this.channel.registerHandler('reset', this.handleReset.bind(this));\n this.channel.registerHandler('get_stats', this.handleGetStats.bind(this));\n this.channel.registerHandler(WorkerMessageType.Dispose, this.handleDispose.bind(this));\n }\n\n private async handleConnect(payload: {\n direction: 'upstream' | 'downstream';\n port: MessagePort;\n streamType?: 'audio';\n sessionId?: string;\n trackId?: string;\n clipStartUs?: number;\n clipDurationUs?: number;\n }): Promise<{ success: boolean }> {\n const { port, direction, sessionId, trackId } = payload;\n\n if (direction === 'upstream') {\n this.upstreamPort = port;\n this.clipId = sessionId || 'default';\n this.trackId = trackId || sessionId || 'default';\n\n const channel = new WorkerChannel(port, {\n name: 'Demux-AudioDecode',\n timeout: 30000,\n });\n\n channel.receiveStream((stream, metadata) => {\n this.handleReceiveStream(stream, {\n ...metadata,\n clipStartUs: payload.clipStartUs,\n clipDurationUs: payload.clipDurationUs,\n });\n });\n\n channel.registerHandler('configure', this.handleConfigure.bind(this));\n }\n\n return { success: true };\n }\n\n private async handleConfigure(payload: {\n config?: { audio?: Partial<AudioDecoderConfig> };\n sessionId?: string;\n streamType?: 'audio';\n codec?: string;\n sampleRate?: number;\n numberOfChannels?: number;\n description?: ArrayBuffer | Uint8Array;\n }): Promise<{ success: boolean }> {\n const { sessionId, streamType, codec, sampleRate, numberOfChannels, description, config } =\n payload;\n\n if (sessionId && streamType === 'audio') {\n // Save codec info to defaultConfig for decoder creation\n Object.assign(this.defaultConfig, {\n codec,\n sampleRate,\n numberOfChannels,\n description: normalizeDescription(description),\n });\n\n // Update existing decoder if present (will process buffered chunks)\n if (this.decoder) {\n await this.decoder.updateConfig({\n codec,\n sampleRate,\n numberOfChannels,\n description: normalizeDescription(description),\n });\n }\n\n return { success: true };\n }\n\n if (config?.audio) {\n Object.assign(this.defaultConfig, config.audio);\n\n if (this.decoder) {\n await this.decoder.updateConfig(config.audio);\n }\n }\n\n this.channel.state = WorkerState.Ready;\n\n return { success: true };\n }\n\n private async handleReceiveStream(\n stream: ReadableStream,\n metadata?: Record<string, any>\n ): Promise<void> {\n const sessionId = metadata?.sessionId || this.clipId;\n const trackId = metadata?.trackId || this.trackId;\n if (!this.decoder) {\n // Prefer metadata, fallback to defaultConfig\n // Note: If codec is not available, decoder will buffer chunks internally\n const decoderConfig = {\n ...this.defaultConfig,\n codec: metadata?.codec ?? this.defaultConfig.codec,\n sampleRate: metadata?.sampleRate ?? this.defaultConfig.sampleRate,\n numberOfChannels: metadata?.numberOfChannels ?? this.defaultConfig.numberOfChannels,\n description: normalizeDescription(metadata?.description) ?? this.defaultConfig.description,\n };\n\n this.decoder = new AudioChunkDecoder(trackId, decoderConfig);\n }\n\n this.trackMetadata = {\n clipId: this.clipId,\n config: this.extractTrackConfig(metadata?.runtimeConfig),\n sampleRate: metadata?.sampleRate,\n numberOfChannels: metadata?.numberOfChannels,\n type: (metadata?.trackType as TrackMetadata['type']) ?? 'other',\n };\n\n const transform = this.decoder.createStream();\n this.channel.sendStream(transform.readable as ReadableStream<AudioData>, {\n streamType: 'audio',\n sessionId,\n trackId,\n clipStartUs: metadata?.clipStartUs ?? 0,\n clipDurationUs: metadata?.clipDurationUs ?? 0,\n trackMetadata: this.trackMetadata,\n });\n\n stream\n .pipeTo(transform.writable)\n .catch((error) => console.error('[AudioDecodeWorker] Audio stream pipe error:', error));\n }\n\n private extractTrackConfig(config: any): AudioTrackConfig {\n return {\n startTimeUs: config?.startTimeUs ?? 0,\n durationUs: config?.durationUs,\n volume: config?.volume ?? 1,\n fadeIn: config?.fadeIn,\n fadeOut: config?.fadeOut,\n effects: config?.effects ?? [],\n duckingTag: config?.duckingTag,\n };\n }\n\n private async handleFlush(): Promise<{ success: boolean }> {\n if (this.decoder) {\n await this.decoder.flush();\n }\n return { success: true };\n }\n\n private async handleReset(): Promise<{ success: boolean }> {\n if (this.decoder) {\n await this.decoder.reset();\n }\n\n this.channel.notify('reset_complete', {\n type: 'audio',\n });\n\n return { success: true };\n }\n\n private async handleGetStats(): Promise<{ audio?: any }> {\n if (!this.decoder) {\n return {};\n }\n\n return {\n audio: {\n clipId: this.clipId,\n trackId: this.trackId,\n configured: this.decoder.isConfigured,\n queueSize: this.decoder.queueSize,\n state: this.decoder.state,\n },\n };\n }\n\n private async handleDispose(): Promise<{ success: boolean }> {\n if (this.decoder) {\n await this.decoder.close();\n this.decoder = null;\n }\n\n this.upstreamPort?.close();\n this.upstreamPort = null;\n\n this.trackMetadata = null;\n\n this.channel.state = WorkerState.Disposed;\n\n return { success: true };\n }\n}\n\nconst worker = new AudioDecodeWorker();\n\nself.addEventListener('beforeunload', () => {\n worker['handleDispose']();\n});\n\nexport default null;\n"],"names":[],"mappings":"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"}
@@ -1,334 +0,0 @@
1
- import { W as WorkerChannel, a as WorkerMessageType, b as WorkerState } from "../../WorkerChannel.DQK8rAab.js";
2
- import { B as BaseDecoder } from "../../BaseDecoder.CB5XmTpS.js";
3
- let cachedPlatformInfo = null;
4
- function detectPlatform() {
5
- if (cachedPlatformInfo) {
6
- return cachedPlatformInfo;
7
- }
8
- const platform = typeof navigator !== "undefined" ? navigator.platform : "";
9
- const userAgent = typeof navigator !== "undefined" ? navigator.userAgent : "";
10
- cachedPlatformInfo = {
11
- isWindows: /Win/i.test(platform) || /Win/i.test(userAgent),
12
- isMacOS: /Mac/i.test(platform),
13
- isLinux: /Linux/i.test(platform),
14
- platform,
15
- userAgent
16
- };
17
- return cachedPlatformInfo;
18
- }
19
- function isWindows() {
20
- return detectPlatform().isWindows;
21
- }
22
- function getRecommendedHardwareAcceleration() {
23
- if (isWindows()) {
24
- return "prefer-software";
25
- }
26
- return "no-preference";
27
- }
28
- class VideoChunkDecoder extends BaseDecoder {
29
- static DEFAULT_HIGH_WATER_MARK = 4;
30
- static DEFAULT_DECODE_QUEUE_THRESHOLD = 16;
31
- trackId;
32
- highWaterMark;
33
- decodeQueueThreshold;
34
- // Buffering support for delayed configuration
35
- bufferedChunks = [];
36
- isProcessingBuffer = false;
37
- constructor(trackId, config) {
38
- super(config || {});
39
- this.trackId = trackId;
40
- this.highWaterMark = config?.backpressure?.highWaterMark ?? VideoChunkDecoder.DEFAULT_HIGH_WATER_MARK;
41
- this.decodeQueueThreshold = config?.backpressure?.decodeQueueThreshold ?? VideoChunkDecoder.DEFAULT_DECODE_QUEUE_THRESHOLD;
42
- }
43
- // Computed properties
44
- get isConfigured() {
45
- return this.isReady;
46
- }
47
- get state() {
48
- return this.decoder?.state || "unconfigured";
49
- }
50
- /**
51
- * Update configuration - can be called before or after initialization
52
- */
53
- async updateConfig(config) {
54
- if (!this.isReady && config.codec) {
55
- await this.configure(config);
56
- await this.processBufferedChunks();
57
- }
58
- }
59
- // Override createStream to handle GOP tracking and buffering
60
- // Always create new stream for each clip (ReadableStreams can only be consumed once)
61
- createStream() {
62
- return new TransformStream(
63
- {
64
- start: async (controller) => {
65
- this.controller = controller;
66
- if (this.config?.codec && this.config?.description && !this.isReady) {
67
- await this.initialize();
68
- }
69
- },
70
- transform: async (chunk) => {
71
- if (!this.isReady) {
72
- this.bufferedChunks.push(chunk);
73
- return;
74
- }
75
- if (this.isProcessingBuffer) {
76
- this.bufferedChunks.push(chunk);
77
- return;
78
- }
79
- await this.processChunk(chunk);
80
- },
81
- flush: async () => {
82
- if (this.isReady) {
83
- await this.flush();
84
- }
85
- }
86
- },
87
- {
88
- highWaterMark: this.highWaterMark,
89
- size: () => 1
90
- }
91
- );
92
- }
93
- /**
94
- * Process a single chunk (extracted from transform for reuse)
95
- */
96
- async processChunk(chunk) {
97
- if (!this.decoder) {
98
- throw new Error("Decoder not initialized");
99
- }
100
- if (this.decoder.state !== "configured") {
101
- console.error("[VideoChunkDecoder] Decoder in unexpected state:", this.decoder.state);
102
- throw new Error(`Decoder not configured, state: ${this.decoder.state}`);
103
- }
104
- this.decode(chunk);
105
- }
106
- // Override handleOutput to enqueue decoded frames
107
- handleOutput(frame) {
108
- super.handleOutput(frame);
109
- }
110
- // Implement abstract methods
111
- async isConfigSupported(config) {
112
- const result = await VideoDecoder.isConfigSupported({
113
- codec: config.codec,
114
- codedWidth: config.width,
115
- codedHeight: config.height
116
- });
117
- return { supported: result.supported ?? false };
118
- }
119
- createDecoder(init) {
120
- return new VideoDecoder(init);
121
- }
122
- getDecoderType() {
123
- return "Video";
124
- }
125
- async configureDecoder(config) {
126
- if (!this.decoder) return;
127
- const hardwareAcceleration = config.hardwareAcceleration ?? getRecommendedHardwareAcceleration();
128
- const decoderConfig = {
129
- codec: config.codec,
130
- codedWidth: config.width,
131
- codedHeight: config.height,
132
- hardwareAcceleration,
133
- optimizeForLatency: true,
134
- ...config.description && { description: config.description },
135
- ...config.displayAspectWidth && { displayAspectWidth: config.displayAspectWidth },
136
- ...config.displayAspectHeight && { displayAspectHeight: config.displayAspectHeight }
137
- };
138
- this.decoder.configure(decoderConfig);
139
- if (hardwareAcceleration === "prefer-software") {
140
- console.info("[VideoChunkDecoder] Using software decoding for platform compatibility");
141
- }
142
- }
143
- decode(chunk) {
144
- this.decoder?.decode(chunk);
145
- }
146
- /**
147
- * Configure the decoder with codec info (can be called after creation)
148
- */
149
- async configure(config) {
150
- if (this.isReady) {
151
- await this.reconfigure(config);
152
- return;
153
- }
154
- this.config = config;
155
- await this.initialize();
156
- }
157
- /**
158
- * Process any buffered chunks after configuration
159
- */
160
- async processBufferedChunks() {
161
- if (!this.isReady || this.bufferedChunks.length === 0) {
162
- return;
163
- }
164
- this.isProcessingBuffer = true;
165
- const chunks = [...this.bufferedChunks];
166
- this.bufferedChunks = [];
167
- for (const chunk of chunks) {
168
- try {
169
- await this.processChunk(chunk);
170
- } catch (error) {
171
- console.error("[VideoChunkDecoder] Error processing buffered chunk:", error);
172
- }
173
- }
174
- this.isProcessingBuffer = false;
175
- if (this.bufferedChunks.length > 0) {
176
- await this.processBufferedChunks();
177
- }
178
- }
179
- // Override close to clean up buffered chunks
180
- async close() {
181
- this.bufferedChunks = [];
182
- await super.close();
183
- }
184
- }
185
- const normalizeDescription = (desc) => {
186
- if (!desc) return void 0;
187
- if (desc instanceof ArrayBuffer) return desc;
188
- const view = desc;
189
- return view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength);
190
- };
191
- class VideoDecodeWorker {
192
- channel;
193
- decoder = null;
194
- clipId = "";
195
- defaultConfig = {};
196
- upstreamPort = null;
197
- downstreamPort = null;
198
- constructor() {
199
- this.channel = new WorkerChannel(self, {
200
- name: "VideoDecodeWorker",
201
- timeout: 3e4
202
- });
203
- this.setupHandlers();
204
- }
205
- setupHandlers() {
206
- this.channel.registerHandler("configure", this.handleConfigure.bind(this));
207
- this.channel.registerHandler("connect", this.handleConnect.bind(this));
208
- this.channel.registerHandler("flush", this.handleFlush.bind(this));
209
- this.channel.registerHandler("reset", this.handleReset.bind(this));
210
- this.channel.registerHandler("get_stats", this.handleGetStats.bind(this));
211
- this.channel.registerHandler(WorkerMessageType.Dispose, this.handleDispose.bind(this));
212
- }
213
- async handleConnect(payload) {
214
- const { port, direction, sessionId } = payload;
215
- if (direction === "upstream") {
216
- this.upstreamPort = port;
217
- this.clipId = sessionId || "default";
218
- const channel = new WorkerChannel(port, {
219
- name: "Demux-VideoDecode",
220
- timeout: 3e4
221
- });
222
- channel.receiveStream((stream, metadata) => {
223
- this.handleReceiveStream(stream, {
224
- ...metadata,
225
- clipStartUs: payload.clipStartUs,
226
- clipDurationUs: payload.clipDurationUs
227
- });
228
- });
229
- channel.registerHandler("configure", this.handleConfigure.bind(this));
230
- }
231
- if (direction === "downstream") {
232
- this.downstreamPort = port;
233
- }
234
- return { success: true };
235
- }
236
- async handleConfigure(payload) {
237
- const { sessionId, streamType, codec, width, height, description, config } = payload;
238
- if (sessionId && streamType === "video") {
239
- if (this.decoder) {
240
- await this.decoder.updateConfig({
241
- codec,
242
- width,
243
- height,
244
- description: normalizeDescription(description)
245
- });
246
- }
247
- return { success: true };
248
- }
249
- if (config?.video) {
250
- Object.assign(this.defaultConfig, config.video);
251
- if (this.decoder) {
252
- await this.decoder.updateConfig(config.video);
253
- }
254
- }
255
- this.channel.state = WorkerState.Ready;
256
- return { success: true };
257
- }
258
- async handleReceiveStream(stream, metadata) {
259
- const sessionId = metadata?.sessionId || this.clipId;
260
- if (!this.decoder) {
261
- this.decoder = new VideoChunkDecoder(sessionId, {
262
- ...this.defaultConfig,
263
- codec: metadata?.codec,
264
- width: metadata?.width,
265
- height: metadata?.height,
266
- description: normalizeDescription(metadata?.description)
267
- });
268
- }
269
- const transform = this.decoder.createStream();
270
- if (this.downstreamPort) {
271
- const channel = new WorkerChannel(this.downstreamPort, {
272
- name: "VideoDecode-Compose",
273
- timeout: 3e4
274
- });
275
- channel.sendStream(transform.readable, {
276
- streamType: "video",
277
- sessionId
278
- });
279
- stream.pipeTo(transform.writable).catch(
280
- (error) => console.error("[VideoDecodeWorker] Video stream pipe error:", sessionId, error)
281
- );
282
- }
283
- }
284
- async handleFlush() {
285
- if (this.decoder) {
286
- await this.decoder.flush();
287
- }
288
- return { success: true };
289
- }
290
- async handleReset() {
291
- if (this.decoder) {
292
- await this.decoder.reset();
293
- }
294
- this.channel.notify("reset_complete", {
295
- type: "video"
296
- });
297
- return { success: true };
298
- }
299
- async handleGetStats() {
300
- if (!this.decoder) {
301
- return {};
302
- }
303
- return {
304
- video: {
305
- clipId: this.clipId,
306
- configured: this.decoder.isConfigured,
307
- queueSize: this.decoder.queueSize,
308
- state: this.decoder.state
309
- }
310
- };
311
- }
312
- async handleDispose() {
313
- if (this.decoder) {
314
- await this.decoder.close();
315
- this.decoder = null;
316
- }
317
- this.upstreamPort?.close();
318
- this.upstreamPort = null;
319
- this.downstreamPort?.close();
320
- this.downstreamPort = null;
321
- this.channel.state = WorkerState.Disposed;
322
- return { success: true };
323
- }
324
- }
325
- const worker = new VideoDecodeWorker();
326
- self.addEventListener("beforeunload", () => {
327
- worker["handleDispose"]();
328
- });
329
- const videoDecode_worker = null;
330
- export {
331
- VideoDecodeWorker,
332
- videoDecode_worker as default
333
- };
334
- //# sourceMappingURL=video-decode.worker.BnWVUkng.js.map