@meframe/core
Advanced tools
| class BaseDecoder { | ||
| decoder; | ||
| config; | ||
| controller = null; | ||
| constructor(config) { | ||
| this.config = config; | ||
| } | ||
| async initialize() { | ||
| if (this.decoder?.state === "configured") { | ||
| return; | ||
| } | ||
| const isSupported = await this.isConfigSupported(this.config); | ||
| if (!isSupported.supported) { | ||
| throw new Error( | ||
| `Codec not supported: ${this.config.codecString || this.config.codec}` | ||
| ); | ||
| } | ||
| this.decoder = this.createDecoder({ | ||
| output: this.handleOutput.bind(this), | ||
| error: this.handleError.bind(this) | ||
| }); | ||
| await this.configureDecoder(this.config); | ||
| } | ||
| async reconfigure(config) { | ||
| this.config = { ...this.config, ...config }; | ||
| if (!this.decoder) { | ||
| await this.initialize(); | ||
| return; | ||
| } | ||
| if (this.decoder.state === "configured") { | ||
| await this.decoder.flush(); | ||
| } | ||
| const isSupported = await this.isConfigSupported(this.config); | ||
| if (!isSupported.supported) { | ||
| throw new Error( | ||
| `New configuration not supported: ${this.config.codecString || this.config.codec}` | ||
| ); | ||
| } | ||
| await this.configureDecoder(this.config); | ||
| } | ||
| async flush() { | ||
| if (!this.decoder) return; | ||
| await this.decoder.flush(); | ||
| } | ||
| async reset() { | ||
| if (!this.decoder) return; | ||
| this.decoder.reset(); | ||
| this.onReset(); | ||
| } | ||
| async close() { | ||
| if (!this.decoder) return; | ||
| if (this.decoder.state === "configured") { | ||
| await this.decoder.flush(); | ||
| } | ||
| this.decoder.close(); | ||
| this.decoder = void 0; | ||
| } | ||
| get isReady() { | ||
| return this.decoder?.state === "configured"; | ||
| } | ||
| get queueSize() { | ||
| return this.decoder?.decodeQueueSize ?? 0; | ||
| } | ||
| handleOutput(data) { | ||
| if (!this.controller) { | ||
| this.closeIfPossible(data); | ||
| return; | ||
| } | ||
| try { | ||
| this.controller.enqueue(data); | ||
| if (data instanceof VideoFrame) { | ||
| setTimeout(() => { | ||
| data.close(); | ||
| }, 500); | ||
| } | ||
| } catch (error) { | ||
| if (error instanceof TypeError) { | ||
| this.closeIfPossible(data); | ||
| return; | ||
| } | ||
| throw error; | ||
| } | ||
| } | ||
| handleError(error) { | ||
| console.error(`${this.getDecoderType()} decoder error:`, error); | ||
| this.controller?.error(error); | ||
| } | ||
| closeIfPossible(data) { | ||
| data?.close?.(); | ||
| data?.frame?.close?.(); | ||
| } | ||
| onReset() { | ||
| } | ||
| createStream() { | ||
| return new TransformStream( | ||
| { | ||
| start: async (controller) => { | ||
| this.controller = controller; | ||
| if (!this.isReady) { | ||
| await this.initialize(); | ||
| } | ||
| }, | ||
| transform: async (input) => { | ||
| if (!this.decoder || this.decoder.state !== "configured") { | ||
| throw new Error("Decoder not configured"); | ||
| } | ||
| if (this.decoder.decodeQueueSize >= this.decodeQueueThreshold) { | ||
| await new Promise((resolve) => { | ||
| const check = () => { | ||
| if (!this.decoder || this.decoder.decodeQueueSize < this.decodeQueueThreshold - 1) { | ||
| resolve(); | ||
| } else { | ||
| setTimeout(check, 10); | ||
| } | ||
| }; | ||
| check(); | ||
| }); | ||
| } | ||
| this.decode(input); | ||
| }, | ||
| flush: async () => { | ||
| await this.flush(); | ||
| } | ||
| }, | ||
| { | ||
| highWaterMark: this.highWaterMark, | ||
| size: () => 1 | ||
| } | ||
| ); | ||
| } | ||
| } | ||
| export { | ||
| BaseDecoder as B | ||
| }; | ||
| //# sourceMappingURL=BaseDecoder.BGsRDny7.js.map |
| {"version":3,"file":"BaseDecoder.BGsRDny7.js","sources":["../../src/stages/decode/BaseDecoder.ts"],"sourcesContent":["/**\n * Base decoder class abstracting common WebCodecs decoder workflow.\n * Mirrors BaseEncoder but for decoding path.\n */\nimport { AudioDecoderConfig, DecodedVideoFrame, VideoDecoderConfig } from './types';\n\nexport abstract class BaseDecoder<\n TDecoder extends VideoDecoder | AudioDecoder,\n TConfig extends VideoDecoderConfig | AudioDecoderConfig,\n TInput extends EncodedVideoChunk | EncodedAudioChunk,\n TOutput extends VideoFrame | AudioData | DecodedVideoFrame,\n> {\n protected decoder?: TDecoder;\n protected config: TConfig;\n protected controller: TransformStreamDefaultController<TOutput> | null = null;\n\n constructor(config: TConfig) {\n this.config = config;\n }\n\n async initialize(): Promise<void> {\n if (this.decoder?.state === 'configured') {\n return;\n }\n const isSupported = await this.isConfigSupported(this.config);\n if (!isSupported.supported) {\n throw new Error(\n `Codec not supported: ${(this.config as any).codecString || (this.config as any).codec}`\n );\n }\n\n this.decoder = this.createDecoder({\n output: this.handleOutput.bind(this),\n error: this.handleError.bind(this),\n });\n\n await this.configureDecoder(this.config);\n }\n\n async reconfigure(config: Partial<TConfig>): Promise<void> {\n this.config = { ...this.config, ...config } as TConfig;\n\n if (!this.decoder) {\n await this.initialize();\n return;\n }\n\n // Flush before reconfiguring\n if (this.decoder.state === 'configured') {\n await this.decoder.flush();\n }\n\n const isSupported = await this.isConfigSupported(this.config);\n if (!isSupported.supported) {\n throw new Error(\n `New configuration not supported: ${(this.config as any).codecString || (this.config as any).codec}`\n );\n }\n\n await this.configureDecoder(this.config);\n }\n\n async flush(): Promise<void> {\n if (!this.decoder) return;\n await this.decoder.flush();\n }\n\n async reset(): Promise<void> {\n if (!this.decoder) return;\n this.decoder.reset();\n this.onReset();\n }\n\n async close(): Promise<void> {\n if (!this.decoder) return;\n\n if (this.decoder.state === 'configured') {\n await this.decoder.flush();\n }\n\n this.decoder.close();\n this.decoder = undefined;\n }\n\n get isReady(): boolean {\n return this.decoder?.state === 'configured';\n }\n\n get queueSize(): number {\n return (this.decoder as any)?.decodeQueueSize ?? 0;\n }\n\n protected handleOutput(data: TOutput): void {\n if (!this.controller) {\n this.closeIfPossible(data);\n return;\n }\n\n try {\n this.controller.enqueue(data);\n if (data instanceof VideoFrame) {\n // ugly hack to avoid memory leak\n setTimeout(() => {\n data.close();\n }, 500);\n }\n } catch (error) {\n // Stream closed or errored - silently drop frame\n if (error instanceof TypeError) {\n this.closeIfPossible(data);\n return;\n }\n\n throw error;\n }\n }\n\n protected handleError(error: DOMException): void {\n console.error(`${this.getDecoderType()} decoder error:`, error);\n this.controller?.error(error);\n }\n\n private closeIfPossible(data: TOutput): void {\n (data as any)?.close?.();\n (data as DecodedVideoFrame)?.frame?.close?.();\n }\n\n // Abstracts\n protected abstract isConfigSupported(config: TConfig): Promise<{ supported: boolean }>;\n protected abstract createDecoder(init: DecoderInit): TDecoder;\n protected abstract configureDecoder(config: TConfig): Promise<void>;\n protected abstract getDecoderType(): string;\n protected abstract decode(input: TInput): void;\n protected onReset(): void {}\n\n // Backpressure\n protected abstract readonly highWaterMark: number;\n protected abstract readonly decodeQueueThreshold: number;\n\n createStream(): TransformStream<TInput, TOutput> {\n return new TransformStream<TInput, TOutput>(\n {\n start: async (controller) => {\n this.controller = controller;\n if (!this.isReady) {\n await this.initialize();\n }\n },\n transform: async (input) => {\n if (!this.decoder || this.decoder.state !== 'configured') {\n throw new Error('Decoder not configured');\n }\n // backpressure\n if ((this.decoder as any).decodeQueueSize >= this.decodeQueueThreshold) {\n await new Promise<void>((resolve) => {\n const check = () => {\n if (\n !this.decoder ||\n (this.decoder as any).decodeQueueSize < this.decodeQueueThreshold - 1\n ) {\n resolve();\n } else {\n setTimeout(check, 10);\n }\n };\n check();\n });\n }\n this.decode(input);\n },\n flush: async () => {\n await this.flush();\n },\n },\n {\n highWaterMark: this.highWaterMark,\n size: () => 1,\n }\n );\n }\n}\n\ninterface DecoderInit {\n output: (data: any) => void;\n error: (error: DOMException) => void;\n}\n"],"names":[],"mappings":"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"} |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
| import { W as WorkerChannel, a as WorkerMessageType, b as WorkerState } from "../../WorkerChannel.CE5euh3R.js"; | ||
| import { B as BaseDecoder } from "../../BaseDecoder.BGsRDny7.js"; | ||
| class AudioChunkDecoder extends BaseDecoder { | ||
| // Default values | ||
| static DEFAULT_HIGH_WATER_MARK = 20; | ||
| static DEFAULT_DECODE_QUEUE_THRESHOLD = 16; | ||
| // Exposed properties | ||
| trackId; | ||
| // Backpressure configuration | ||
| highWaterMark; | ||
| decodeQueueThreshold; | ||
| // Buffering support for delayed configuration | ||
| bufferedChunks = []; | ||
| isProcessingBuffer = false; | ||
| constructor(trackId, config) { | ||
| super(config); | ||
| this.trackId = trackId; | ||
| this.highWaterMark = config?.backpressure?.highWaterMark ?? AudioChunkDecoder.DEFAULT_HIGH_WATER_MARK; | ||
| this.decodeQueueThreshold = AudioChunkDecoder.DEFAULT_DECODE_QUEUE_THRESHOLD; | ||
| } | ||
| // Computed properties | ||
| get isConfigured() { | ||
| return this.isReady; | ||
| } | ||
| get state() { | ||
| return this.decoder?.state || "unconfigured"; | ||
| } | ||
| /** | ||
| * Update configuration - can be called before or after initialization | ||
| */ | ||
| async updateConfig(config) { | ||
| if (!this.isReady && config.codec) { | ||
| await this.configure(config); | ||
| await this.processBufferedChunks(); | ||
| return; | ||
| } | ||
| } | ||
| // Override createStream to handle buffering | ||
| createStream() { | ||
| return new TransformStream( | ||
| { | ||
| start: async (controller) => { | ||
| this.controller = controller; | ||
| if (this.config?.codec && !this.isReady) { | ||
| await this.initialize(); | ||
| } | ||
| }, | ||
| transform: async (chunk) => { | ||
| if (!this.isReady) { | ||
| this.bufferedChunks.push(chunk); | ||
| return; | ||
| } | ||
| if (this.isProcessingBuffer) { | ||
| this.bufferedChunks.push(chunk); | ||
| return; | ||
| } | ||
| await this.processChunk(chunk); | ||
| }, | ||
| flush: async () => { | ||
| if (this.isReady) { | ||
| await this.flush(); | ||
| } | ||
| } | ||
| }, | ||
| { | ||
| highWaterMark: this.highWaterMark, | ||
| size: () => 1 | ||
| } | ||
| ); | ||
| } | ||
| /** | ||
| * Process a single chunk (extracted from transform for reuse) | ||
| */ | ||
| async processChunk(chunk) { | ||
| if (!this.decoder) { | ||
| throw new Error("Decoder not initialized"); | ||
| } | ||
| if (this.decoder.state !== "configured") { | ||
| console.error("[AudioChunkDecoder] Decoder in unexpected state:", this.decoder.state); | ||
| throw new Error(`Decoder not configured, state: ${this.decoder.state}`); | ||
| } | ||
| if (this.decoder.decodeQueueSize >= this.decodeQueueThreshold) { | ||
| await new Promise((resolve) => { | ||
| const check = () => { | ||
| if (!this.decoder || this.decoder.decodeQueueSize < this.decodeQueueThreshold - 1) { | ||
| resolve(); | ||
| } else { | ||
| setTimeout(check, 20); | ||
| } | ||
| }; | ||
| check(); | ||
| }); | ||
| } | ||
| try { | ||
| this.decode(chunk); | ||
| } catch (error) { | ||
| console.error(`[AudioChunkDecoder] decode error:`, error); | ||
| throw error; | ||
| } | ||
| } | ||
| // Implement abstract methods | ||
| async isConfigSupported(config) { | ||
| const result = await AudioDecoder.isConfigSupported({ | ||
| codec: config.codec, | ||
| sampleRate: config.sampleRate, | ||
| numberOfChannels: config.numberOfChannels | ||
| }); | ||
| return { supported: result.supported ?? false }; | ||
| } | ||
| createDecoder(init) { | ||
| return new AudioDecoder(init); | ||
| } | ||
| getDecoderType() { | ||
| return "Audio"; | ||
| } | ||
| async configureDecoder(config) { | ||
| if (!this.decoder) return; | ||
| await this.decoder.configure({ | ||
| codec: config.codec, | ||
| sampleRate: config.sampleRate, | ||
| numberOfChannels: config.numberOfChannels, | ||
| ...config.description && { description: config.description } | ||
| }); | ||
| } | ||
| decode(chunk) { | ||
| this.decoder?.decode(chunk); | ||
| } | ||
| /** | ||
| * Configure the decoder with codec info (can be called after creation) | ||
| */ | ||
| async configure(config) { | ||
| if (this.isReady) { | ||
| await this.reconfigure(config); | ||
| return; | ||
| } | ||
| this.config = config; | ||
| await this.initialize(); | ||
| } | ||
| /** | ||
| * Process any buffered chunks after configuration | ||
| */ | ||
| async processBufferedChunks() { | ||
| if (!this.isReady || this.bufferedChunks.length === 0) { | ||
| return; | ||
| } | ||
| this.isProcessingBuffer = true; | ||
| const chunks = [...this.bufferedChunks]; | ||
| this.bufferedChunks = []; | ||
| for (const chunk of chunks) { | ||
| try { | ||
| await this.processChunk(chunk); | ||
| } catch (error) { | ||
| console.error("[AudioChunkDecoder] Error processing buffered chunk:", error); | ||
| } | ||
| } | ||
| this.isProcessingBuffer = false; | ||
| if (this.bufferedChunks.length > 0) { | ||
| await this.processBufferedChunks(); | ||
| } | ||
| } | ||
| // Override close to clean up buffered chunks | ||
| async close() { | ||
| this.bufferedChunks = []; | ||
| await super.close(); | ||
| } | ||
| } | ||
| const normalizeDescription = (desc) => { | ||
| if (!desc) return void 0; | ||
| if (desc instanceof ArrayBuffer) return desc; | ||
| const view = desc; | ||
| return view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength); | ||
| }; | ||
| class AudioDecodeWorker { | ||
| channel; | ||
| decoder = null; | ||
| clipId = ""; | ||
| trackId = ""; | ||
| defaultConfig = {}; | ||
| trackMetadata = null; | ||
| upstreamPort = null; | ||
| constructor() { | ||
| this.channel = new WorkerChannel(self, { | ||
| name: "AudioDecodeWorker", | ||
| timeout: 3e4 | ||
| }); | ||
| this.setupHandlers(); | ||
| } | ||
| setupHandlers() { | ||
| this.channel.registerHandler("configure", this.handleConfigure.bind(this)); | ||
| this.channel.registerHandler("connect", this.handleConnect.bind(this)); | ||
| this.channel.registerHandler("flush", this.handleFlush.bind(this)); | ||
| this.channel.registerHandler("reset", this.handleReset.bind(this)); | ||
| this.channel.registerHandler("get_stats", this.handleGetStats.bind(this)); | ||
| this.channel.registerHandler(WorkerMessageType.Dispose, this.handleDispose.bind(this)); | ||
| } | ||
| async handleConnect(payload) { | ||
| const { port, direction, sessionId, trackId } = payload; | ||
| if (direction === "upstream") { | ||
| this.upstreamPort = port; | ||
| this.clipId = sessionId || "default"; | ||
| this.trackId = trackId || sessionId || "default"; | ||
| const channel = new WorkerChannel(port, { | ||
| name: "Demux-AudioDecode", | ||
| timeout: 3e4 | ||
| }); | ||
| channel.receiveStream((stream, metadata) => { | ||
| this.handleReceiveStream(stream, { | ||
| ...metadata, | ||
| clipStartUs: payload.clipStartUs, | ||
| clipDurationUs: payload.clipDurationUs | ||
| }); | ||
| }); | ||
| channel.registerHandler("configure", this.handleConfigure.bind(this)); | ||
| } | ||
| return { success: true }; | ||
| } | ||
| async handleConfigure(payload) { | ||
| const { sessionId, streamType, codec, sampleRate, numberOfChannels, description, config } = payload; | ||
| if (sessionId && streamType === "audio") { | ||
| Object.assign(this.defaultConfig, { | ||
| codec, | ||
| sampleRate, | ||
| numberOfChannels, | ||
| description: normalizeDescription(description) | ||
| }); | ||
| if (this.decoder) { | ||
| await this.decoder.updateConfig({ | ||
| codec, | ||
| sampleRate, | ||
| numberOfChannels, | ||
| description: normalizeDescription(description) | ||
| }); | ||
| } | ||
| return { success: true }; | ||
| } | ||
| if (config?.audio) { | ||
| Object.assign(this.defaultConfig, config.audio); | ||
| if (this.decoder) { | ||
| await this.decoder.updateConfig(config.audio); | ||
| } | ||
| } | ||
| this.channel.state = WorkerState.Ready; | ||
| return { success: true }; | ||
| } | ||
| async handleReceiveStream(stream, metadata) { | ||
| const sessionId = metadata?.sessionId || this.clipId; | ||
| const trackId = metadata?.trackId || this.trackId; | ||
| if (!this.decoder) { | ||
| const decoderConfig = { | ||
| ...this.defaultConfig, | ||
| codec: metadata?.codec ?? this.defaultConfig.codec, | ||
| sampleRate: metadata?.sampleRate ?? this.defaultConfig.sampleRate, | ||
| numberOfChannels: metadata?.numberOfChannels ?? this.defaultConfig.numberOfChannels, | ||
| description: normalizeDescription(metadata?.description) ?? this.defaultConfig.description | ||
| }; | ||
| this.decoder = new AudioChunkDecoder(trackId, decoderConfig); | ||
| } | ||
| this.trackMetadata = { | ||
| clipId: this.clipId, | ||
| config: this.extractTrackConfig(metadata?.runtimeConfig), | ||
| sampleRate: metadata?.sampleRate, | ||
| numberOfChannels: metadata?.numberOfChannels, | ||
| type: metadata?.trackType ?? "other" | ||
| }; | ||
| const transform = this.decoder.createStream(); | ||
| this.channel.sendStream(transform.readable, { | ||
| streamType: "audio", | ||
| sessionId, | ||
| trackId, | ||
| clipStartUs: metadata?.clipStartUs ?? 0, | ||
| clipDurationUs: metadata?.clipDurationUs ?? 0, | ||
| trackMetadata: this.trackMetadata | ||
| }); | ||
| stream.pipeTo(transform.writable).catch((error) => console.error("[AudioDecodeWorker] Audio stream pipe error:", error)); | ||
| } | ||
| extractTrackConfig(config) { | ||
| return { | ||
| startTimeUs: config?.startTimeUs ?? 0, | ||
| durationUs: config?.durationUs, | ||
| volume: config?.volume ?? 1, | ||
| fadeIn: config?.fadeIn, | ||
| fadeOut: config?.fadeOut, | ||
| effects: config?.effects ?? [], | ||
| duckingTag: config?.duckingTag | ||
| }; | ||
| } | ||
| async handleFlush() { | ||
| if (this.decoder) { | ||
| await this.decoder.flush(); | ||
| } | ||
| return { success: true }; | ||
| } | ||
| async handleReset() { | ||
| if (this.decoder) { | ||
| await this.decoder.reset(); | ||
| } | ||
| this.channel.notify("reset_complete", { | ||
| type: "audio" | ||
| }); | ||
| return { success: true }; | ||
| } | ||
| async handleGetStats() { | ||
| if (!this.decoder) { | ||
| return {}; | ||
| } | ||
| return { | ||
| audio: { | ||
| clipId: this.clipId, | ||
| trackId: this.trackId, | ||
| configured: this.decoder.isConfigured, | ||
| queueSize: this.decoder.queueSize, | ||
| state: this.decoder.state | ||
| } | ||
| }; | ||
| } | ||
| async handleDispose() { | ||
| if (this.decoder) { | ||
| await this.decoder.close(); | ||
| this.decoder = null; | ||
| } | ||
| this.upstreamPort?.close(); | ||
| this.upstreamPort = null; | ||
| this.trackMetadata = null; | ||
| this.channel.state = WorkerState.Disposed; | ||
| return { success: true }; | ||
| } | ||
| } | ||
| const worker = new AudioDecodeWorker(); | ||
| self.addEventListener("beforeunload", () => { | ||
| worker["handleDispose"](); | ||
| }); | ||
| const audioDecode_worker = null; | ||
| export { | ||
| AudioDecodeWorker, | ||
| audioDecode_worker as default | ||
| }; | ||
| //# sourceMappingURL=audio-decode.worker.3SD5i_Oe.js.map |
| {"version":3,"file":"audio-decode.worker.3SD5i_Oe.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 // 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","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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"} |
| import { W as WorkerChannel, a as WorkerMessageType, b as WorkerState } from "../../WorkerChannel.CE5euh3R.js"; | ||
| import { B as BaseDecoder } from "../../BaseDecoder.BGsRDny7.js"; | ||
| class VideoChunkDecoder extends BaseDecoder { | ||
| static DEFAULT_HIGH_WATER_MARK = 4; | ||
| static DEFAULT_DECODE_QUEUE_THRESHOLD = 16; | ||
| trackId; | ||
| highWaterMark; | ||
| decodeQueueThreshold; | ||
| // Buffering support for delayed configuration | ||
| bufferedChunks = []; | ||
| isProcessingBuffer = false; | ||
| constructor(trackId, config) { | ||
| super(config || {}); | ||
| this.trackId = trackId; | ||
| this.highWaterMark = config?.backpressure?.highWaterMark ?? VideoChunkDecoder.DEFAULT_HIGH_WATER_MARK; | ||
| this.decodeQueueThreshold = config?.backpressure?.decodeQueueThreshold ?? VideoChunkDecoder.DEFAULT_DECODE_QUEUE_THRESHOLD; | ||
| } | ||
| // Computed properties | ||
| get isConfigured() { | ||
| return this.isReady; | ||
| } | ||
| get state() { | ||
| return this.decoder?.state || "unconfigured"; | ||
| } | ||
| /** | ||
| * Update configuration - can be called before or after initialization | ||
| */ | ||
| async updateConfig(config) { | ||
| if (!this.isReady && config.codec) { | ||
| await this.configure(config); | ||
| await this.processBufferedChunks(); | ||
| } | ||
| } | ||
| // Override createStream to handle GOP tracking and buffering | ||
| // Always create new stream for each clip (ReadableStreams can only be consumed once) | ||
| createStream() { | ||
| return new TransformStream( | ||
| { | ||
| start: async (controller) => { | ||
| this.controller = controller; | ||
| if (this.config?.codec && this.config?.description && !this.isReady) { | ||
| await this.initialize(); | ||
| } | ||
| }, | ||
| transform: async (chunk) => { | ||
| if (!this.isReady) { | ||
| this.bufferedChunks.push(chunk); | ||
| return; | ||
| } | ||
| if (this.isProcessingBuffer) { | ||
| this.bufferedChunks.push(chunk); | ||
| return; | ||
| } | ||
| await this.processChunk(chunk); | ||
| }, | ||
| flush: async () => { | ||
| if (this.isReady) { | ||
| await this.flush(); | ||
| } | ||
| } | ||
| }, | ||
| { | ||
| highWaterMark: this.highWaterMark, | ||
| size: () => 1 | ||
| } | ||
| ); | ||
| } | ||
| /** | ||
| * Process a single chunk (extracted from transform for reuse) | ||
| */ | ||
| async processChunk(chunk) { | ||
| if (!this.decoder) { | ||
| throw new Error("Decoder not initialized"); | ||
| } | ||
| if (this.decoder.state !== "configured") { | ||
| console.error("[VideoChunkDecoder] Decoder in unexpected state:", this.decoder.state); | ||
| throw new Error(`Decoder not configured, state: ${this.decoder.state}`); | ||
| } | ||
| if (this.decoder.decodeQueueSize >= this.decodeQueueThreshold) { | ||
| await new Promise((resolve) => { | ||
| const check = () => { | ||
| if (!this.decoder || this.decoder.decodeQueueSize < this.decodeQueueThreshold - 1) { | ||
| resolve(); | ||
| } else { | ||
| setTimeout(check, 20); | ||
| } | ||
| }; | ||
| check(); | ||
| }); | ||
| } | ||
| try { | ||
| this.decode(chunk); | ||
| } catch (error) { | ||
| console.error(`[VideoChunkDecoder] decode error:`, error); | ||
| throw error; | ||
| } | ||
| } | ||
| // Override handleOutput to enqueue decoded frames | ||
| handleOutput(frame) { | ||
| super.handleOutput(frame); | ||
| } | ||
| // Implement abstract methods | ||
| async isConfigSupported(config) { | ||
| const result = await VideoDecoder.isConfigSupported({ | ||
| codec: config.codec, | ||
| codedWidth: config.width, | ||
| codedHeight: config.height | ||
| }); | ||
| return { supported: result.supported ?? false }; | ||
| } | ||
| createDecoder(init) { | ||
| return new VideoDecoder(init); | ||
| } | ||
| getDecoderType() { | ||
| return "Video"; | ||
| } | ||
| async configureDecoder(config) { | ||
| if (!this.decoder) return; | ||
| const decoderConfig = { | ||
| codec: config.codec, | ||
| codedWidth: config.width, | ||
| codedHeight: config.height, | ||
| hardwareAcceleration: config.hardwareAcceleration || "no-preference", | ||
| optimizeForLatency: false, | ||
| ...config.description && { description: config.description }, | ||
| ...config.displayAspectWidth && { displayAspectWidth: config.displayAspectWidth }, | ||
| ...config.displayAspectHeight && { displayAspectHeight: config.displayAspectHeight } | ||
| }; | ||
| this.decoder.configure(decoderConfig); | ||
| } | ||
| decode(chunk) { | ||
| this.decoder?.decode(chunk); | ||
| } | ||
| /** | ||
| * Configure the decoder with codec info (can be called after creation) | ||
| */ | ||
| async configure(config) { | ||
| if (this.isReady) { | ||
| await this.reconfigure(config); | ||
| return; | ||
| } | ||
| this.config = config; | ||
| await this.initialize(); | ||
| } | ||
| /** | ||
| * Process any buffered chunks after configuration | ||
| */ | ||
| async processBufferedChunks() { | ||
| if (!this.isReady || this.bufferedChunks.length === 0) { | ||
| return; | ||
| } | ||
| this.isProcessingBuffer = true; | ||
| const chunks = [...this.bufferedChunks]; | ||
| this.bufferedChunks = []; | ||
| for (const chunk of chunks) { | ||
| try { | ||
| await this.processChunk(chunk); | ||
| } catch (error) { | ||
| console.error("[VideoChunkDecoder] Error processing buffered chunk:", error); | ||
| } | ||
| } | ||
| this.isProcessingBuffer = false; | ||
| if (this.bufferedChunks.length > 0) { | ||
| await this.processBufferedChunks(); | ||
| } | ||
| } | ||
| // Override close to clean up buffered chunks | ||
| async close() { | ||
| this.bufferedChunks = []; | ||
| await super.close(); | ||
| } | ||
| } | ||
| const normalizeDescription = (desc) => { | ||
| if (!desc) return void 0; | ||
| if (desc instanceof ArrayBuffer) return desc; | ||
| const view = desc; | ||
| return view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength); | ||
| }; | ||
| class VideoDecodeWorker { | ||
| channel; | ||
| decoder = null; | ||
| clipId = ""; | ||
| defaultConfig = {}; | ||
| upstreamPort = null; | ||
| downstreamPort = null; | ||
| constructor() { | ||
| this.channel = new WorkerChannel(self, { | ||
| name: "VideoDecodeWorker", | ||
| timeout: 3e4 | ||
| }); | ||
| this.setupHandlers(); | ||
| } | ||
| setupHandlers() { | ||
| this.channel.registerHandler("configure", this.handleConfigure.bind(this)); | ||
| this.channel.registerHandler("connect", this.handleConnect.bind(this)); | ||
| this.channel.registerHandler("flush", this.handleFlush.bind(this)); | ||
| this.channel.registerHandler("reset", this.handleReset.bind(this)); | ||
| this.channel.registerHandler("get_stats", this.handleGetStats.bind(this)); | ||
| this.channel.registerHandler(WorkerMessageType.Dispose, this.handleDispose.bind(this)); | ||
| } | ||
| async handleConnect(payload) { | ||
| const { port, direction, sessionId } = payload; | ||
| if (direction === "upstream") { | ||
| this.upstreamPort = port; | ||
| this.clipId = sessionId || "default"; | ||
| const channel = new WorkerChannel(port, { | ||
| name: "Demux-VideoDecode", | ||
| timeout: 3e4 | ||
| }); | ||
| channel.receiveStream((stream, metadata) => { | ||
| this.handleReceiveStream(stream, { | ||
| ...metadata, | ||
| clipStartUs: payload.clipStartUs, | ||
| clipDurationUs: payload.clipDurationUs | ||
| }); | ||
| }); | ||
| channel.registerHandler("configure", this.handleConfigure.bind(this)); | ||
| } | ||
| if (direction === "downstream") { | ||
| this.downstreamPort = port; | ||
| } | ||
| return { success: true }; | ||
| } | ||
| async handleConfigure(payload) { | ||
| const { sessionId, streamType, codec, width, height, description, config } = payload; | ||
| if (sessionId && streamType === "video") { | ||
| if (this.decoder) { | ||
| await this.decoder.updateConfig({ | ||
| codec, | ||
| width, | ||
| height, | ||
| description: normalizeDescription(description) | ||
| }); | ||
| } | ||
| return { success: true }; | ||
| } | ||
| if (config?.video) { | ||
| Object.assign(this.defaultConfig, config.video); | ||
| if (this.decoder) { | ||
| await this.decoder.updateConfig(config.video); | ||
| } | ||
| } | ||
| this.channel.state = WorkerState.Ready; | ||
| return { success: true }; | ||
| } | ||
| async handleReceiveStream(stream, metadata) { | ||
| const sessionId = metadata?.sessionId || this.clipId; | ||
| if (!this.decoder) { | ||
| this.decoder = new VideoChunkDecoder(sessionId, { | ||
| ...this.defaultConfig, | ||
| codec: metadata?.codec, | ||
| width: metadata?.width, | ||
| height: metadata?.height, | ||
| description: normalizeDescription(metadata?.description) | ||
| }); | ||
| } | ||
| const transform = this.decoder.createStream(); | ||
| if (this.downstreamPort) { | ||
| const channel = new WorkerChannel(this.downstreamPort, { | ||
| name: "VideoDecode-Compose", | ||
| timeout: 3e4 | ||
| }); | ||
| channel.sendStream(transform.readable, { | ||
| streamType: "video", | ||
| sessionId | ||
| }); | ||
| stream.pipeTo(transform.writable).catch( | ||
| (error) => console.error("[VideoDecodeWorker] Video stream pipe error:", sessionId, error) | ||
| ); | ||
| } | ||
| } | ||
| async handleFlush() { | ||
| if (this.decoder) { | ||
| await this.decoder.flush(); | ||
| } | ||
| return { success: true }; | ||
| } | ||
| async handleReset() { | ||
| if (this.decoder) { | ||
| await this.decoder.reset(); | ||
| } | ||
| this.channel.notify("reset_complete", { | ||
| type: "video" | ||
| }); | ||
| return { success: true }; | ||
| } | ||
| async handleGetStats() { | ||
| if (!this.decoder) { | ||
| return {}; | ||
| } | ||
| return { | ||
| video: { | ||
| clipId: this.clipId, | ||
| configured: this.decoder.isConfigured, | ||
| queueSize: this.decoder.queueSize, | ||
| state: this.decoder.state | ||
| } | ||
| }; | ||
| } | ||
| async handleDispose() { | ||
| if (this.decoder) { | ||
| await this.decoder.close(); | ||
| this.decoder = null; | ||
| } | ||
| this.upstreamPort?.close(); | ||
| this.upstreamPort = null; | ||
| this.downstreamPort?.close(); | ||
| this.downstreamPort = null; | ||
| this.channel.state = WorkerState.Disposed; | ||
| return { success: true }; | ||
| } | ||
| } | ||
| const worker = new VideoDecodeWorker(); | ||
| self.addEventListener("beforeunload", () => { | ||
| worker["handleDispose"](); | ||
| }); | ||
| const videoDecode_worker = null; | ||
| export { | ||
| VideoDecodeWorker, | ||
| videoDecode_worker as default | ||
| }; | ||
| //# sourceMappingURL=video-decode.worker.BAB6216v.js.map |
| {"version":3,"file":"video-decode.worker.BAB6216v.js","sources":["../../../../src/stages/decode/VideoChunkDecoder.ts","../../../../src/stages/decode/video-decode.worker.ts"],"sourcesContent":["import { VideoDecoderConfig } from './types';\nimport { BaseDecoder } from './BaseDecoder';\n\n/**\n * Video decoder with GOP tracking\n * Tracks keyframe boundaries and attaches GOP metadata to decoded frames\n */\nexport class VideoChunkDecoder extends BaseDecoder<\n VideoDecoder,\n VideoDecoderConfig,\n EncodedVideoChunk,\n VideoFrame\n> {\n private static readonly DEFAULT_HIGH_WATER_MARK = 4;\n private static readonly DEFAULT_DECODE_QUEUE_THRESHOLD = 16;\n\n readonly trackId: string;\n\n protected readonly highWaterMark: number;\n protected readonly decodeQueueThreshold: number;\n\n // Buffering support for delayed configuration\n private bufferedChunks: EncodedVideoChunk[] = [];\n private isProcessingBuffer: boolean = false;\n\n constructor(trackId: string, config?: Partial<VideoDecoderConfig>) {\n super((config || {}) as VideoDecoderConfig);\n\n this.trackId = trackId;\n this.highWaterMark =\n config?.backpressure?.highWaterMark ?? VideoChunkDecoder.DEFAULT_HIGH_WATER_MARK;\n this.decodeQueueThreshold =\n config?.backpressure?.decodeQueueThreshold ??\n VideoChunkDecoder.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<VideoDecoderConfig>): Promise<void> {\n if (!this.isReady && config.codec) {\n await this.configure(config as VideoDecoderConfig);\n await this.processBufferedChunks();\n }\n }\n\n // Override createStream to handle GOP tracking and buffering\n // Always create new stream for each clip (ReadableStreams can only be consumed once)\n override createStream(): TransformStream<EncodedVideoChunk, VideoFrame> {\n return new TransformStream<EncodedVideoChunk, VideoFrame>(\n {\n start: async (controller) => {\n this.controller = controller;\n // Don't initialize if no codec config yet\n if (this.config?.codec && this.config?.description && !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: EncodedVideoChunk): Promise<void> {\n if (!this.decoder) {\n throw new Error('Decoder not initialized');\n }\n\n if (this.decoder.state !== 'configured') {\n console.error('[VideoChunkDecoder] 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(`[VideoChunkDecoder] decode error:`, error);\n throw error; // Re-throw to propagate\n }\n }\n\n // Override handleOutput to enqueue decoded frames\n protected override handleOutput(frame: VideoFrame): void {\n super.handleOutput(frame);\n }\n\n // Implement abstract methods\n protected async isConfigSupported(config: VideoDecoderConfig): Promise<{ supported: boolean }> {\n const result = await VideoDecoder.isConfigSupported({\n codec: config.codec,\n codedWidth: config.width,\n codedHeight: config.height,\n });\n return { supported: result.supported ?? false };\n }\n\n protected createDecoder(init: {\n output: (frame: VideoFrame) => void;\n error: (error: DOMException) => void;\n }): VideoDecoder {\n return new VideoDecoder(init);\n }\n\n protected getDecoderType(): string {\n return 'Video';\n }\n\n protected async configureDecoder(config: VideoDecoderConfig): Promise<void> {\n if (!this.decoder) return;\n\n const decoderConfig = {\n codec: config.codec,\n codedWidth: config.width,\n codedHeight: config.height,\n hardwareAcceleration: config.hardwareAcceleration || 'no-preference',\n optimizeForLatency: false,\n ...(config.description && { description: config.description }),\n ...(config.displayAspectWidth && { displayAspectWidth: config.displayAspectWidth }),\n ...(config.displayAspectHeight && { displayAspectHeight: config.displayAspectHeight }),\n };\n\n this.decoder.configure(decoderConfig as any);\n // console.log('[VideoChunkDecoder] Decoder configured, state:', this.decoder.state);\n }\n\n protected decode(chunk: EncodedVideoChunk): 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: VideoDecoderConfig): Promise<void> {\n // console.log('[VideoChunkDecoder] Configuring with:', config);\n\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 // console.log('[VideoChunkDecoder] Processing', this.bufferedChunks.length, 'buffered chunks');\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('[VideoChunkDecoder] 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 { VideoChunkDecoder } from './VideoChunkDecoder';\nimport { VideoDecoderConfig } from './types';\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 * VideoDecodeWorker (Clip Local) - Decodes video for a single clip\n * Receives encoded video chunks from VideoDemuxWorker and outputs decoded frames\n *\n * Pipeline: VideoDemuxWorker → VideoDecodeWorker → VideoComposeWorker\n *\n * Features:\n * - Single clip, single VideoDecoder instance (no routing)\n * - GOP-based decoding with metadata\n * - Stream-based processing with backpressure\n * - Lifecycle tied to clip pipeline\n */\nexport class VideoDecodeWorker {\n private channel: WorkerChannel;\n private decoder: VideoChunkDecoder | null = null;\n private clipId: string = '';\n\n private defaultConfig: Partial<VideoDecoderConfig> = {};\n\n private upstreamPort: MessagePort | null = null;\n private downstreamPort: MessagePort | null = null;\n\n constructor() {\n this.channel = new WorkerChannel(self as any, {\n name: 'VideoDecodeWorker',\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?: 'video';\n sessionId?: string;\n clipStartUs?: number;\n clipDurationUs?: number;\n }): Promise<{ success: boolean }> {\n const { port, direction, sessionId } = payload;\n\n if (direction === 'upstream') {\n this.upstreamPort = port;\n this.clipId = sessionId || 'default';\n\n const channel = new WorkerChannel(port, {\n name: 'Demux-VideoDecode',\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 if (direction === 'downstream') {\n this.downstreamPort = port;\n }\n\n return { success: true };\n }\n\n private async handleConfigure(payload: {\n config?: { video?: Partial<VideoDecoderConfig> };\n sessionId?: string;\n streamType?: 'video';\n codec?: string;\n width?: number;\n height?: number;\n description?: ArrayBuffer | Uint8Array;\n }): Promise<{ success: boolean }> {\n const { sessionId, streamType, codec, width, height, description, config } = payload;\n\n if (sessionId && streamType === 'video') {\n if (this.decoder) {\n await this.decoder.updateConfig({\n codec,\n width,\n height,\n description: normalizeDescription(description),\n });\n }\n return { success: true };\n }\n\n if (config?.video) {\n Object.assign(this.defaultConfig, config.video);\n\n if (this.decoder) {\n await this.decoder.updateConfig(config.video);\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\n if (!this.decoder) {\n this.decoder = new VideoChunkDecoder(sessionId, {\n ...this.defaultConfig,\n codec: metadata?.codec,\n width: metadata?.width,\n height: metadata?.height,\n description: normalizeDescription(metadata?.description),\n });\n }\n\n const transform = this.decoder.createStream();\n\n if (this.downstreamPort) {\n const channel = new WorkerChannel(this.downstreamPort, {\n name: 'VideoDecode-Compose',\n timeout: 30000,\n });\n\n channel.sendStream(transform.readable as ReadableStream, {\n streamType: 'video',\n sessionId,\n });\n\n stream\n .pipeTo(transform.writable)\n .catch((error) =>\n console.error('[VideoDecodeWorker] Video stream pipe error:', sessionId, error)\n );\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: 'video',\n });\n\n return { success: true };\n }\n\n private async handleGetStats(): Promise<{ video?: any }> {\n if (!this.decoder) {\n return {};\n }\n\n return {\n video: {\n clipId: this.clipId,\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.downstreamPort?.close();\n this.downstreamPort = null;\n\n this.channel.state = WorkerState.Disposed;\n\n return { success: true };\n }\n}\n\nconst worker = new VideoDecodeWorker();\n\nself.addEventListener('beforeunload', () => {\n worker['handleDispose']();\n});\n\nexport default null;\n"],"names":[],"mappings":"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"} |
@@ -34,6 +34,3 @@ import { TimeUs } from '../model/types'; | ||
| init(): Promise<void>; | ||
| receiveComposedFrames(stream: ReadableStream<VideoFrame | { | ||
| frame: VideoFrame; | ||
| metadata?: any; | ||
| }>, params: { | ||
| receiveComposedFrames(stream: ReadableStream<VideoFrame>, params: { | ||
| clipId: string; | ||
@@ -40,0 +37,0 @@ trackId: string; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"CacheManager.d.ts","sourceRoot":"","sources":["../../src/cache/CacheManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAEnC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAIvD,UAAU,kBAAkB;IAC1B,EAAE,EAAE;QACF,WAAW,EAAE,MAAM,CAAC;QACpB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,aAAa,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,EAAE,EAAE;QACF,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAYD;;;;;;;GAOG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;IAC5C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;IAC5C,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,OAAO,CAAC,gBAAgB,CAAsC;IAC9D,OAAO,CAAC,QAAQ,CAAC,CAA4B;gBAEjC,MAAM,EAAE,kBAAkB,EAAE,QAAQ,CAAC,EAAE,QAAQ,CAAC,eAAe,CAAC;IAOtE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAIrB,qBAAqB,CACzB,MAAM,EAAE,cAAc,CAAC,UAAU,GAAG;QAAE,KAAK,EAAE,UAAU,CAAC;QAAC,QAAQ,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC,EAC1E,MAAM,EAAE;QACN,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,GAAG,EAAE,MAAM,CAAC;QACZ,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,OAAO,EAAE,CAAC,IAAI,EAAE;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,KAAK,IAAI,CAAC;KAC7D,GACA,OAAO,CAAC,IAAI,CAAC;IAyEV,oBAAoB,CACxB,MAAM,EAAE,cAAc,CAAC;QAAE,KAAK,EAAE,iBAAiB,CAAC;QAAC,QAAQ,EAAE,yBAAyB,CAAA;KAAE,CAAC,EACzF,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,OAAO,GAAG,OAAO,EACxB,OAAO,CAAC,EAAE;QACR,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,yBAAyB,KAAK,IAAI,CAAC;QAC3D,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;KAClC,GACA,OAAO,CAAC,IAAI,CAAC;IAmGhB,gBAAgB,CACd,MAAM,EAAE,cAAc,CAAC,SAAS,CAAC,EACjC,QAAQ,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAE,GACzD,IAAI;IAIP,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI;IAIpF,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,EAAE,GAAG,IAAI;IAIjF,sBAAsB,CACpB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GACZ;QAAE,MAAM,EAAE,YAAY,EAAE,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAIlF,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAInC,eAAe,IAAI,IAAI;IAIvB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAIxC;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI;IAIxD;;;;;OAKG;IA2EG,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKnD;;OAEG;IACH,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAIxC;;OAEG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAIpC;;;;OAIG;IACH,gBAAgB,CACd,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;QAAE,aAAa,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAO,GACjF,OAAO,CAAC,OAAO,CAAC;IA0Cb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAK5B,WAAW;;;;;;;;;IAOX;;OAEG;IACG,kBAAkB,CACtB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,OAAO,GAAG,OAAO,GACvB,OAAO,CAAC,cAAc,CAAC,iBAAiB,GAAG,iBAAiB,CAAC,GAAG,IAAI,CAAC;IAIxE;;;OAGG;IACG,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAK7E;;OAEG;IACG,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/E;;OAEG;IACG,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC;IAIlF;;OAEG;IACG,YAAY,IAAI,OAAO,CAC3B,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CACxF;IAID;;OAEG;IACG,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIpD;;OAEG;IACG,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAIxD;;;OAGG;IACH,OAAO,CAAC,uBAAuB;CAoEhC"} | ||
| {"version":3,"file":"CacheManager.d.ts","sourceRoot":"","sources":["../../src/cache/CacheManager.ts"],"names":[],"mappings":"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"} |
@@ -32,6 +32,3 @@ import { VideoL1Cache } from "./l1/VideoL1Cache.js"; | ||
| const frameDuration = Math.round(1e6 / fps); | ||
| const frame = value.frame || value; | ||
| const metadata = value.metadata; | ||
| const gopSerial = metadata?.gopSerial; | ||
| const isKeyframe = metadata?.isKeyframe; | ||
| const frame = value; | ||
| const timestamp = frame.timestamp ?? 0; | ||
@@ -42,10 +39,4 @@ const rcFrame = this.videoL1Cache.addFrame( | ||
| frameDuration, | ||
| params.trackId, | ||
| gopSerial, | ||
| isKeyframe | ||
| params.trackId | ||
| ); | ||
| if (!rcFrame) { | ||
| await process(); | ||
| return; | ||
| } | ||
| const globalTimeUs = (params.clipStartUs ?? 0) + timestamp; | ||
@@ -81,3 +72,3 @@ this.checkAndNotifyClipReady(params.clipId, timestamp); | ||
| reader.releaseLock(); | ||
| throw error; | ||
| console.error("[CacheManager] receiveComposedFrames error: ", error); | ||
| } | ||
@@ -166,3 +157,2 @@ } | ||
| } | ||
| throw error; | ||
| } | ||
@@ -169,0 +159,0 @@ } |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"CacheManager.js","sources":["../../src/cache/CacheManager.ts"],"sourcesContent":["import type { TimeUs } from '../model/types';\nimport { RcFrame } from '../model';\nimport { VideoL1Cache } from './l1/VideoL1Cache';\nimport { L2Cache } from './l2/L2Cache';\nimport { MeframeEvent } from '../event/events';\nimport type { EventBus } from '../event/EventBus';\nimport type { EventPayloadMap } from '../event/events';\nimport { AudioL1Cache } from './l1/AudioL1Cache';\nimport { WaiterReplacedError } from '../utils/errors';\n\ninterface CacheManagerConfig {\n l1: {\n maxMemoryMB: number;\n maxGOPs?: number;\n gopIntervalUs?: number;\n };\n l2: {\n maxSizeMB: number;\n projectId: string;\n };\n}\n\ninterface ClipReadyWaiter {\n clipId: string;\n minFrameCount: number;\n startTimeUs: TimeUs;\n currentCount: number;\n resolve: (ready: boolean) => void;\n reject: (reason?: unknown) => void;\n timeoutId?: ReturnType<typeof setTimeout>;\n}\n\n/**\n * Simplified CacheManager for 2-Clip strategy\n *\n * Core features:\n * - L1 (VRAM) for composed VideoFrames\n * - L2 (IndexedDB/OPFS) for encoded chunks\n * - Clip-level cache management\n */\nexport class CacheManager {\n private readonly videoL1Cache: VideoL1Cache;\n private readonly audioL1Cache: AudioL1Cache;\n readonly l2Cache: L2Cache;\n private clipReadyWaiters = new Map<string, ClipReadyWaiter>();\n private eventBus?: EventBus<EventPayloadMap>;\n\n constructor(config: CacheManagerConfig, eventBus?: EventBus<EventPayloadMap>) {\n this.videoL1Cache = new VideoL1Cache(config.l1);\n this.l2Cache = new L2Cache(config.l2);\n this.audioL1Cache = new AudioL1Cache();\n this.eventBus = eventBus;\n }\n\n async init(): Promise<void> {\n await this.l2Cache.init();\n }\n\n async receiveComposedFrames(\n stream: ReadableStream<VideoFrame | { frame: VideoFrame; metadata?: any }>,\n params: {\n clipId: string;\n trackId: string;\n fps: number;\n clipStartUs?: TimeUs;\n onFrame: (info: { clipId: string; timeUs: TimeUs }) => void;\n }\n ): Promise<void> {\n const reader = stream.getReader();\n const process = async (): Promise<void> => {\n const { done, value } = await reader.read();\n if (done) {\n reader.releaseLock();\n return;\n }\n if (value) {\n const fps = params.fps > 0 ? params.fps : 30;\n const frameDuration = Math.round(1_000_000 / fps);\n\n const frame = (value as any).frame || value;\n const metadata = (value as any).metadata;\n const gopSerial = metadata?.gopSerial;\n const isKeyframe = metadata?.isKeyframe;\n\n const timestamp = frame.timestamp ?? 0;\n\n const rcFrame = this.videoL1Cache.addFrame(\n frame,\n params.clipId,\n frameDuration,\n params.trackId,\n gopSerial,\n isKeyframe\n );\n if (!rcFrame) {\n await process();\n return;\n }\n\n // Calculate global time for event emission\n const globalTimeUs = (params.clipStartUs ?? 0) + timestamp;\n\n this.checkAndNotifyClipReady(params.clipId, timestamp);\n // Emit cover event only for the first frame of the composition (global time = 0)\n if (globalTimeUs === 0) {\n this.eventBus?.emit(MeframeEvent.CacheCover, {\n timeUs: globalTimeUs,\n clipId: params.clipId,\n level: 'L1',\n size: rcFrame.sizeEstimate ?? 0,\n });\n }\n\n const info = { clipId: params.clipId, timeUs: timestamp };\n this.eventBus?.emit(MeframeEvent.ComposeFrameReady, {\n timeUs: globalTimeUs,\n frameNumber: Math.floor(globalTimeUs / frameDuration),\n renderTimeMs: 0,\n trackId: params.trackId,\n clipId: params.clipId,\n });\n\n params.onFrame(info);\n }\n\n await process();\n };\n\n try {\n await process();\n } catch (error) {\n this.eventBus?.emit(MeframeEvent.ComposeFrameDropped, {\n timeUs: 0,\n reason: 'compose_slow',\n });\n reader.releaseLock();\n throw error;\n }\n }\n\n async receiveEncodedChunks(\n stream: ReadableStream<{ chunk: EncodedVideoChunk; metadata: EncodedVideoChunkMetadata }>,\n clipId: string,\n track: 'video' | 'audio',\n options?: {\n onComplete?: (metadata: EncodedVideoChunkMetadata) => void;\n onError?: (error: Error) => void;\n }\n ): Promise<void> {\n const reader = stream.getReader();\n const chunks: Array<EncodedVideoChunk | EncodedAudioChunk> = [];\n const batchSize = track === 'video' ? 30 : 50;\n let decoderConfig: any = null;\n let metadata: EncodedVideoChunkMetadata | EncodedAudioChunkMetadata | undefined;\n const process = async (): Promise<void> => {\n const { done, value } = await reader.read();\n const { chunk, metadata: chunkMetadata } = value ?? {};\n if (chunkMetadata) {\n metadata = chunkMetadata;\n // Extract decoderConfig from first chunk's metadata\n if (!decoderConfig && chunkMetadata.decoderConfig) {\n decoderConfig = chunkMetadata.decoderConfig;\n }\n }\n if (done) {\n // Flush remaining chunks on stream end\n if (chunks.length > 0) {\n await this.l2Cache.put(clipId, chunks, track, {\n isComplete: true,\n metadata: decoderConfig,\n });\n const firstChunk = chunks[0];\n if (firstChunk) {\n this.eventBus?.emit(MeframeEvent.CacheWrite, {\n clipId,\n timeUs: firstChunk.timestamp,\n level: 'L2',\n size: chunks.reduce((sum, c) => sum + c.byteLength, 0),\n });\n }\n } else {\n // Mark as complete even if no chunks in final batch\n await this.l2Cache.markComplete(clipId, track);\n }\n reader.releaseLock();\n\n // Notify completion\n if (options?.onComplete) {\n options.onComplete(metadata!);\n }\n return;\n }\n\n if (chunk) {\n chunks.push(chunk);\n\n this.eventBus?.emit(MeframeEvent.EncodeChunkReady, {\n timeUs: chunk.timestamp,\n durationUs: chunk.duration ?? 0,\n track,\n size: chunk.byteLength,\n });\n\n // Batch write to L2 when buffer is full\n if (chunks.length >= batchSize) {\n const batchToWrite = chunks.splice(0);\n if (batchToWrite.length > 0) {\n await this.l2Cache.put(clipId, batchToWrite, track, {\n metadata: decoderConfig,\n });\n this.eventBus?.emit(MeframeEvent.CacheWrite, {\n clipId,\n timeUs: chunk.timestamp,\n level: 'L2',\n size: batchToWrite.reduce((sum, c) => sum + c.byteLength, 0),\n });\n }\n }\n }\n\n await process();\n };\n\n try {\n await process();\n } catch (error) {\n // Flush any accumulated chunks before throwing\n if (chunks.length > 0) {\n await this.l2Cache.put(clipId, chunks, track, {\n metadata: decoderConfig,\n });\n }\n this.eventBus?.emit(MeframeEvent.EncodeChunkError, {\n timeUs: 0,\n track,\n error: error as Error,\n });\n reader.releaseLock();\n\n // Notify error\n if (options?.onError) {\n options.onError(error as Error);\n }\n throw error;\n }\n }\n\n acceptMixedAudio(\n stream: ReadableStream<AudioData>,\n metadata: { sampleRate: number; numberOfChannels: number }\n ): void {\n this.audioL1Cache.attachStream(stream, metadata);\n }\n\n putClipAudioData(clipId: string, audioData: AudioData, clipDurationUs: TimeUs): void {\n this.audioL1Cache.putClipAudioData(clipId, audioData, clipDurationUs);\n }\n\n getClipPCM(clipId: string, startUs: TimeUs, endUs: TimeUs): Float32Array[] | null {\n return this.audioL1Cache.getPCM(clipId, startUs, endUs);\n }\n\n getClipPCMWithMetadata(\n clipId: string,\n startUs: TimeUs,\n endUs: TimeUs\n ): { planes: Float32Array[]; sampleRate: number; numberOfChannels: number } | null {\n return this.audioL1Cache.getPCMWithMetadata(clipId, startUs, endUs);\n }\n\n hasClipPCM(clipId: string): boolean {\n return this.audioL1Cache.hasClipPCM(clipId);\n }\n\n resetAudioCache(): void {\n this.audioL1Cache.reset();\n }\n\n clearClipAudioData(clipId: string): void {\n this.audioL1Cache.clearClipPCM(clipId);\n }\n\n /**\n * Get frame from L1 cache\n * @param timeUs - Clip-relative timestamp (0-based)\n * @param clipId - Clip identifier\n */\n getFrame(timeUs: TimeUs, clipId: string): RcFrame | null {\n return this.videoL1Cache.get(timeUs, clipId);\n }\n\n /**\n * Wait for frame to be available in cache\n * @param timeUs - Clip-relative timestamp (0-based)\n * @param clipId - Clip identifier\n * @param options - Wait options (timeout, tolerance, etc.)\n */\n // waitForFrame(\n // timeUs: TimeUs,\n // clipId: string,\n // options: WaitForFrameOptions = {}\n // ): Promise<WaitForFrameResult> {\n // const existing = this.videoL1Cache.get(timeUs, clipId);\n // if (existing) {\n // return Promise.resolve({ frame: existing, source: 'l1', timestampUs: timeUs, clipId });\n // }\n\n // const requestKey = this.makeRequestKey(clipId, timeUs);\n // const existingPromise = this.pendingFramePromises.get(requestKey);\n // if (existingPromise) {\n // return existingPromise;\n // }\n\n // const promise = new Promise<WaitForFrameResult>((resolve, reject) => {\n // const toleranceUs = Math.max(\n // options.toleranceUs ?? DEFAULT_WAIT_TOLERANCE_US,\n // DEFAULT_WAIT_TOLERANCE_US\n // );\n\n // const waiter: FrameWaiter = {\n // requestKey,\n // clipId,\n // targetTimeUs: timeUs,\n // resolve,\n // reject,\n // toleranceUs,\n // };\n\n // let waiters = this.frameWaiters.get(clipId);\n // if (!waiters) {\n // waiters = new Set();\n // this.frameWaiters.set(clipId, waiters);\n // }\n // waiters.add(waiter);\n\n // const signal = options.signal;\n // if (signal) {\n // const onAbort = (): void => {\n // this.removeWaiter(waiter);\n // this.cleanupWaiter(waiter);\n // reject(new DOMException('Render aborted', 'AbortError'));\n // };\n\n // if (signal.aborted) {\n // onAbort();\n // return;\n // }\n\n // signal.addEventListener('abort', onAbort, { once: true });\n // waiter.abortCleanup = () => {\n // signal.removeEventListener('abort', onAbort);\n // };\n // }\n\n // if (options.timeoutMs && options.timeoutMs > 0) {\n // waiter.timeoutId = setTimeout(() => {\n // this.removeWaiter(waiter);\n // this.cleanupWaiter(waiter);\n // reject(new Error('waitForFrame timeout'));\n // }, options.timeoutMs);\n // }\n // });\n\n // const trackedPromise = promise.finally(() => {\n // this.pendingFramePromises.delete(requestKey);\n // });\n\n // this.pendingFramePromises.set(requestKey, trackedPromise);\n // return trackedPromise;\n // }\n\n async invalidateClip(clipId: string): Promise<void> {\n this.videoL1Cache.invalidateClip(clipId);\n await this.l2Cache.invalidateClip(clipId);\n }\n\n /**\n * Evict a clip from L1 cache\n */\n invalidateClipInL1(clipId: string): void {\n this.videoL1Cache.invalidateClip(clipId);\n }\n\n /**\n * Check if a clip is cached in L1\n */\n hasClipInL1(clipId: string): boolean {\n return this.videoL1Cache.hasClip(clipId);\n }\n\n /**\n * Wait for a clip to have minimum frames cached\n * Used by PlaybackController for buffering state\n * Only one waiter per clip - new waiter replaces old one\n */\n waitForClipReady(\n clipId: string,\n options: { minFrameCount?: number; timeoutMs?: number; startTimeUs?: TimeUs } = {}\n ): Promise<boolean> {\n const minFrameCount = options.minFrameCount ?? 30;\n const startTimeUs = options.startTimeUs ?? 0;\n\n // Check if already have enough frames\n const currentFrameCount = this.videoL1Cache.getClipFrameCount(clipId, startTimeUs);\n if (currentFrameCount >= minFrameCount) {\n return Promise.resolve(true);\n }\n\n // Cancel previous waiter if exists\n const oldWaiter = this.clipReadyWaiters.get(clipId);\n if (oldWaiter) {\n if (oldWaiter.timeoutId) {\n clearTimeout(oldWaiter.timeoutId);\n }\n oldWaiter.reject(new WaiterReplacedError(clipId));\n }\n\n return new Promise<boolean>((resolve, reject) => {\n const waiter: ClipReadyWaiter = {\n clipId,\n minFrameCount,\n startTimeUs,\n currentCount: currentFrameCount,\n resolve,\n reject,\n };\n\n this.clipReadyWaiters.set(clipId, waiter);\n\n if (options.timeoutMs && options.timeoutMs > 0) {\n waiter.timeoutId = setTimeout(() => {\n if (this.clipReadyWaiters.get(clipId) === waiter) {\n this.clipReadyWaiters.delete(clipId);\n }\n resolve(false);\n }, options.timeoutMs);\n }\n });\n }\n\n async clear(): Promise<void> {\n this.videoL1Cache.clear();\n await this.l2Cache.clear();\n }\n\n getMetadata() {\n return {\n l1: this.videoL1Cache.getMetadata(),\n l2: this.l2Cache.getMetadata(),\n };\n }\n\n /**\n * Create read stream from L2 cache for export\n */\n async createL2ReadStream(\n clipId: string,\n track: 'video' | 'audio'\n ): Promise<ReadableStream<EncodedVideoChunk | EncodedAudioChunk> | null> {\n return this.l2Cache.createReadStream(clipId, track);\n }\n\n /**\n * Check if clip is fully cached in L2\n * Returns true only if the clip is marked as complete\n */\n async hasClipInL2(clipId: string, track: 'video' | 'audio'): Promise<boolean> {\n const result = await this.l2Cache.hasCompleteClip(clipId, track);\n return result;\n }\n\n /**\n * Mark clip as complete in L2 cache\n */\n async markClipComplete(clipId: string, track: 'video' | 'audio'): Promise<void> {\n await this.l2Cache.markComplete(clipId, track);\n }\n\n /**\n * Get chunk metadata (decoderConfig) from L2 cache\n */\n async getL2Metadata(clipId: string, track: 'video' | 'audio'): Promise<any | null> {\n return this.l2Cache.getClipMetadata(clipId, track);\n }\n\n /**\n * List all cached projects\n */\n async listProjects(): Promise<\n Array<{ projectId: string; totalBytes: number; clipCount: number; lastAccess: number }>\n > {\n return this.l2Cache.listProjects();\n }\n\n /**\n * Clear all cache data for a specific project\n */\n async clearProject(projectId: string): Promise<void> {\n return this.l2Cache.clearProject(projectId);\n }\n\n /**\n * Get cache size for a specific project (in bytes)\n */\n async getProjectSize(projectId: string): Promise<number> {\n return this.l2Cache.getProjectSize(projectId);\n }\n\n /**\n * Check if incoming frame satisfies clip ready condition\n * O(1) complexity - only checks single waiter and increments counter\n */\n private checkAndNotifyClipReady(clipId: string, frameTimestampUs: TimeUs): void {\n const waiter = this.clipReadyWaiters.get(clipId);\n if (!waiter) {\n return;\n }\n\n // Count all frames if startTimeUs is 0 (buffering scenario)\n // Otherwise only count frames at or after the target start time\n const shouldCount = waiter.startTimeUs === 0 || frameTimestampUs >= waiter.startTimeUs;\n\n if (shouldCount) {\n waiter.currentCount++;\n\n if (waiter.currentCount >= waiter.minFrameCount) {\n if (waiter.timeoutId) {\n clearTimeout(waiter.timeoutId);\n }\n waiter.resolve(true);\n this.clipReadyWaiters.delete(clipId);\n }\n }\n }\n\n // private cleanupWaiter(waiter: FrameWaiter): void {\n // if (waiter.timeoutId) {\n // clearTimeout(waiter.timeoutId);\n // waiter.timeoutId = undefined;\n // }\n\n // if (waiter.abortCleanup) {\n // waiter.abortCleanup();\n // waiter.abortCleanup = undefined;\n // }\n // }\n\n // private removeWaiter(waiter: FrameWaiter): void {\n // const waiters = this.frameWaiters.get(waiter.clipId);\n // if (!waiters) return;\n\n // waiters.delete(waiter);\n // if (waiters.size === 0) {\n // this.frameWaiters.delete(waiter.clipId);\n // }\n // }\n\n // private matchesTimestamp(\n // targetTimeUs: TimeUs,\n // actualTimeUs: TimeUs,\n // frameDurationUs: TimeUs,\n // toleranceUs: TimeUs\n // ): boolean {\n // if (targetTimeUs === actualTimeUs) return true;\n\n // const delta = Math.abs(targetTimeUs - actualTimeUs);\n // if (delta <= toleranceUs) {\n // return true;\n // }\n\n // if (actualTimeUs >= targetTimeUs && actualTimeUs < targetTimeUs + frameDurationUs) {\n // return true;\n // }\n\n // return false;\n // }\n\n // private makeRequestKey(clipId: string, timeUs: TimeUs): string {\n // return `${clipId}:${timeUs}`;\n // }\n}\n"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"CacheManager.js","sources":["../../src/cache/CacheManager.ts"],"sourcesContent":["import type { TimeUs } from '../model/types';\nimport { RcFrame } from '../model';\nimport { VideoL1Cache } from './l1/VideoL1Cache';\nimport { L2Cache } from './l2/L2Cache';\nimport { MeframeEvent } from '../event/events';\nimport type { EventBus } from '../event/EventBus';\nimport type { EventPayloadMap } from '../event/events';\nimport { AudioL1Cache } from './l1/AudioL1Cache';\nimport { WaiterReplacedError } from '../utils/errors';\n\ninterface CacheManagerConfig {\n l1: {\n maxMemoryMB: number;\n maxGOPs?: number;\n gopIntervalUs?: number;\n };\n l2: {\n maxSizeMB: number;\n projectId: string;\n };\n}\n\ninterface ClipReadyWaiter {\n clipId: string;\n minFrameCount: number;\n startTimeUs: TimeUs;\n currentCount: number;\n resolve: (ready: boolean) => void;\n reject: (reason?: unknown) => void;\n timeoutId?: ReturnType<typeof setTimeout>;\n}\n\n/**\n * Simplified CacheManager for 2-Clip strategy\n *\n * Core features:\n * - L1 (VRAM) for composed VideoFrames\n * - L2 (IndexedDB/OPFS) for encoded chunks\n * - Clip-level cache management\n */\nexport class CacheManager {\n private readonly videoL1Cache: VideoL1Cache;\n private readonly audioL1Cache: AudioL1Cache;\n readonly l2Cache: L2Cache;\n private clipReadyWaiters = new Map<string, ClipReadyWaiter>();\n private eventBus?: EventBus<EventPayloadMap>;\n\n constructor(config: CacheManagerConfig, eventBus?: EventBus<EventPayloadMap>) {\n this.videoL1Cache = new VideoL1Cache(config.l1);\n this.l2Cache = new L2Cache(config.l2);\n this.audioL1Cache = new AudioL1Cache();\n this.eventBus = eventBus;\n }\n\n async init(): Promise<void> {\n await this.l2Cache.init();\n }\n\n async receiveComposedFrames(\n stream: ReadableStream<VideoFrame>,\n params: {\n clipId: string;\n trackId: string;\n fps: number;\n clipStartUs?: TimeUs;\n onFrame: (info: { clipId: string; timeUs: TimeUs }) => void;\n }\n ): Promise<void> {\n const reader = stream.getReader();\n const process = async (): Promise<void> => {\n const { done, value } = await reader.read();\n if (done) {\n reader.releaseLock();\n return;\n }\n if (value) {\n const fps = params.fps > 0 ? params.fps : 30;\n const frameDuration = Math.round(1_000_000 / fps);\n\n const frame = value;\n const timestamp = frame.timestamp ?? 0;\n\n const rcFrame = this.videoL1Cache.addFrame(\n frame,\n params.clipId,\n frameDuration,\n params.trackId\n );\n\n // Calculate global time for event emission\n const globalTimeUs = (params.clipStartUs ?? 0) + timestamp;\n\n this.checkAndNotifyClipReady(params.clipId, timestamp);\n // Emit cover event only for the first frame of the composition (global time = 0)\n if (globalTimeUs === 0) {\n this.eventBus?.emit(MeframeEvent.CacheCover, {\n timeUs: globalTimeUs,\n clipId: params.clipId,\n level: 'L1',\n size: rcFrame.sizeEstimate ?? 0,\n });\n }\n\n const info = { clipId: params.clipId, timeUs: timestamp };\n this.eventBus?.emit(MeframeEvent.ComposeFrameReady, {\n timeUs: globalTimeUs,\n frameNumber: Math.floor(globalTimeUs / frameDuration),\n renderTimeMs: 0,\n trackId: params.trackId,\n clipId: params.clipId,\n });\n\n params.onFrame(info);\n }\n\n await process();\n };\n\n try {\n await process();\n } catch (error) {\n this.eventBus?.emit(MeframeEvent.ComposeFrameDropped, {\n timeUs: 0,\n reason: 'compose_slow',\n });\n reader.releaseLock();\n console.error('[CacheManager] receiveComposedFrames error: ', error);\n }\n }\n\n async receiveEncodedChunks(\n stream: ReadableStream<{ chunk: EncodedVideoChunk; metadata: EncodedVideoChunkMetadata }>,\n clipId: string,\n track: 'video' | 'audio',\n options?: {\n onComplete?: (metadata: EncodedVideoChunkMetadata) => void;\n onError?: (error: Error) => void;\n }\n ): Promise<void> {\n const reader = stream.getReader();\n const chunks: Array<EncodedVideoChunk | EncodedAudioChunk> = [];\n const batchSize = track === 'video' ? 30 : 50;\n let decoderConfig: any = null;\n let metadata: EncodedVideoChunkMetadata | EncodedAudioChunkMetadata | undefined;\n const process = async (): Promise<void> => {\n const { done, value } = await reader.read();\n const { chunk, metadata: chunkMetadata } = value ?? {};\n if (chunkMetadata) {\n metadata = chunkMetadata;\n // Extract decoderConfig from first chunk's metadata\n if (!decoderConfig && chunkMetadata.decoderConfig) {\n decoderConfig = chunkMetadata.decoderConfig;\n }\n }\n if (done) {\n // Flush remaining chunks on stream end\n if (chunks.length > 0) {\n await this.l2Cache.put(clipId, chunks, track, {\n isComplete: true,\n metadata: decoderConfig,\n });\n const firstChunk = chunks[0];\n if (firstChunk) {\n this.eventBus?.emit(MeframeEvent.CacheWrite, {\n clipId,\n timeUs: firstChunk.timestamp,\n level: 'L2',\n size: chunks.reduce((sum, c) => sum + c.byteLength, 0),\n });\n }\n } else {\n // Mark as complete even if no chunks in final batch\n await this.l2Cache.markComplete(clipId, track);\n }\n reader.releaseLock();\n\n // Notify completion\n if (options?.onComplete) {\n options.onComplete(metadata!);\n }\n return;\n }\n\n if (chunk) {\n chunks.push(chunk);\n\n this.eventBus?.emit(MeframeEvent.EncodeChunkReady, {\n timeUs: chunk.timestamp,\n durationUs: chunk.duration ?? 0,\n track,\n size: chunk.byteLength,\n });\n\n // Batch write to L2 when buffer is full\n if (chunks.length >= batchSize) {\n const batchToWrite = chunks.splice(0);\n if (batchToWrite.length > 0) {\n await this.l2Cache.put(clipId, batchToWrite, track, {\n metadata: decoderConfig,\n });\n this.eventBus?.emit(MeframeEvent.CacheWrite, {\n clipId,\n timeUs: chunk.timestamp,\n level: 'L2',\n size: batchToWrite.reduce((sum, c) => sum + c.byteLength, 0),\n });\n }\n }\n }\n\n await process();\n };\n\n try {\n await process();\n } catch (error) {\n // Flush any accumulated chunks before throwing\n if (chunks.length > 0) {\n await this.l2Cache.put(clipId, chunks, track, {\n metadata: decoderConfig,\n });\n }\n this.eventBus?.emit(MeframeEvent.EncodeChunkError, {\n timeUs: 0,\n track,\n error: error as Error,\n });\n reader.releaseLock();\n\n // Notify error\n if (options?.onError) {\n options.onError(error as Error);\n }\n }\n }\n\n acceptMixedAudio(\n stream: ReadableStream<AudioData>,\n metadata: { sampleRate: number; numberOfChannels: number }\n ): void {\n this.audioL1Cache.attachStream(stream, metadata);\n }\n\n putClipAudioData(clipId: string, audioData: AudioData, clipDurationUs: TimeUs): void {\n this.audioL1Cache.putClipAudioData(clipId, audioData, clipDurationUs);\n }\n\n getClipPCM(clipId: string, startUs: TimeUs, endUs: TimeUs): Float32Array[] | null {\n return this.audioL1Cache.getPCM(clipId, startUs, endUs);\n }\n\n getClipPCMWithMetadata(\n clipId: string,\n startUs: TimeUs,\n endUs: TimeUs\n ): { planes: Float32Array[]; sampleRate: number; numberOfChannels: number } | null {\n return this.audioL1Cache.getPCMWithMetadata(clipId, startUs, endUs);\n }\n\n hasClipPCM(clipId: string): boolean {\n return this.audioL1Cache.hasClipPCM(clipId);\n }\n\n resetAudioCache(): void {\n this.audioL1Cache.reset();\n }\n\n clearClipAudioData(clipId: string): void {\n this.audioL1Cache.clearClipPCM(clipId);\n }\n\n /**\n * Get frame from L1 cache\n * @param timeUs - Clip-relative timestamp (0-based)\n * @param clipId - Clip identifier\n */\n getFrame(timeUs: TimeUs, clipId: string): RcFrame | null {\n return this.videoL1Cache.get(timeUs, clipId);\n }\n\n /**\n * Wait for frame to be available in cache\n * @param timeUs - Clip-relative timestamp (0-based)\n * @param clipId - Clip identifier\n * @param options - Wait options (timeout, tolerance, etc.)\n */\n // waitForFrame(\n // timeUs: TimeUs,\n // clipId: string,\n // options: WaitForFrameOptions = {}\n // ): Promise<WaitForFrameResult> {\n // const existing = this.videoL1Cache.get(timeUs, clipId);\n // if (existing) {\n // return Promise.resolve({ frame: existing, source: 'l1', timestampUs: timeUs, clipId });\n // }\n\n // const requestKey = this.makeRequestKey(clipId, timeUs);\n // const existingPromise = this.pendingFramePromises.get(requestKey);\n // if (existingPromise) {\n // return existingPromise;\n // }\n\n // const promise = new Promise<WaitForFrameResult>((resolve, reject) => {\n // const toleranceUs = Math.max(\n // options.toleranceUs ?? DEFAULT_WAIT_TOLERANCE_US,\n // DEFAULT_WAIT_TOLERANCE_US\n // );\n\n // const waiter: FrameWaiter = {\n // requestKey,\n // clipId,\n // targetTimeUs: timeUs,\n // resolve,\n // reject,\n // toleranceUs,\n // };\n\n // let waiters = this.frameWaiters.get(clipId);\n // if (!waiters) {\n // waiters = new Set();\n // this.frameWaiters.set(clipId, waiters);\n // }\n // waiters.add(waiter);\n\n // const signal = options.signal;\n // if (signal) {\n // const onAbort = (): void => {\n // this.removeWaiter(waiter);\n // this.cleanupWaiter(waiter);\n // reject(new DOMException('Render aborted', 'AbortError'));\n // };\n\n // if (signal.aborted) {\n // onAbort();\n // return;\n // }\n\n // signal.addEventListener('abort', onAbort, { once: true });\n // waiter.abortCleanup = () => {\n // signal.removeEventListener('abort', onAbort);\n // };\n // }\n\n // if (options.timeoutMs && options.timeoutMs > 0) {\n // waiter.timeoutId = setTimeout(() => {\n // this.removeWaiter(waiter);\n // this.cleanupWaiter(waiter);\n // reject(new Error('waitForFrame timeout'));\n // }, options.timeoutMs);\n // }\n // });\n\n // const trackedPromise = promise.finally(() => {\n // this.pendingFramePromises.delete(requestKey);\n // });\n\n // this.pendingFramePromises.set(requestKey, trackedPromise);\n // return trackedPromise;\n // }\n\n async invalidateClip(clipId: string): Promise<void> {\n this.videoL1Cache.invalidateClip(clipId);\n await this.l2Cache.invalidateClip(clipId);\n }\n\n /**\n * Evict a clip from L1 cache\n */\n invalidateClipInL1(clipId: string): void {\n this.videoL1Cache.invalidateClip(clipId);\n }\n\n /**\n * Check if a clip is cached in L1\n */\n hasClipInL1(clipId: string): boolean {\n return this.videoL1Cache.hasClip(clipId);\n }\n\n /**\n * Wait for a clip to have minimum frames cached\n * Used by PlaybackController for buffering state\n * Only one waiter per clip - new waiter replaces old one\n */\n waitForClipReady(\n clipId: string,\n options: { minFrameCount?: number; timeoutMs?: number; startTimeUs?: TimeUs } = {}\n ): Promise<boolean> {\n const minFrameCount = options.minFrameCount ?? 30;\n const startTimeUs = options.startTimeUs ?? 0;\n\n // Check if already have enough frames\n const currentFrameCount = this.videoL1Cache.getClipFrameCount(clipId, startTimeUs);\n if (currentFrameCount >= minFrameCount) {\n return Promise.resolve(true);\n }\n\n // Cancel previous waiter if exists\n const oldWaiter = this.clipReadyWaiters.get(clipId);\n if (oldWaiter) {\n if (oldWaiter.timeoutId) {\n clearTimeout(oldWaiter.timeoutId);\n }\n oldWaiter.reject(new WaiterReplacedError(clipId));\n }\n\n return new Promise<boolean>((resolve, reject) => {\n const waiter: ClipReadyWaiter = {\n clipId,\n minFrameCount,\n startTimeUs,\n currentCount: currentFrameCount,\n resolve,\n reject,\n };\n\n this.clipReadyWaiters.set(clipId, waiter);\n\n if (options.timeoutMs && options.timeoutMs > 0) {\n waiter.timeoutId = setTimeout(() => {\n if (this.clipReadyWaiters.get(clipId) === waiter) {\n this.clipReadyWaiters.delete(clipId);\n }\n resolve(false);\n }, options.timeoutMs);\n }\n });\n }\n\n async clear(): Promise<void> {\n this.videoL1Cache.clear();\n await this.l2Cache.clear();\n }\n\n getMetadata() {\n return {\n l1: this.videoL1Cache.getMetadata(),\n l2: this.l2Cache.getMetadata(),\n };\n }\n\n /**\n * Create read stream from L2 cache for export\n */\n async createL2ReadStream(\n clipId: string,\n track: 'video' | 'audio'\n ): Promise<ReadableStream<EncodedVideoChunk | EncodedAudioChunk> | null> {\n return this.l2Cache.createReadStream(clipId, track);\n }\n\n /**\n * Check if clip is fully cached in L2\n * Returns true only if the clip is marked as complete\n */\n async hasClipInL2(clipId: string, track: 'video' | 'audio'): Promise<boolean> {\n const result = await this.l2Cache.hasCompleteClip(clipId, track);\n return result;\n }\n\n /**\n * Mark clip as complete in L2 cache\n */\n async markClipComplete(clipId: string, track: 'video' | 'audio'): Promise<void> {\n await this.l2Cache.markComplete(clipId, track);\n }\n\n /**\n * Get chunk metadata (decoderConfig) from L2 cache\n */\n async getL2Metadata(clipId: string, track: 'video' | 'audio'): Promise<any | null> {\n return this.l2Cache.getClipMetadata(clipId, track);\n }\n\n /**\n * List all cached projects\n */\n async listProjects(): Promise<\n Array<{ projectId: string; totalBytes: number; clipCount: number; lastAccess: number }>\n > {\n return this.l2Cache.listProjects();\n }\n\n /**\n * Clear all cache data for a specific project\n */\n async clearProject(projectId: string): Promise<void> {\n return this.l2Cache.clearProject(projectId);\n }\n\n /**\n * Get cache size for a specific project (in bytes)\n */\n async getProjectSize(projectId: string): Promise<number> {\n return this.l2Cache.getProjectSize(projectId);\n }\n\n /**\n * Check if incoming frame satisfies clip ready condition\n * O(1) complexity - only checks single waiter and increments counter\n */\n private checkAndNotifyClipReady(clipId: string, frameTimestampUs: TimeUs): void {\n const waiter = this.clipReadyWaiters.get(clipId);\n if (!waiter) {\n return;\n }\n\n // Count all frames if startTimeUs is 0 (buffering scenario)\n // Otherwise only count frames at or after the target start time\n const shouldCount = waiter.startTimeUs === 0 || frameTimestampUs >= waiter.startTimeUs;\n\n if (shouldCount) {\n waiter.currentCount++;\n\n if (waiter.currentCount >= waiter.minFrameCount) {\n if (waiter.timeoutId) {\n clearTimeout(waiter.timeoutId);\n }\n waiter.resolve(true);\n this.clipReadyWaiters.delete(clipId);\n }\n }\n }\n\n // private cleanupWaiter(waiter: FrameWaiter): void {\n // if (waiter.timeoutId) {\n // clearTimeout(waiter.timeoutId);\n // waiter.timeoutId = undefined;\n // }\n\n // if (waiter.abortCleanup) {\n // waiter.abortCleanup();\n // waiter.abortCleanup = undefined;\n // }\n // }\n\n // private removeWaiter(waiter: FrameWaiter): void {\n // const waiters = this.frameWaiters.get(waiter.clipId);\n // if (!waiters) return;\n\n // waiters.delete(waiter);\n // if (waiters.size === 0) {\n // this.frameWaiters.delete(waiter.clipId);\n // }\n // }\n\n // private matchesTimestamp(\n // targetTimeUs: TimeUs,\n // actualTimeUs: TimeUs,\n // frameDurationUs: TimeUs,\n // toleranceUs: TimeUs\n // ): boolean {\n // if (targetTimeUs === actualTimeUs) return true;\n\n // const delta = Math.abs(targetTimeUs - actualTimeUs);\n // if (delta <= toleranceUs) {\n // return true;\n // }\n\n // if (actualTimeUs >= targetTimeUs && actualTimeUs < targetTimeUs + frameDurationUs) {\n // return true;\n // }\n\n // return false;\n // }\n\n // private makeRequestKey(clipId: string, timeUs: TimeUs): string {\n // return `${clipId}:${timeUs}`;\n // }\n}\n"],"names":[],"mappings":"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"} |
@@ -6,3 +6,3 @@ export { VideoL1Cache } from './l1/VideoL1Cache'; | ||
| export { CacheManager } from './CacheManager'; | ||
| export type { CacheConfig, CacheStats, CacheEntry, GOP, ClipMetadata } from './types'; | ||
| export type { CacheConfig, CacheStats, CacheEntry, ClipMetadata } from './types'; | ||
| //# sourceMappingURL=index.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cache/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,GAAG,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC"} | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cache/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC"} |
@@ -5,4 +5,2 @@ import { TimeUs } from '../../model/types'; | ||
| maxMemoryMB: number; | ||
| maxGOPs?: number; | ||
| gopIntervalUs?: number; | ||
| } | ||
@@ -9,0 +7,0 @@ export interface AudioSlot { |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/cache/l1/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,WAAW,QAAQ;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,SAAS;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,YAAY,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;CAC1B"} | ||
| {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/cache/l1/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,WAAW,QAAQ;IACvB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,SAAS;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,YAAY,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;CAC1B"} |
| import { TimeUs } from '../../model/types'; | ||
| import { GOP } from '../types'; | ||
| import { RcFrame } from '../../model'; | ||
@@ -7,4 +6,2 @@ | ||
| maxMemoryMB: number; | ||
| maxGOPs?: number; | ||
| gopIntervalUs?: number; | ||
| } | ||
@@ -18,53 +15,23 @@ export interface L1CacheMetadata { | ||
| /** | ||
| * Simplified VideoL1Cache for 2-Clip strategy | ||
| * Simplified VideoL1Cache - Single-layer frame storage per clip | ||
| * | ||
| * Clip lifecycle is managed by ClipSessionManager: | ||
| * - No LRU eviction (clips evicted explicitly) | ||
| * - No capacity limits (fixed 3 clips) | ||
| * - Simple GOP storage per clip | ||
| * Each clip stores frames in a sorted array by timestamp. | ||
| * No GOP management - frames are directly stored and retrieved via binary search. | ||
| */ | ||
| export declare class VideoL1Cache { | ||
| private readonly entriesByClip; | ||
| private maxMemoryBytes; | ||
| private gopIntervalUs; | ||
| private readonly framesByClip; | ||
| private readonly maxMemoryBytes; | ||
| private currentBytes; | ||
| constructor(config: L1Config); | ||
| get(timeUs: TimeUs, clipId: string): RcFrame | null; | ||
| putGOP(gop: GOP, clipId: string, trackId: string): void; | ||
| addFrame(frame: VideoFrame, clipId: string, frameDuration: TimeUs, trackId: string, gopSerial?: number, isKeyframe?: boolean): RcFrame | null; | ||
| /** | ||
| * Evict all cache entries for a specific clip | ||
| */ | ||
| addFrame(frame: VideoFrame, clipId: string, frameDuration: TimeUs, trackId: string): RcFrame; | ||
| invalidateClip(clipId: string): void; | ||
| /** | ||
| * Check if a clip has any cached entries | ||
| */ | ||
| hasClip(clipId: string): boolean; | ||
| /** | ||
| * Get total frame count for a clip | ||
| * @param clipId - Clip identifier | ||
| * @param startTimeUs - Optional start time to count frames from (inclusive) | ||
| */ | ||
| getClipFrameCount(clipId: string, startTimeUs?: TimeUs): number; | ||
| clear(): void; | ||
| getMetadata(): L1CacheMetadata; | ||
| private registerEntry; | ||
| private createEntry; | ||
| private mergeFrames; | ||
| private insertFrame; | ||
| private closeFrames; | ||
| private composeKey; | ||
| private wrapFrame; | ||
| private wrapFrames; | ||
| private getEntryExact; | ||
| private findEntryByTime; | ||
| private countEntries; | ||
| private ensureClipEntries; | ||
| private binarySearchFrame; | ||
| private findInsertIndex; | ||
| private findFrameInsertIndex; | ||
| private normalizeFrames; | ||
| private updateEntryStats; | ||
| private deriveFallbackDuration; | ||
| } | ||
| export {}; | ||
| //# sourceMappingURL=VideoL1Cache.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"VideoL1Cache.d.ts","sourceRoot":"","sources":["../../../src/cache/l1/VideoL1Cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAMtC,UAAU,QAAQ;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAYD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;GAOG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAqC;IACnE,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,YAAY,CAAK;gBAEb,MAAM,EAAE,QAAQ;IAK5B,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI;IA6BnD,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAwBvD,QAAQ,CACN,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,OAAO,GACnB,OAAO,GAAG,IAAI;IA0BjB;;OAEG;IACH,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAYpC;;OAEG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAKhC;;;;OAIG;IACH,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM;IAqC/D,KAAK,IAAI,IAAI;IAUb,WAAW,IAAI,eAAe;IAS9B,OAAO,CAAC,aAAa;IAMrB,OAAO,CAAC,WAAW;IAyBnB,OAAO,CAAC,WAAW;IAWnB,OAAO,CAAC,WAAW;IAqBnB,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,SAAS;IAkBjB,OAAO,CAAC,UAAU;IAmBlB,OAAO,CAAC,aAAa;IAMrB,OAAO,CAAC,eAAe;IAqBvB,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,iBAAiB;IASzB,OAAO,CAAC,eAAe;IAevB,OAAO,CAAC,oBAAoB;IAe5B,OAAO,CAAC,eAAe;IAgBvB,OAAO,CAAC,gBAAgB;IAQxB,OAAO,CAAC,sBAAsB;CAM/B"} | ||
| {"version":3,"file":"VideoL1Cache.d.ts","sourceRoot":"","sources":["../../../src/cache/l1/VideoL1Cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAItC,UAAU,QAAQ;IAChB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;GAKG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAgC;IAC7D,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,YAAY,CAAK;gBAEb,MAAM,EAAE,QAAQ;IAI5B,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI;IASnD,QAAQ,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO;IA+B5F,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAYpC,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAKhC,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM;IAkB/D,KAAK,IAAI,IAAI;IAUb,WAAW,IAAI,eAAe;IAc9B,OAAO,CAAC,iBAAiB;IA0BzB,OAAO,CAAC,eAAe;CAiBxB"} |
| import { RcFrame } from "../../model/RcFrame.js"; | ||
| import { findFrameIndex, findGopIndex } from "./gop-utils.js"; | ||
| const DEFAULT_GOP_INTERVAL_US = 2e6; | ||
| const BYTES_PER_MB = 1024 * 1024; | ||
| class VideoL1Cache { | ||
| entriesByClip = /* @__PURE__ */ new Map(); | ||
| framesByClip = /* @__PURE__ */ new Map(); | ||
| maxMemoryBytes; | ||
| gopIntervalUs; | ||
| currentBytes = 0; | ||
| constructor(config) { | ||
| this.maxMemoryBytes = config.maxMemoryMB * BYTES_PER_MB; | ||
| this.gopIntervalUs = config.gopIntervalUs ?? DEFAULT_GOP_INTERVAL_US; | ||
| } | ||
| get(timeUs, clipId) { | ||
| const clipEntries = this.entriesByClip.get(clipId); | ||
| if (!clipEntries || clipEntries.length === 0) { | ||
| return null; | ||
| } | ||
| const entry = this.findEntryByTime(clipEntries, timeUs); | ||
| if (!entry) { | ||
| return null; | ||
| } | ||
| const frameIndex = findFrameIndex( | ||
| { | ||
| index: entry.gopIndex, | ||
| startUs: entry.gopStartUs, | ||
| durationUs: entry.durationUs, | ||
| frames: entry.frames, | ||
| clipId: entry.clipId | ||
| }, | ||
| timeUs | ||
| ); | ||
| if (frameIndex === -1) { | ||
| return null; | ||
| } | ||
| return entry.frames[frameIndex] ?? null; | ||
| const frames = this.framesByClip.get(clipId); | ||
| if (!frames || frames.length === 0) return null; | ||
| const index = this.binarySearchFrame(frames, timeUs); | ||
| return index !== -1 ? frames[index] ?? null : null; | ||
| } | ||
| putGOP(gop, clipId, trackId) { | ||
| const gopIndex = gop.index ?? gop.startUs; | ||
| const existing = this.getEntryExact(clipId, gopIndex); | ||
| const frames = this.wrapFrames(gop.frames, clipId, gop.durationUs, trackId); | ||
| if (existing) { | ||
| existing.gopStartUs = gop.startUs; | ||
| this.mergeFrames(existing, frames, gop.durationUs); | ||
| return; | ||
| } | ||
| const entry = this.createEntry(clipId, { | ||
| gopIndex, | ||
| startUs: gop.startUs, | ||
| durationUs: gop.durationUs, | ||
| frames, | ||
| isKeyframe: gop.isKeyframe | ||
| addFrame(frame, clipId, frameDuration, trackId) { | ||
| const rcFrame = RcFrame.wrap(frame, { | ||
| trackId, | ||
| clipId, | ||
| timestampUs: frame.timestamp ?? 0, | ||
| durationUs: frame.duration ?? frameDuration | ||
| }); | ||
| this.registerEntry(entry); | ||
| this.currentBytes += entry.size; | ||
| } | ||
| addFrame(frame, clipId, frameDuration, trackId, gopSerial, isKeyframe) { | ||
| const timestamp = frame.timestamp ?? 0; | ||
| const gopIndex = typeof gopSerial === "number" ? gopSerial : findGopIndex(timestamp, this.gopIntervalUs); | ||
| const existing = this.getEntryExact(clipId, gopIndex); | ||
| const rcFrame = this.wrapFrame(frame, clipId, frameDuration, trackId, gopSerial, isKeyframe); | ||
| if (existing) { | ||
| this.mergeFrames(existing, [rcFrame], frameDuration); | ||
| return rcFrame; | ||
| let frames = this.framesByClip.get(clipId); | ||
| if (!frames) { | ||
| frames = []; | ||
| this.framesByClip.set(clipId, frames); | ||
| } | ||
| const entry = this.createEntry(clipId, { | ||
| gopIndex, | ||
| startUs: timestamp, | ||
| durationUs: frameDuration, | ||
| frames: [rcFrame], | ||
| isKeyframe: isKeyframe ?? true | ||
| }); | ||
| this.registerEntry(entry); | ||
| this.currentBytes += entry.size; | ||
| const timestamp = rcFrame.timestampUs ?? 0; | ||
| const insertIndex = this.findInsertIndex(frames, timestamp); | ||
| if (insertIndex < frames.length && (frames[insertIndex]?.timestampUs ?? 0) === timestamp) { | ||
| const oldFrame = frames[insertIndex]; | ||
| frames[insertIndex] = rcFrame; | ||
| this.currentBytes -= oldFrame?.sizeEstimate ?? 0; | ||
| oldFrame?.close?.(); | ||
| } else { | ||
| frames.splice(insertIndex, 0, rcFrame); | ||
| } | ||
| this.currentBytes += rcFrame.sizeEstimate; | ||
| return rcFrame; | ||
| } | ||
| /** | ||
| * Evict all cache entries for a specific clip | ||
| */ | ||
| invalidateClip(clipId) { | ||
| const entries = this.entriesByClip.get(clipId); | ||
| if (!entries) return; | ||
| for (const entry of entries) { | ||
| this.closeFrames(entry); | ||
| this.currentBytes -= entry.size; | ||
| const frames = this.framesByClip.get(clipId); | ||
| if (!frames) return; | ||
| for (const frame of frames) { | ||
| this.currentBytes -= frame.sizeEstimate; | ||
| frame?.close?.(); | ||
| } | ||
| this.entriesByClip.delete(clipId); | ||
| this.framesByClip.delete(clipId); | ||
| } | ||
| /** | ||
| * Check if a clip has any cached entries | ||
| */ | ||
| hasClip(clipId) { | ||
| const entries = this.entriesByClip.get(clipId); | ||
| return !!entries && entries.length > 0; | ||
| const frames = this.framesByClip.get(clipId); | ||
| return !!frames && frames.length > 0; | ||
| } | ||
| /** | ||
| * Get total frame count for a clip | ||
| * @param clipId - Clip identifier | ||
| * @param startTimeUs - Optional start time to count frames from (inclusive) | ||
| */ | ||
| getClipFrameCount(clipId, startTimeUs) { | ||
| const entries = this.entriesByClip.get(clipId); | ||
| if (!entries) return 0; | ||
| const frames = this.framesByClip.get(clipId); | ||
| if (!frames) return 0; | ||
| if (startTimeUs === void 0) { | ||
| return entries.reduce((sum, entry) => sum + entry.frames.length, 0); | ||
| return frames.length; | ||
| } | ||
| let count = 0; | ||
| for (const entry of entries) { | ||
| for (const frame of entry.frames) { | ||
| const frameTime = frame.timestampUs ?? entry.gopStartUs; | ||
| if (frameTime >= startTimeUs) { | ||
| count++; | ||
| } | ||
| for (const frame of frames) { | ||
| if ((frame.timestampUs ?? 0) >= startTimeUs) { | ||
| count++; | ||
| } | ||
@@ -118,156 +68,46 @@ } | ||
| } | ||
| // invalidateRange(startUs: TimeUs, endUs: TimeUs, clipId?: string): void { | ||
| // for (const entry of this.iterateEntries()) { | ||
| // if (clipId && entry.clipId !== clipId) { | ||
| // continue; | ||
| // } | ||
| // const gopEnd = entry.gopStartUs + entry.durationUs; | ||
| // if (entry.gopStartUs < endUs && gopEnd > startUs) { | ||
| // this.removeEntry(entry); | ||
| // } | ||
| // } | ||
| // } | ||
| clear() { | ||
| for (const entries of this.entriesByClip.values()) { | ||
| for (const entry of entries) { | ||
| this.closeFrames(entry); | ||
| for (const frames of this.framesByClip.values()) { | ||
| for (const frame of frames) { | ||
| frame?.close?.(); | ||
| } | ||
| } | ||
| this.entriesByClip.clear(); | ||
| this.framesByClip.clear(); | ||
| this.currentBytes = 0; | ||
| } | ||
| getMetadata() { | ||
| let totalFrames = 0; | ||
| for (const frames of this.framesByClip.values()) { | ||
| totalFrames += frames.length; | ||
| } | ||
| return { | ||
| size: this.currentBytes, | ||
| maxSize: this.maxMemoryBytes, | ||
| entries: this.countEntries(), | ||
| clipCount: this.entriesByClip.size | ||
| entries: totalFrames, | ||
| clipCount: this.framesByClip.size | ||
| }; | ||
| } | ||
| registerEntry(entry) { | ||
| const entries = this.ensureClipEntries(entry.clipId); | ||
| const insertIndex = this.findInsertIndex(entries, entry.gopIndex); | ||
| entries.splice(insertIndex, 0, entry); | ||
| } | ||
| createEntry(clipId, gop) { | ||
| const frames = this.normalizeFrames(gop.frames); | ||
| const entry = { | ||
| key: this.composeKey(clipId, gop.gopIndex), | ||
| clipId, | ||
| gopIndex: gop.gopIndex, | ||
| gopStartUs: gop.startUs, | ||
| durationUs: gop.durationUs, | ||
| frames, | ||
| size: 0 | ||
| }; | ||
| this.updateEntryStats(entry, this.deriveFallbackDuration(gop.durationUs, frames.length)); | ||
| return entry; | ||
| } | ||
| mergeFrames(entry, frames, fallbackDuration) { | ||
| const durationFallback = this.deriveFallbackDuration( | ||
| fallbackDuration, | ||
| entry.frames.length + frames.length | ||
| ); | ||
| for (const rcFrame of frames) { | ||
| this.insertFrame(entry, rcFrame, durationFallback); | ||
| } | ||
| this.updateEntryStats(entry, durationFallback); | ||
| } | ||
| insertFrame(entry, frame, fallbackDuration) { | ||
| const timestamp = frame.timestampUs ?? entry.gopStartUs; | ||
| const frames = entry.frames; | ||
| const insertIndex = this.findFrameInsertIndex(frames, timestamp); | ||
| if (insertIndex < frames.length && (frames[insertIndex]?.timestampUs ?? entry.gopStartUs) === timestamp) { | ||
| const oldFrame = frames[insertIndex]; | ||
| frames[insertIndex] = frame; | ||
| oldFrame?.close?.(); | ||
| } else { | ||
| frames.splice(insertIndex, 0, frame); | ||
| } | ||
| entry.size += frame.sizeEstimate; | ||
| const duration = frame.durationUs || fallbackDuration; | ||
| entry.durationUs = Math.max(entry.durationUs, timestamp + duration - entry.gopStartUs); | ||
| } | ||
| closeFrames(entry) { | ||
| for (const frame of entry.frames) { | ||
| frame?.close?.(); | ||
| } | ||
| } | ||
| composeKey(clipId, gopIndex) { | ||
| return `${clipId}:${gopIndex}`; | ||
| } | ||
| wrapFrame(frame, clipId, frameDuration, trackId, gopSerial, isKeyframe) { | ||
| return RcFrame.wrap(frame, { | ||
| trackId, | ||
| clipId, | ||
| timestampUs: frame.timestamp ?? 0, | ||
| durationUs: frame.duration ?? frameDuration, | ||
| gopSerial, | ||
| isKeyframe | ||
| }); | ||
| } | ||
| wrapFrames(frames, clipId, fallbackDuration, trackId) { | ||
| const wrapped = frames.map( | ||
| (frame) => frame instanceof RcFrame ? frame : this.wrapFrame( | ||
| frame, | ||
| clipId, | ||
| fallbackDuration / Math.max(frames.length, 1), | ||
| trackId | ||
| ) | ||
| ); | ||
| return this.normalizeFrames(wrapped); | ||
| } | ||
| getEntryExact(clipId, gopIndex) { | ||
| const entries = this.entriesByClip.get(clipId); | ||
| if (!entries) return void 0; | ||
| return entries.find((e) => e.gopIndex === gopIndex); | ||
| } | ||
| findEntryByTime(entries, timeUs) { | ||
| binarySearchFrame(frames, timeUs) { | ||
| let low = 0; | ||
| let high = entries.length - 1; | ||
| let high = frames.length - 1; | ||
| let result = -1; | ||
| while (low <= high) { | ||
| const mid = Math.floor((low + high) / 2); | ||
| const entry = entries[mid]; | ||
| if (!entry) break; | ||
| if (timeUs < entry.gopStartUs) { | ||
| const frame = frames[mid]; | ||
| if (!frame) break; | ||
| const frameTime = frame.timestampUs ?? 0; | ||
| const frameDuration = frame.durationUs ?? 0; | ||
| if (timeUs < frameTime) { | ||
| high = mid - 1; | ||
| } else if (timeUs >= entry.gopStartUs + entry.durationUs) { | ||
| } else if (timeUs >= frameTime + frameDuration) { | ||
| low = mid + 1; | ||
| } else { | ||
| return entry; | ||
| result = mid; | ||
| break; | ||
| } | ||
| } | ||
| return void 0; | ||
| return result; | ||
| } | ||
| countEntries() { | ||
| let count = 0; | ||
| for (const clipEntries of this.entriesByClip.values()) { | ||
| count += clipEntries.length; | ||
| } | ||
| return count; | ||
| } | ||
| ensureClipEntries(clipId) { | ||
| let entries = this.entriesByClip.get(clipId); | ||
| if (!entries) { | ||
| entries = []; | ||
| this.entriesByClip.set(clipId, entries); | ||
| } | ||
| return entries; | ||
| } | ||
| findInsertIndex(entries, gopIndex) { | ||
| findInsertIndex(frames, timestamp) { | ||
| let low = 0; | ||
| let high = entries.length; | ||
| while (low < high) { | ||
| const mid = Math.floor((low + high) / 2); | ||
| const entry = entries[mid]; | ||
| if (entry && entry.gopIndex < gopIndex) { | ||
| low = mid + 1; | ||
| } else { | ||
| high = mid; | ||
| } | ||
| } | ||
| return low; | ||
| } | ||
| findFrameInsertIndex(frames, timestamp) { | ||
| let low = 0; | ||
| let high = frames.length; | ||
@@ -285,30 +125,2 @@ while (low < high) { | ||
| } | ||
| normalizeFrames(frames) { | ||
| const seen = /* @__PURE__ */ new Set(); | ||
| const sorted = [...frames].sort((a, b) => (a.timestampUs ?? 0) - (b.timestampUs ?? 0)); | ||
| const result = []; | ||
| for (const frame of sorted) { | ||
| const ts = frame.timestampUs ?? 0; | ||
| if (seen.has(ts)) { | ||
| frame?.close?.(); | ||
| } else { | ||
| seen.add(ts); | ||
| result.push(frame); | ||
| } | ||
| } | ||
| return result; | ||
| } | ||
| updateEntryStats(entry, fallbackDuration) { | ||
| entry.size = entry.frames.reduce((acc, frame) => acc + frame.sizeEstimate, 0); | ||
| entry.durationUs = entry.frames.reduce((acc, frame) => { | ||
| const duration = frame.durationUs || fallbackDuration; | ||
| return Math.max(acc, (frame.timestampUs ?? entry.gopStartUs) + duration - entry.gopStartUs); | ||
| }, entry.durationUs); | ||
| } | ||
| deriveFallbackDuration(durationUs, frameCount) { | ||
| if (frameCount <= 1) { | ||
| return durationUs || this.gopIntervalUs; | ||
| } | ||
| return Math.max(durationUs / frameCount, this.gopIntervalUs / frameCount); | ||
| } | ||
| } | ||
@@ -315,0 +127,0 @@ export { |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"VideoL1Cache.js","sources":["../../../src/cache/l1/VideoL1Cache.ts"],"sourcesContent":["import type { TimeUs } from '../../model/types';\nimport type { GOP } from '../types';\nimport { RcFrame } from '../../model';\nimport { findFrameIndex, findGopIndex } from './gop-utils';\n\nconst DEFAULT_GOP_INTERVAL_US = 2_000_000;\nconst BYTES_PER_MB = 1024 * 1024;\n\ninterface L1Config {\n maxMemoryMB: number;\n maxGOPs?: number;\n gopIntervalUs?: number;\n}\n\ninterface L1CacheEntry {\n key: string;\n clipId: string;\n gopIndex: number;\n gopStartUs: TimeUs;\n durationUs: TimeUs;\n frames: RcFrame[];\n size: number;\n}\n\nexport interface L1CacheMetadata {\n size: number;\n maxSize: number;\n entries: number;\n clipCount: number;\n}\n\n/**\n * Simplified VideoL1Cache for 2-Clip strategy\n *\n * Clip lifecycle is managed by ClipSessionManager:\n * - No LRU eviction (clips evicted explicitly)\n * - No capacity limits (fixed 3 clips)\n * - Simple GOP storage per clip\n */\nexport class VideoL1Cache {\n private readonly entriesByClip = new Map<string, L1CacheEntry[]>();\n private maxMemoryBytes: number;\n private gopIntervalUs: number;\n private currentBytes = 0;\n\n constructor(config: L1Config) {\n this.maxMemoryBytes = config.maxMemoryMB * BYTES_PER_MB;\n this.gopIntervalUs = config.gopIntervalUs ?? DEFAULT_GOP_INTERVAL_US;\n }\n\n get(timeUs: TimeUs, clipId: string): RcFrame | null {\n const clipEntries = this.entriesByClip.get(clipId);\n if (!clipEntries || clipEntries.length === 0) {\n return null;\n }\n\n const entry = this.findEntryByTime(clipEntries, timeUs);\n if (!entry) {\n return null;\n }\n\n const frameIndex = findFrameIndex(\n {\n index: entry.gopIndex,\n startUs: entry.gopStartUs,\n durationUs: entry.durationUs,\n frames: entry.frames,\n isKeyframe: true,\n clipId: entry.clipId,\n },\n timeUs\n );\n if (frameIndex === -1) {\n return null;\n }\n\n return entry.frames[frameIndex] ?? null;\n }\n\n putGOP(gop: GOP, clipId: string, trackId: string): void {\n const gopIndex = gop.index ?? gop.startUs;\n const existing = this.getEntryExact(clipId, gopIndex);\n\n const frames = this.wrapFrames(gop.frames, clipId, gop.durationUs, trackId);\n\n if (existing) {\n existing.gopStartUs = gop.startUs;\n this.mergeFrames(existing, frames, gop.durationUs);\n return;\n }\n\n const entry = this.createEntry(clipId, {\n gopIndex,\n startUs: gop.startUs,\n durationUs: gop.durationUs,\n frames,\n isKeyframe: gop.isKeyframe,\n });\n\n this.registerEntry(entry);\n this.currentBytes += entry.size;\n }\n\n addFrame(\n frame: VideoFrame,\n clipId: string,\n frameDuration: TimeUs,\n trackId: string,\n gopSerial?: number,\n isKeyframe?: boolean\n ): RcFrame | null {\n const timestamp = frame.timestamp ?? 0;\n const gopIndex =\n typeof gopSerial === 'number' ? gopSerial : findGopIndex(timestamp, this.gopIntervalUs);\n const existing = this.getEntryExact(clipId, gopIndex);\n\n const rcFrame = this.wrapFrame(frame, clipId, frameDuration, trackId, gopSerial, isKeyframe);\n\n if (existing) {\n this.mergeFrames(existing, [rcFrame], frameDuration);\n return rcFrame;\n }\n\n const entry = this.createEntry(clipId, {\n gopIndex,\n startUs: timestamp,\n durationUs: frameDuration,\n frames: [rcFrame],\n isKeyframe: isKeyframe ?? true,\n });\n\n this.registerEntry(entry);\n this.currentBytes += entry.size;\n return rcFrame;\n }\n\n /**\n * Evict all cache entries for a specific clip\n */\n invalidateClip(clipId: string): void {\n const entries = this.entriesByClip.get(clipId);\n if (!entries) return;\n\n for (const entry of entries) {\n this.closeFrames(entry);\n this.currentBytes -= entry.size;\n }\n\n this.entriesByClip.delete(clipId);\n }\n\n /**\n * Check if a clip has any cached entries\n */\n hasClip(clipId: string): boolean {\n const entries = this.entriesByClip.get(clipId);\n return !!entries && entries.length > 0;\n }\n\n /**\n * Get total frame count for a clip\n * @param clipId - Clip identifier\n * @param startTimeUs - Optional start time to count frames from (inclusive)\n */\n getClipFrameCount(clipId: string, startTimeUs?: TimeUs): number {\n const entries = this.entriesByClip.get(clipId);\n if (!entries) return 0;\n\n // If no startTimeUs specified, return total frame count (backward compatible)\n if (startTimeUs === undefined) {\n return entries.reduce((sum, entry) => sum + entry.frames.length, 0);\n }\n\n // Count only frames at or after startTimeUs\n // Always check individual frame timestamps since gopSerial might not split correctly\n let count = 0;\n for (const entry of entries) {\n for (const frame of entry.frames) {\n const frameTime = frame.timestampUs ?? entry.gopStartUs;\n if (frameTime >= startTimeUs) {\n count++;\n }\n }\n }\n\n return count;\n }\n\n // invalidateRange(startUs: TimeUs, endUs: TimeUs, clipId?: string): void {\n // for (const entry of this.iterateEntries()) {\n // if (clipId && entry.clipId !== clipId) {\n // continue;\n // }\n\n // const gopEnd = entry.gopStartUs + entry.durationUs;\n // if (entry.gopStartUs < endUs && gopEnd > startUs) {\n // this.removeEntry(entry);\n // }\n // }\n // }\n\n clear(): void {\n for (const entries of this.entriesByClip.values()) {\n for (const entry of entries) {\n this.closeFrames(entry);\n }\n }\n this.entriesByClip.clear();\n this.currentBytes = 0;\n }\n\n getMetadata(): L1CacheMetadata {\n return {\n size: this.currentBytes,\n maxSize: this.maxMemoryBytes,\n entries: this.countEntries(),\n clipCount: this.entriesByClip.size,\n };\n }\n\n private registerEntry(entry: L1CacheEntry): void {\n const entries = this.ensureClipEntries(entry.clipId);\n const insertIndex = this.findInsertIndex(entries, entry.gopIndex);\n entries.splice(insertIndex, 0, entry);\n }\n\n private createEntry(\n clipId: string,\n gop: {\n gopIndex: number;\n startUs: TimeUs;\n durationUs: TimeUs;\n frames: RcFrame[];\n isKeyframe: boolean;\n }\n ): L1CacheEntry {\n const frames = this.normalizeFrames(gop.frames);\n const entry: L1CacheEntry = {\n key: this.composeKey(clipId, gop.gopIndex),\n clipId,\n gopIndex: gop.gopIndex,\n gopStartUs: gop.startUs,\n durationUs: gop.durationUs,\n frames,\n size: 0,\n };\n\n this.updateEntryStats(entry, this.deriveFallbackDuration(gop.durationUs, frames.length));\n return entry;\n }\n\n private mergeFrames(entry: L1CacheEntry, frames: RcFrame[], fallbackDuration: TimeUs): void {\n const durationFallback = this.deriveFallbackDuration(\n fallbackDuration,\n entry.frames.length + frames.length\n );\n for (const rcFrame of frames) {\n this.insertFrame(entry, rcFrame, durationFallback);\n }\n this.updateEntryStats(entry, durationFallback);\n }\n\n private insertFrame(entry: L1CacheEntry, frame: RcFrame, fallbackDuration: TimeUs): void {\n const timestamp = frame.timestampUs ?? entry.gopStartUs;\n const frames = entry.frames;\n const insertIndex = this.findFrameInsertIndex(frames, timestamp);\n\n if (\n insertIndex < frames.length &&\n (frames[insertIndex]?.timestampUs ?? entry.gopStartUs) === timestamp\n ) {\n const oldFrame = frames[insertIndex];\n frames[insertIndex] = frame;\n oldFrame?.close?.();\n } else {\n frames.splice(insertIndex, 0, frame);\n }\n\n entry.size += frame.sizeEstimate;\n const duration = frame.durationUs || fallbackDuration;\n entry.durationUs = Math.max(entry.durationUs, timestamp + duration - entry.gopStartUs);\n }\n\n private closeFrames(entry: L1CacheEntry): void {\n for (const frame of entry.frames) {\n frame?.close?.();\n }\n }\n\n private composeKey(clipId: string, gopIndex: number): string {\n return `${clipId}:${gopIndex}`;\n }\n\n private wrapFrame(\n frame: VideoFrame,\n clipId: string,\n frameDuration: TimeUs,\n trackId: string,\n gopSerial?: number,\n isKeyframe?: boolean\n ): RcFrame {\n return RcFrame.wrap(frame, {\n trackId,\n clipId,\n timestampUs: frame.timestamp ?? 0,\n durationUs: frame.duration ?? frameDuration,\n gopSerial,\n isKeyframe,\n });\n }\n\n private wrapFrames(\n frames: (RcFrame | VideoFrame)[],\n clipId: string,\n fallbackDuration: TimeUs,\n trackId: string\n ): RcFrame[] {\n const wrapped = frames.map((frame) =>\n frame instanceof RcFrame\n ? frame\n : this.wrapFrame(\n frame as VideoFrame,\n clipId,\n fallbackDuration / Math.max(frames.length, 1),\n trackId\n )\n );\n return this.normalizeFrames(wrapped);\n }\n\n private getEntryExact(clipId: string, gopIndex: number): L1CacheEntry | undefined {\n const entries = this.entriesByClip.get(clipId);\n if (!entries) return undefined;\n return entries.find((e) => e.gopIndex === gopIndex);\n }\n\n private findEntryByTime(entries: L1CacheEntry[], timeUs: TimeUs): L1CacheEntry | undefined {\n let low = 0;\n let high = entries.length - 1;\n\n while (low <= high) {\n const mid = Math.floor((low + high) / 2);\n const entry = entries[mid];\n if (!entry) break;\n\n if (timeUs < entry.gopStartUs) {\n high = mid - 1;\n } else if (timeUs >= entry.gopStartUs + entry.durationUs) {\n low = mid + 1;\n } else {\n return entry;\n }\n }\n\n return undefined;\n }\n\n private countEntries(): number {\n let count = 0;\n for (const clipEntries of this.entriesByClip.values()) {\n count += clipEntries.length;\n }\n return count;\n }\n\n private ensureClipEntries(clipId: string): L1CacheEntry[] {\n let entries = this.entriesByClip.get(clipId);\n if (!entries) {\n entries = [];\n this.entriesByClip.set(clipId, entries);\n }\n return entries;\n }\n\n private findInsertIndex(entries: L1CacheEntry[], gopIndex: number): number {\n let low = 0;\n let high = entries.length;\n while (low < high) {\n const mid = Math.floor((low + high) / 2);\n const entry = entries[mid];\n if (entry && entry.gopIndex < gopIndex) {\n low = mid + 1;\n } else {\n high = mid;\n }\n }\n return low;\n }\n\n private findFrameInsertIndex(frames: RcFrame[], timestamp: TimeUs): number {\n let low = 0;\n let high = frames.length;\n while (low < high) {\n const mid = Math.floor((low + high) / 2);\n const midTs = frames[mid]?.timestampUs ?? 0;\n if (midTs < timestamp) {\n low = mid + 1;\n } else {\n high = mid;\n }\n }\n return low;\n }\n\n private normalizeFrames(frames: RcFrame[]): RcFrame[] {\n const seen = new Set<number>();\n const sorted = [...frames].sort((a, b) => (a.timestampUs ?? 0) - (b.timestampUs ?? 0));\n const result: RcFrame[] = [];\n for (const frame of sorted) {\n const ts = frame.timestampUs ?? 0;\n if (seen.has(ts)) {\n frame?.close?.();\n } else {\n seen.add(ts);\n result.push(frame);\n }\n }\n return result;\n }\n\n private updateEntryStats(entry: L1CacheEntry, fallbackDuration: TimeUs): void {\n entry.size = entry.frames.reduce((acc, frame) => acc + frame.sizeEstimate, 0);\n entry.durationUs = entry.frames.reduce((acc, frame) => {\n const duration = frame.durationUs || fallbackDuration;\n return Math.max(acc, (frame.timestampUs ?? entry.gopStartUs) + duration - entry.gopStartUs);\n }, entry.durationUs);\n }\n\n private deriveFallbackDuration(durationUs: TimeUs, frameCount: number): TimeUs {\n if (frameCount <= 1) {\n return durationUs || this.gopIntervalUs;\n }\n return Math.max(durationUs / frameCount, this.gopIntervalUs / frameCount);\n }\n}\n"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"VideoL1Cache.js","sources":["../../../src/cache/l1/VideoL1Cache.ts"],"sourcesContent":["import type { TimeUs } from '../../model/types';\nimport { RcFrame } from '../../model';\n\nconst BYTES_PER_MB = 1024 * 1024;\n\ninterface L1Config {\n maxMemoryMB: number;\n}\n\nexport interface L1CacheMetadata {\n size: number;\n maxSize: number;\n entries: number;\n clipCount: number;\n}\n\n/**\n * Simplified VideoL1Cache - Single-layer frame storage per clip\n *\n * Each clip stores frames in a sorted array by timestamp.\n * No GOP management - frames are directly stored and retrieved via binary search.\n */\nexport class VideoL1Cache {\n private readonly framesByClip = new Map<string, RcFrame[]>();\n private readonly maxMemoryBytes: number;\n private currentBytes = 0;\n\n constructor(config: L1Config) {\n this.maxMemoryBytes = config.maxMemoryMB * BYTES_PER_MB;\n }\n\n get(timeUs: TimeUs, clipId: string): RcFrame | null {\n const frames = this.framesByClip.get(clipId);\n if (!frames || frames.length === 0) return null;\n\n // Binary search for frame at timeUs\n const index = this.binarySearchFrame(frames, timeUs);\n return index !== -1 ? (frames[index] ?? null) : null;\n }\n\n addFrame(frame: VideoFrame, clipId: string, frameDuration: TimeUs, trackId: string): RcFrame {\n const rcFrame = RcFrame.wrap(frame, {\n trackId,\n clipId,\n timestampUs: frame.timestamp ?? 0,\n durationUs: frame.duration ?? frameDuration,\n });\n\n let frames = this.framesByClip.get(clipId);\n if (!frames) {\n frames = [];\n this.framesByClip.set(clipId, frames);\n }\n\n const timestamp = rcFrame.timestampUs ?? 0;\n const insertIndex = this.findInsertIndex(frames, timestamp);\n\n // Check for duplicate timestamp\n if (insertIndex < frames.length && (frames[insertIndex]?.timestampUs ?? 0) === timestamp) {\n const oldFrame = frames[insertIndex];\n frames[insertIndex] = rcFrame;\n this.currentBytes -= oldFrame?.sizeEstimate ?? 0;\n oldFrame?.close?.();\n } else {\n frames.splice(insertIndex, 0, rcFrame);\n }\n\n this.currentBytes += rcFrame.sizeEstimate;\n return rcFrame;\n }\n\n invalidateClip(clipId: string): void {\n const frames = this.framesByClip.get(clipId);\n if (!frames) return;\n\n for (const frame of frames) {\n this.currentBytes -= frame.sizeEstimate;\n frame?.close?.();\n }\n\n this.framesByClip.delete(clipId);\n }\n\n hasClip(clipId: string): boolean {\n const frames = this.framesByClip.get(clipId);\n return !!frames && frames.length > 0;\n }\n\n getClipFrameCount(clipId: string, startTimeUs?: TimeUs): number {\n const frames = this.framesByClip.get(clipId);\n if (!frames) return 0;\n\n if (startTimeUs === undefined) {\n return frames.length;\n }\n\n // Count frames >= startTimeUs\n let count = 0;\n for (const frame of frames) {\n if ((frame.timestampUs ?? 0) >= startTimeUs) {\n count++;\n }\n }\n return count;\n }\n\n clear(): void {\n for (const frames of this.framesByClip.values()) {\n for (const frame of frames) {\n frame?.close?.();\n }\n }\n this.framesByClip.clear();\n this.currentBytes = 0;\n }\n\n getMetadata(): L1CacheMetadata {\n let totalFrames = 0;\n for (const frames of this.framesByClip.values()) {\n totalFrames += frames.length;\n }\n\n return {\n size: this.currentBytes,\n maxSize: this.maxMemoryBytes,\n entries: totalFrames,\n clipCount: this.framesByClip.size,\n };\n }\n\n private binarySearchFrame(frames: RcFrame[], timeUs: TimeUs): number {\n let low = 0;\n let high = frames.length - 1;\n let result = -1;\n\n while (low <= high) {\n const mid = Math.floor((low + high) / 2);\n const frame = frames[mid];\n if (!frame) break;\n\n const frameTime = frame.timestampUs ?? 0;\n const frameDuration = frame.durationUs ?? 0;\n\n if (timeUs < frameTime) {\n high = mid - 1;\n } else if (timeUs >= frameTime + frameDuration) {\n low = mid + 1;\n } else {\n result = mid;\n break;\n }\n }\n\n return result;\n }\n\n private findInsertIndex(frames: RcFrame[], timestamp: TimeUs): number {\n let low = 0;\n let high = frames.length;\n\n while (low < high) {\n const mid = Math.floor((low + high) / 2);\n const midTs = frames[mid]?.timestampUs ?? 0;\n\n if (midTs < timestamp) {\n low = mid + 1;\n } else {\n high = mid;\n }\n }\n\n return low;\n }\n}\n"],"names":[],"mappings":"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"} |
@@ -1,2 +0,1 @@ | ||
| import { RcFrame } from '../model'; | ||
| import { TimeUs } from '../model/types'; | ||
@@ -58,16 +57,2 @@ | ||
| } | ||
| export interface GOP { | ||
| /** GOP index */ | ||
| index: number; | ||
| /** Start time in microseconds */ | ||
| startUs: TimeUs; | ||
| /** Duration in microseconds */ | ||
| durationUs: TimeUs; | ||
| /** Video frames in this GOP */ | ||
| frames: RcFrame[]; | ||
| /** Whether this GOP starts with a keyframe */ | ||
| isKeyframe: boolean; | ||
| /** Clip ID for clip-aware caching */ | ||
| clipId: string; | ||
| } | ||
| export interface ClipMetadata { | ||
@@ -74,0 +59,0 @@ /** Clip identifier */ |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/cache/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACxC;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAE7C,MAAM,WAAW,WAAW;IAC1B,qCAAqC;IACrC,SAAS,EAAE,MAAM,CAAC;IAElB,qCAAqC;IACrC,SAAS,EAAE,MAAM,CAAC;IAElB,4BAA4B;IAC5B,cAAc,EAAE,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;IAEvC,sCAAsC;IACtC,gBAAgB,EAAE,MAAM,CAAC;IAEzB,uCAAuC;IACvC,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,UAAU;IACzB,0BAA0B;IAC1B,EAAE,EAAE;QACF,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IAEF,0BAA0B;IAC1B,EAAE,EAAE;QACF,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IAEF,yBAAyB;IACzB,KAAK,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AAED,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,gBAAgB;IAChB,GAAG,EAAE,MAAM,CAAC;IAEZ,kBAAkB;IAClB,IAAI,EAAE,CAAC,CAAC;IAER,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAC;IAEb,yBAAyB;IACzB,SAAS,EAAE,MAAM,CAAC;IAElB,4BAA4B;IAC5B,cAAc,EAAE,MAAM,CAAC;IAEvB,mBAAmB;IACnB,WAAW,EAAE,MAAM,CAAC;IAEpB,+FAA+F;IAC/F,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACxB;AAED,MAAM,WAAW,GAAG;IAClB,gBAAgB;IAChB,KAAK,EAAE,MAAM,CAAC;IAEd,iCAAiC;IACjC,OAAO,EAAE,MAAM,CAAC;IAEhB,+BAA+B;IAC/B,UAAU,EAAE,MAAM,CAAC;IAEnB,+BAA+B;IAC/B,MAAM,EAAE,OAAO,EAAE,CAAC;IAElB,8CAA8C;IAC9C,UAAU,EAAE,OAAO,CAAC;IAEpB,qCAAqC;IACrC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,sBAAsB;IACtB,EAAE,EAAE,MAAM,CAAC;IAEX,8BAA8B;IAC9B,IAAI,EAAE,MAAM,CAAC;IAEb,gCAAgC;IAChC,OAAO,EAAE,MAAM,CAAC;IAEhB,eAAe;IACf,UAAU,EAAE,MAAM,CAAC;IAEnB,uBAAuB;IACvB,UAAU,EAAE,MAAM,CAAC;IAEnB,0BAA0B;IAC1B,UAAU,EAAE,MAAM,CAAC;IAEnB,0BAA0B;IAC1B,WAAW,CAAC,EAAE;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;CACH"} | ||
| {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/cache/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAE7C,MAAM,WAAW,WAAW;IAC1B,qCAAqC;IACrC,SAAS,EAAE,MAAM,CAAC;IAElB,qCAAqC;IACrC,SAAS,EAAE,MAAM,CAAC;IAElB,4BAA4B;IAC5B,cAAc,EAAE,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;IAEvC,sCAAsC;IACtC,gBAAgB,EAAE,MAAM,CAAC;IAEzB,uCAAuC;IACvC,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,UAAU;IACzB,0BAA0B;IAC1B,EAAE,EAAE;QACF,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IAEF,0BAA0B;IAC1B,EAAE,EAAE;QACF,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IAEF,yBAAyB;IACzB,KAAK,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AAED,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,gBAAgB;IAChB,GAAG,EAAE,MAAM,CAAC;IAEZ,kBAAkB;IAClB,IAAI,EAAE,CAAC,CAAC;IAER,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAC;IAEb,yBAAyB;IACzB,SAAS,EAAE,MAAM,CAAC;IAElB,4BAA4B;IAC5B,cAAc,EAAE,MAAM,CAAC;IAEvB,mBAAmB;IACnB,WAAW,EAAE,MAAM,CAAC;IAEpB,+FAA+F;IAC/F,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC3B,sBAAsB;IACtB,EAAE,EAAE,MAAM,CAAC;IAEX,8BAA8B;IAC9B,IAAI,EAAE,MAAM,CAAC;IAEb,gCAAgC;IAChC,OAAO,EAAE,MAAM,CAAC;IAEhB,eAAe;IACf,UAAU,EAAE,MAAM,CAAC;IAEnB,uBAAuB;IACvB,UAAU,EAAE,MAAM,CAAC;IAEnB,0BAA0B;IAC1B,UAAU,EAAE,MAAM,CAAC;IAEnB,0BAA0B;IAC1B,WAAW,CAAC,EAAE;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;CACH"} |
@@ -24,5 +24,2 @@ import { hasResourceId } from "../model/types.js"; | ||
| start() { | ||
| if (this._isRunning) return; | ||
| this._isRunning = true; | ||
| this.scheduleNextRender(); | ||
| } | ||
@@ -29,0 +26,0 @@ async stop() { |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"PreRenderService.js","sources":["../../src/controllers/PreRenderService.ts"],"sourcesContent":["import type { IPreRenderService, RenderStrategy, PreRenderStatus } from './types';\nimport type { TimeUs } from '../model/types';\nimport type { Orchestrator } from '../orchestrator';\nimport { hasResourceId } from '../model/types';\n\n/**\n * PreRenderService: Background pre-render for filling L2 cache\n *\n * Dual-mode strategy:\n * 1. When playback active: Ensure 2-Clip window (Prev/Current/Next) for preview\n * 2. When idle: Continue rendering subsequent clips for L2 export cache\n *\n * Goals:\n * - Maintain smooth preview (2-Clip L1 cache)\n * - Build complete L2 cache for fast export\n * - Operate in background without blocking\n */\nexport class PreRenderService implements IPreRenderService {\n private orchestrator: Orchestrator;\n private _isRunning = false;\n private _isPaused = false;\n private renderLoopId: number | null = null;\n private _framesRendered = 0;\n private _currentTimeUs: TimeUs = 0;\n private isPlaybackActive = false;\n private strategy: RenderStrategy = {\n direction: 'forward',\n lookahead: 3_000_000,\n lookbehind: 2_000_000,\n keyframesOnly: false,\n };\n\n private isRendering = false;\n private highPriorityMode = false;\n private exportWaiters: Array<{ resolve: () => void; reject: (err: Error) => void }> = [];\n private progressCallback: ((completed: number, total: number) => void) | null = null;\n\n constructor(orchestrator: Orchestrator, _eventBus: any) {\n this.orchestrator = orchestrator;\n }\n\n start(): void {\n if (this._isRunning) return;\n this._isRunning = true;\n this.scheduleNextRender();\n }\n\n async stop(): Promise<void> {\n this._isRunning = false;\n\n if (this.renderLoopId !== null) {\n cancelIdleCallback(this.renderLoopId);\n this.renderLoopId = null;\n }\n }\n\n pause(): void {\n this._isPaused = true;\n }\n\n resume(): void {\n this._isPaused = false;\n if (this._isRunning && !this.renderLoopId) {\n this.scheduleNextRender();\n }\n }\n\n setWindow(size: TimeUs): void {\n this.strategy.lookahead = size;\n }\n\n setStrategy(strategy: Partial<RenderStrategy>): void {\n this.strategy = { ...this.strategy, ...strategy };\n }\n\n setPriority(_priority: 'low' | 'normal' | 'high'): void {\n // Simplified - no-op for 2-Clip strategy\n }\n\n updatePlaybackTime(timeUs: TimeUs): void {\n this._currentTimeUs = timeUs;\n }\n\n setPlaybackActive(active: boolean): void {\n this.isPlaybackActive = active;\n }\n\n get isRunning(): boolean {\n return this._isRunning;\n }\n\n get queueSize(): number {\n return 0;\n }\n\n get status(): PreRenderStatus {\n return {\n isRunning: this._isRunning,\n framesRendered: this._framesRendered,\n currentTimeUs: this._currentTimeUs,\n };\n }\n\n private scheduleNextRender(): void {\n if (!this._isRunning) return;\n\n if (this.highPriorityMode) {\n // High priority: use requestAnimationFrame for faster rendering\n this.renderLoopId = requestAnimationFrame(async () => {\n await this.renderTick();\n this.scheduleNextRender();\n }) as any;\n } else {\n // Normal priority: use requestIdleCallback\n this.renderLoopId = requestIdleCallback(\n async () => {\n await this.renderTick();\n this.scheduleNextRender();\n },\n { timeout: 100 }\n );\n }\n }\n\n private async renderTick(): Promise<void> {\n if (!this._isRunning || this._isPaused || !this.orchestrator.compositionModel) {\n return;\n }\n\n const model = this.orchestrator.compositionModel;\n\n if (this.isPlaybackActive) {\n // Playback mode: Do nothing, PlaybackController manages 2-clip window\n return;\n }\n\n // If already rendering, skip this tick\n if (this.isRendering) {\n return;\n }\n\n // Idle mode: Continue rendering clips beyond 2-Clip window for L2 cache\n // Only process main track clips (attachments are already included)\n const mainTrack = model.findTrack(model.mainTrackId);\n const allClips = mainTrack?.clips ?? [];\n\n if (allClips.length === 0) {\n return;\n }\n // Find next clip to render, skip if resource is loading\n let clipToRender = null;\n let completedCount = 0;\n\n for (const clip of allClips) {\n // Check L2 cache\n const inL2 = await this.orchestrator.cacheManager.hasClipInL2(clip.id, 'video');\n if (inL2) {\n completedCount++;\n continue;\n }\n\n // Check if resource is being loaded by preview channel\n if (\n hasResourceId(clip) &&\n this.orchestrator.resourceLoader.isResourceLoading(clip.resourceId)\n ) {\n continue; // Preview priority: skip this clip, will retry in next tick\n }\n\n // Found available clip\n clipToRender = clip;\n break;\n }\n\n // Handle completion\n if (!clipToRender) {\n if (completedCount < allClips.length) {\n // All unprocessed clips are temporarily skipped due to resource conflicts\n // Continue loop, will retry in next tick\n return;\n }\n\n // Really done: all clips are in L2\n if (this.highPriorityMode) {\n this.exportWaiters.forEach((w) => w.resolve());\n this.exportWaiters = [];\n this.highPriorityMode = false;\n this.progressCallback = null;\n }\n\n void this.stop();\n return;\n }\n\n // Render this clip completely with concurrency protection\n this.isRendering = true;\n try {\n const rendered = await this.renderClipToL2(clipToRender.id);\n if (!rendered) {\n // Resource conflict: don't mark as processed, will retry in next tick\n return;\n }\n\n // Report progress in high priority mode\n if (this.highPriorityMode && this.progressCallback) {\n const totalClips = allClips.length;\n // completedCount from loop + 1 for the clip just rendered\n this.progressCallback(completedCount + 1, totalClips);\n }\n } catch (error) {\n console.error(`[PreRenderService] Failed to render clip ${clipToRender.id}:`, error);\n\n // Notify waiters of error in high priority mode\n if (this.highPriorityMode) {\n this.exportWaiters.forEach((w) => w.reject(error as Error));\n this.exportWaiters = [];\n this.highPriorityMode = false;\n this.progressCallback = null;\n }\n } finally {\n this.isRendering = false;\n }\n }\n\n /**\n * Render a complete clip to L2 cache\n * Creates dedicated pipeline that bypasses L1 window management\n */\n private async renderClipToL2(clipId: string): Promise<boolean> {\n const model = this.orchestrator.compositionModel;\n if (!model) return false;\n\n const clip = model.findClip(clipId);\n if (!clip) return false;\n\n // Start L2 rendering\n const rendered = await this.orchestrator.renderClipForL2(clipId);\n\n // Update counter\n const fps = model.fps || 30;\n const expectedFrames = Math.ceil((clip.durationUs / 1_000_000) * fps);\n this._framesRendered += expectedFrames;\n\n return rendered;\n }\n\n /**\n * Ensure all clips are in L2 cache\n * Switches to high priority mode and waits for completion\n */\n async ensureClipsInL2(options?: {\n onProgress?: (completed: number, total: number) => void;\n signal?: AbortSignal;\n }): Promise<void> {\n // Switch to high priority mode and wait for all clips to complete\n return new Promise<void>((resolve, reject) => {\n this.highPriorityMode = true;\n this.progressCallback = options?.onProgress || null;\n this.exportWaiters.push({ resolve, reject });\n\n // Cancel current idle callback and restart with RAF\n this.stop();\n this.start();\n });\n }\n}\n"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"PreRenderService.js","sources":["../../src/controllers/PreRenderService.ts"],"sourcesContent":["import type { IPreRenderService, RenderStrategy, PreRenderStatus } from './types';\nimport type { TimeUs } from '../model/types';\nimport type { Orchestrator } from '../orchestrator';\nimport { hasResourceId } from '../model/types';\n\n/**\n * PreRenderService: Background pre-render for filling L2 cache\n *\n * Dual-mode strategy:\n * 1. When playback active: Ensure 2-Clip window (Prev/Current/Next) for preview\n * 2. When idle: Continue rendering subsequent clips for L2 export cache\n *\n * Goals:\n * - Maintain smooth preview (2-Clip L1 cache)\n * - Build complete L2 cache for fast export\n * - Operate in background without blocking\n */\nexport class PreRenderService implements IPreRenderService {\n private orchestrator: Orchestrator;\n private _isRunning = false;\n private _isPaused = false;\n private renderLoopId: number | null = null;\n private _framesRendered = 0;\n private _currentTimeUs: TimeUs = 0;\n private isPlaybackActive = false;\n private strategy: RenderStrategy = {\n direction: 'forward',\n lookahead: 3_000_000,\n lookbehind: 2_000_000,\n keyframesOnly: false,\n };\n\n private isRendering = false;\n private highPriorityMode = false;\n private exportWaiters: Array<{ resolve: () => void; reject: (err: Error) => void }> = [];\n private progressCallback: ((completed: number, total: number) => void) | null = null;\n\n constructor(orchestrator: Orchestrator, _eventBus: any) {\n this.orchestrator = orchestrator;\n }\n\n start(): void {\n // if (this._isRunning) return;\n // this._isRunning = true;\n // this.scheduleNextRender();\n }\n\n async stop(): Promise<void> {\n this._isRunning = false;\n\n if (this.renderLoopId !== null) {\n cancelIdleCallback(this.renderLoopId);\n this.renderLoopId = null;\n }\n }\n\n pause(): void {\n this._isPaused = true;\n }\n\n resume(): void {\n this._isPaused = false;\n if (this._isRunning && !this.renderLoopId) {\n this.scheduleNextRender();\n }\n }\n\n setWindow(size: TimeUs): void {\n this.strategy.lookahead = size;\n }\n\n setStrategy(strategy: Partial<RenderStrategy>): void {\n this.strategy = { ...this.strategy, ...strategy };\n }\n\n setPriority(_priority: 'low' | 'normal' | 'high'): void {\n // Simplified - no-op for 2-Clip strategy\n }\n\n updatePlaybackTime(timeUs: TimeUs): void {\n this._currentTimeUs = timeUs;\n }\n\n setPlaybackActive(active: boolean): void {\n this.isPlaybackActive = active;\n }\n\n get isRunning(): boolean {\n return this._isRunning;\n }\n\n get queueSize(): number {\n return 0;\n }\n\n get status(): PreRenderStatus {\n return {\n isRunning: this._isRunning,\n framesRendered: this._framesRendered,\n currentTimeUs: this._currentTimeUs,\n };\n }\n\n private scheduleNextRender(): void {\n if (!this._isRunning) return;\n\n if (this.highPriorityMode) {\n // High priority: use requestAnimationFrame for faster rendering\n this.renderLoopId = requestAnimationFrame(async () => {\n await this.renderTick();\n this.scheduleNextRender();\n }) as any;\n } else {\n // Normal priority: use requestIdleCallback\n this.renderLoopId = requestIdleCallback(\n async () => {\n await this.renderTick();\n this.scheduleNextRender();\n },\n { timeout: 100 }\n );\n }\n }\n\n private async renderTick(): Promise<void> {\n if (!this._isRunning || this._isPaused || !this.orchestrator.compositionModel) {\n return;\n }\n\n const model = this.orchestrator.compositionModel;\n\n if (this.isPlaybackActive) {\n // Playback mode: Do nothing, PlaybackController manages 2-clip window\n return;\n }\n\n // If already rendering, skip this tick\n if (this.isRendering) {\n return;\n }\n\n // Idle mode: Continue rendering clips beyond 2-Clip window for L2 cache\n // Only process main track clips (attachments are already included)\n const mainTrack = model.findTrack(model.mainTrackId);\n const allClips = mainTrack?.clips ?? [];\n\n if (allClips.length === 0) {\n return;\n }\n // Find next clip to render, skip if resource is loading\n let clipToRender = null;\n let completedCount = 0;\n\n for (const clip of allClips) {\n // Check L2 cache\n const inL2 = await this.orchestrator.cacheManager.hasClipInL2(clip.id, 'video');\n if (inL2) {\n completedCount++;\n continue;\n }\n\n // Check if resource is being loaded by preview channel\n if (\n hasResourceId(clip) &&\n this.orchestrator.resourceLoader.isResourceLoading(clip.resourceId)\n ) {\n continue; // Preview priority: skip this clip, will retry in next tick\n }\n\n // Found available clip\n clipToRender = clip;\n break;\n }\n\n // Handle completion\n if (!clipToRender) {\n if (completedCount < allClips.length) {\n // All unprocessed clips are temporarily skipped due to resource conflicts\n // Continue loop, will retry in next tick\n return;\n }\n\n // Really done: all clips are in L2\n if (this.highPriorityMode) {\n this.exportWaiters.forEach((w) => w.resolve());\n this.exportWaiters = [];\n this.highPriorityMode = false;\n this.progressCallback = null;\n }\n\n void this.stop();\n return;\n }\n\n // Render this clip completely with concurrency protection\n this.isRendering = true;\n try {\n const rendered = await this.renderClipToL2(clipToRender.id);\n if (!rendered) {\n // Resource conflict: don't mark as processed, will retry in next tick\n return;\n }\n\n // Report progress in high priority mode\n if (this.highPriorityMode && this.progressCallback) {\n const totalClips = allClips.length;\n // completedCount from loop + 1 for the clip just rendered\n this.progressCallback(completedCount + 1, totalClips);\n }\n } catch (error) {\n console.error(`[PreRenderService] Failed to render clip ${clipToRender.id}:`, error);\n\n // Notify waiters of error in high priority mode\n if (this.highPriorityMode) {\n this.exportWaiters.forEach((w) => w.reject(error as Error));\n this.exportWaiters = [];\n this.highPriorityMode = false;\n this.progressCallback = null;\n }\n } finally {\n this.isRendering = false;\n }\n }\n\n /**\n * Render a complete clip to L2 cache\n * Creates dedicated pipeline that bypasses L1 window management\n */\n private async renderClipToL2(clipId: string): Promise<boolean> {\n const model = this.orchestrator.compositionModel;\n if (!model) return false;\n\n const clip = model.findClip(clipId);\n if (!clip) return false;\n\n // Start L2 rendering\n const rendered = await this.orchestrator.renderClipForL2(clipId);\n\n // Update counter\n const fps = model.fps || 30;\n const expectedFrames = Math.ceil((clip.durationUs / 1_000_000) * fps);\n this._framesRendered += expectedFrames;\n\n return rendered;\n }\n\n /**\n * Ensure all clips are in L2 cache\n * Switches to high priority mode and waits for completion\n */\n async ensureClipsInL2(options?: {\n onProgress?: (completed: number, total: number) => void;\n signal?: AbortSignal;\n }): Promise<void> {\n // Switch to high priority mode and wait for all clips to complete\n return new Promise<void>((resolve, reject) => {\n this.highPriorityMode = true;\n this.progressCallback = options?.onProgress || null;\n this.exportWaiters.push({ resolve, reject });\n\n // Cancel current idle callback and restart with RAF\n this.stop();\n this.start();\n });\n }\n}\n"],"names":[],"mappings":"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"} |
@@ -37,6 +37,9 @@ import { CompositionModelData, Track, Clip, Resource, TimeUs } from './types'; | ||
| /** | ||
| * Get all clip IDs that should be cached using 2-Clip strategy | ||
| * Returns array of unique clip IDs (Prev/Current/Next) | ||
| * Get all clip IDs that should be cached using adaptive strategy | ||
| * - Short clips (≤ maxDuration): cache Current + Next (smooth transitions) | ||
| * - Long clips (> maxDuration): cache Current only (memory control) | ||
| * @param timeUs - Current playback time | ||
| * @param maxDuration - Max duration for 2-clip strategy (default 5s) | ||
| */ | ||
| getClipsToCacheAtTime(timeUs: TimeUs): Set<string>; | ||
| getClipsToCacheAtTime(timeUs: TimeUs, maxDuration?: number): Set<string>; | ||
| getResource(id: string): Resource | null; | ||
@@ -43,0 +46,0 @@ updateResourceState(id: string, state: 'pending' | 'loading' | 'ready' | 'error'): void; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"CompositionModel.d.ts","sourceRoot":"","sources":["../../src/model/CompositionModel.ts"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"CompositionModel.d.ts","sourceRoot":"","sources":["../../src/model/CompositionModel.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,MAAM,EAGP,MAAM,SAAS,CAAC;AAIjB,qBAAa,gBAAgB;IAC3B,SAAgB,OAAO,EAAG,KAAK,CAAU;IACzC,SAAgB,GAAG,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IAChC,UAAU,EAAG,MAAM,CAAC;IAC3B,SAAgB,WAAW,EAAE,MAAM,CAAC;IAC7B,MAAM,EAAE,KAAK,EAAE,CAAC;IACvB,SAAgB,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAEjD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;IAC9C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;IAC5C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAsB;IAEvD,SAAgB,YAAY,CAAC,EAAE;QAC7B,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,CAAC;IAEF,SAAgB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;gBAElC,IAAI,EAAE,oBAAoB;IA6BtC,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI;IAInC,eAAe,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,GAAG,IAAI,GAAG,KAAK,EAAE;IAKhF,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAIjC,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE;IAoBxD,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE;IAgBtD,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE;IAKpD,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAcpE;;;OAGG;IACH,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE;IAsCvF;;;;;;OAMG;IACH,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,SAAY,GAAG,GAAG,CAAC,MAAM,CAAC;IAiB3E,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI;IAIxC,mBAAmB,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,IAAI;IAOvF,kBAAkB,IAAI,QAAQ,EAAE;IAahC,WAAW,IAAI,MAAM;IAIrB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IASzC,YAAY,CAAC,OAAO,CAAC,EAAE;QACrB,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,SAAS,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAC;KACzC,GAAG,IAAI;IAkCR,OAAO,CAAC,kBAAkB;IA0D1B,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,qBAAqB;IAc7B;;OAEG;IACH,uBAAuB,CACrB,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,aAAa,EAAE,MAAM,GAAG,SAAS,GAChC,IAAI;IA2BP;;OAEG;IACH,mBAAmB,IAAI,IAAI;IAY3B,OAAO,CAAC,oBAAoB;IAgF5B,OAAO,CAAC,cAAc;CAoBvB"} |
@@ -127,10 +127,17 @@ import { hasResourceId } from "./types.js"; | ||
| /** | ||
| * Get all clip IDs that should be cached using 2-Clip strategy | ||
| * Returns array of unique clip IDs (Prev/Current/Next) | ||
| * Get all clip IDs that should be cached using adaptive strategy | ||
| * - Short clips (≤ maxDuration): cache Current + Next (smooth transitions) | ||
| * - Long clips (> maxDuration): cache Current only (memory control) | ||
| * @param timeUs - Current playback time | ||
| * @param maxDuration - Max duration for 2-clip strategy (default 5s) | ||
| */ | ||
| getClipsToCacheAtTime(timeUs) { | ||
| getClipsToCacheAtTime(timeUs, maxDuration = 5e6) { | ||
| const { current, next } = this.getNeighboringClips(timeUs); | ||
| const clipIds = /* @__PURE__ */ new Set(); | ||
| if (current) clipIds.add(current); | ||
| if (next) clipIds.add(next); | ||
| if (!current) return clipIds; | ||
| clipIds.add(current); | ||
| const currentClip = this.findClip(current); | ||
| if (currentClip && currentClip.durationUs <= maxDuration && next) { | ||
| clipIds.add(next); | ||
| } | ||
| return clipIds; | ||
@@ -137,0 +144,0 @@ } |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"CompositionModel.js","sources":["../../src/model/CompositionModel.ts"],"sourcesContent":["import {\n CompositionModelData,\n Track,\n Clip,\n Resource,\n TimeUs,\n AnimationEffect,\n hasResourceId,\n} from './types';\nimport { binarySearchRange, binarySearchOverlapping } from '../utils/binary-search';\nimport { validateCompositionStructure } from './validation';\n\nexport class CompositionModel {\n public readonly version = '1.0' as const;\n public readonly fps: 24 | 25 | 30 | 60;\n public durationUs!: TimeUs; // Assigned in buildIndexes()\n public readonly mainTrackId: string;\n public tracks: Track[];\n public readonly resources: Map<string, Resource>;\n\n private readonly trackMap: Map<string, Track>;\n private readonly clipMap: Map<string, Clip>;\n private readonly resourceRefCount: Map<string, number>;\n\n public readonly renderConfig?: {\n width: number;\n height: number;\n backgroundColor?: string;\n };\n\n public readonly ext?: Record<string, unknown>;\n\n constructor(data: CompositionModelData) {\n const errors = validateCompositionStructure(data);\n if (errors.length > 0) {\n throw new Error(\n `Validation failed:\\n${errors.map((e) => `${e.path}: ${e.message}`).join('\\n')}`\n );\n }\n\n this.fps = data.fps;\n this.mainTrackId = data.mainTrackId ?? 'main';\n this.tracks = data.tracks;\n this.resources = new Map(Object.entries(data.resources));\n this.renderConfig = data.renderConfig;\n this.ext = data.ext;\n\n // Build indexes\n this.trackMap = new Map();\n this.clipMap = new Map();\n this.resourceRefCount = new Map();\n\n this.buildIndexes();\n\n console.log(\n 'CompositionModel constructed',\n this.tracks.find((t) => t.id === this.mainTrackId)?.clips[0]\n );\n }\n\n // Track operations\n findTrack(id: string): Track | null {\n return this.trackMap.get(id) || null;\n }\n\n getTracksByKind(kind: 'video' | 'audio' | 'caption' | 'overlay' | 'fx'): Track[] {\n return this.tracks.filter((track) => track.kind === kind);\n }\n\n // Clip operations with binary search optimization\n findClip(id: string): Clip | null {\n return this.clipMap.get(id) || null;\n }\n\n getClipsAtTime(timeUs: TimeUs, trackId?: string): Clip[] {\n const tracks = trackId ? [this.findTrack(trackId)] : this.tracks;\n const clips: Clip[] = [];\n\n for (const track of tracks) {\n if (!track) continue;\n // Use binary search for single point lookup\n const clip = binarySearchRange(track.clips, timeUs, (entry) => ({\n start: entry.startUs,\n end: entry.startUs + entry.durationUs,\n }));\n\n if (clip) {\n clips.push(clip);\n }\n }\n\n return clips;\n }\n\n getActiveClips(startUs: TimeUs, endUs: TimeUs): Clip[] {\n const clips: Clip[] = [];\n\n for (const track of this.tracks) {\n // Use binary search for range overlap\n const overlappingClips = binarySearchOverlapping(track.clips, startUs, endUs, (clip) => ({\n start: clip.startUs,\n end: clip.startUs + clip.durationUs,\n }));\n\n clips.push(...overlappingClips);\n }\n\n return clips;\n }\n\n getClipIdsByResourceId(resourceId: string): string[] {\n const resource = this.resources.get(resourceId);\n return resource?.clipIds || [];\n }\n\n getClipIdAtTime(trackId: string, timeUs: TimeUs): string | undefined {\n const track = this.findTrack(trackId);\n if (!track) {\n return undefined;\n }\n\n const clip = binarySearchRange(track.clips, timeUs, (entry) => ({\n start: entry.startUs,\n end: entry.startUs + entry.durationUs,\n }));\n\n return clip?.id;\n }\n\n /**\n * Get neighboring clips (Prev/Current/Next) at a specific time for video tracks\n * Returns prev, current, and next clip IDs\n */\n getNeighboringClips(timeUs: TimeUs): { prev?: string; current?: string; next?: string } {\n const videoTracks = this.getTracksByKind('video');\n const result: { prev?: string; current?: string; next?: string } = {};\n\n for (const track of videoTracks) {\n const clips = track.clips;\n\n for (let i = 0; i < clips.length; i++) {\n const clip = clips[i];\n if (!clip) continue;\n\n const clipEndUs = clip.startUs + clip.durationUs;\n\n if (clip.startUs <= timeUs && timeUs < clipEndUs) {\n if (!result.current) {\n result.current = clip.id;\n }\n\n if (i > 0 && !result.prev) {\n const prevClip = clips[i - 1];\n if (prevClip) {\n result.prev = prevClip.id;\n }\n }\n\n if (i < clips.length - 1 && !result.next) {\n const nextClip = clips[i + 1];\n if (nextClip) {\n result.next = nextClip.id;\n }\n }\n }\n }\n }\n\n return result;\n }\n\n /**\n * Get all clip IDs that should be cached using 2-Clip strategy\n * Returns array of unique clip IDs (Prev/Current/Next)\n */\n getClipsToCacheAtTime(timeUs: TimeUs): Set<string> {\n const { current, next } = this.getNeighboringClips(timeUs);\n const clipIds = new Set<string>();\n\n if (current) clipIds.add(current);\n if (next) clipIds.add(next);\n // if (prev) clipIds.push(prev);\n\n return clipIds;\n }\n\n // Resource operations\n getResource(id: string): Resource | null {\n return this.resources.get(id) || null;\n }\n\n updateResourceState(id: string, state: 'pending' | 'loading' | 'ready' | 'error'): void {\n const resource = this.resources.get(id);\n if (resource) {\n resource.state = state;\n }\n }\n\n getUnusedResources(): Resource[] {\n const unused: Resource[] = [];\n\n for (const [id, resource] of this.resources) {\n if (!this.resourceRefCount.has(id) || this.resourceRefCount.get(id) === 0) {\n unused.push(resource);\n }\n }\n\n return unused;\n }\n\n // Time operations\n getDuration(): TimeUs {\n return this.durationUs;\n }\n\n getTrackDuration(trackId: string): TimeUs {\n const track = this.findTrack(trackId);\n if (!track || track.clips.length === 0) return 0;\n\n // Since clips are sorted, last clip determines duration\n const lastClip = track.clips[track.clips.length - 1];\n return (lastClip?.startUs ?? 0) + (lastClip?.durationUs ?? 0);\n }\n\n buildIndexes(options?: {\n incremental?: boolean;\n trackId?: string;\n clipId?: string;\n operation?: 'add' | 'update' | 'remove';\n }): void {\n const track = options?.trackId ? this.findTrack(options.trackId) : undefined;\n const isAttachmentTrack = track && track.kind !== 'video' && track.kind !== 'audio';\n\n // Incremental update for video/audio track clip operations\n if (options?.incremental && !isAttachmentTrack && options.clipId && options.operation) {\n const clip = this.clipMap.get(options.clipId);\n\n if (options.operation === 'add' && clip) {\n this.addClipToIndexes(clip);\n } else if (options.operation === 'remove' && clip) {\n this.removeClipFromIndexes(clip);\n } else if (options.operation === 'update' && clip) {\n // Handle resource change during update\n if (clip.oldResourceId) {\n this.updateClipResourceIndex(\n options.clipId,\n clip.oldResourceId,\n hasResourceId(clip) ? clip.resourceId : undefined\n );\n }\n }\n\n // Recalculate duration only if affected main track\n if (track?.id === this.mainTrackId) {\n this.recalculateDuration();\n }\n return;\n }\n\n // Full rebuild: needed for attachment tracks or initial load\n this.fullRebuildIndexes();\n }\n\n private fullRebuildIndexes(): void {\n // Clear existing indexes\n this.trackMap.clear();\n this.clipMap.clear();\n this.resourceRefCount.clear();\n\n // Step 1: Sink attachment tracks to main track (preserves original tracks)\n this.sinkAttachmentTracks();\n\n let maxEndUs = 0;\n\n // Step 2: Build all indexes in one pass (track, clip, resource)\n for (const track of this.tracks) {\n this.trackMap.set(track.id, track);\n\n for (const clip of track.clips) {\n (clip as Clip).trackId = track.id;\n (clip as Clip).trackKind = track.kind;\n this.clipMap.set(clip.id, clip);\n\n // Main track resource index (only for clips with resourceId)\n if (hasResourceId(clip)) {\n const resource = this.resources.get(clip.resourceId);\n if (resource) {\n resource.clipIds = [...(resource.clipIds || []), clip.id];\n }\n const count = this.resourceRefCount.get(clip.resourceId) || 0;\n this.resourceRefCount.set(clip.resourceId, count + 1);\n }\n\n // Attachment resource indexes (attachments are already sunk)\n const attachments = clip.attachments ?? [];\n for (const attachment of attachments) {\n const attachmentResourceId = attachment.data?.resourceId;\n if (attachmentResourceId && typeof attachmentResourceId === 'string') {\n const attachmentResource = this.resources.get(attachmentResourceId);\n if (attachmentResource) {\n const clipIds = attachmentResource.clipIds || [];\n if (!clipIds.includes(clip.id)) {\n attachmentResource.clipIds = [...clipIds, clip.id];\n }\n }\n const attachmentCount = this.resourceRefCount.get(attachmentResourceId) || 0;\n this.resourceRefCount.set(attachmentResourceId, attachmentCount + 1);\n }\n }\n\n // Calculate max end time\n const clipEndUs = clip.startUs + clip.durationUs;\n if (clipEndUs > maxEndUs) {\n maxEndUs = clipEndUs;\n }\n }\n }\n\n this.durationUs = maxEndUs;\n }\n\n private addClipToIndexes(clip: Clip): void {\n this.clipMap.set(clip.id, clip);\n\n if (hasResourceId(clip)) {\n const resource = this.resources.get(clip.resourceId);\n if (resource) {\n if (!resource.clipIds) {\n resource.clipIds = [];\n }\n if (!resource.clipIds.includes(clip.id)) {\n resource.clipIds.push(clip.id);\n }\n }\n const count = this.resourceRefCount.get(clip.resourceId) || 0;\n this.resourceRefCount.set(clip.resourceId, count + 1);\n }\n }\n\n private removeClipFromIndexes(clip: Clip): void {\n this.clipMap.delete(clip.id);\n\n const resourceId = clip.oldResourceId || (hasResourceId(clip) ? clip.resourceId : undefined);\n if (resourceId) {\n const resource = this.resources.get(resourceId);\n if (resource?.clipIds) {\n resource.clipIds = resource.clipIds.filter((id) => id !== clip.id);\n }\n const count = this.resourceRefCount.get(resourceId) || 0;\n this.resourceRefCount.set(resourceId, Math.max(0, count - 1));\n }\n }\n\n /**\n * Incrementally update resource index when clip's resourceId changes\n */\n updateClipResourceIndex(\n clipId: string,\n oldResourceId: string | undefined,\n newResourceId: string | undefined\n ): void {\n // Remove from old resource\n if (oldResourceId) {\n const oldResource = this.resources.get(oldResourceId);\n if (oldResource?.clipIds) {\n oldResource.clipIds = oldResource.clipIds.filter((id) => id !== clipId);\n }\n const oldCount = this.resourceRefCount.get(oldResourceId) || 0;\n this.resourceRefCount.set(oldResourceId, Math.max(0, oldCount - 1));\n }\n\n // Add to new resource\n if (newResourceId) {\n const newResource = this.resources.get(newResourceId);\n if (newResource) {\n if (!newResource.clipIds) {\n newResource.clipIds = [];\n }\n if (!newResource.clipIds.includes(clipId)) {\n newResource.clipIds.push(clipId);\n }\n }\n const newCount = this.resourceRefCount.get(newResourceId) || 0;\n this.resourceRefCount.set(newResourceId, newCount + 1);\n }\n }\n\n /**\n * Recalculate total duration based on main track\n */\n recalculateDuration(): void {\n const mainTrack = this.findTrack(this.mainTrackId);\n if (!mainTrack || mainTrack.clips.length === 0) {\n this.durationUs = 0;\n return;\n }\n\n // Since clips are sorted, last clip determines duration\n const lastClip = mainTrack.clips[mainTrack.clips.length - 1];\n this.durationUs = (lastClip?.startUs ?? 0) + (lastClip?.durationUs ?? 0);\n }\n\n private sinkAttachmentTracks(): void {\n let mainTrack: Track | undefined;\n const attachmentTracks = [];\n\n // Sort all tracks\n for (const track of this.tracks) {\n track.clips.sort((a, b) => a.startUs - b.startUs);\n\n if (track.id === this.mainTrackId) {\n mainTrack = track;\n continue;\n }\n // Collect attachment tracks for sinking (caption, overlay, fx)\n // Video and audio tracks are not sunk\n if (track.kind === 'caption' || track.kind === 'overlay' || track.kind === 'fx') {\n attachmentTracks.push(track);\n }\n }\n\n if (!mainTrack) {\n throw new Error('Main track not found');\n }\n\n // Clear existing attachments before sinking (in case of rebuild)\n for (const clip of mainTrack.clips) {\n clip.attachments = [];\n }\n\n for (const attachmentTrack of attachmentTracks) {\n for (const attachmentClip of attachmentTrack.clips) {\n // Use track.kind directly as attachment kind\n const attachmentKind = attachmentTrack.kind;\n\n for (const mainClip of mainTrack.clips) {\n const overlap = this.getTimeOverlap(attachmentClip, mainClip);\n if (!overlap) continue;\n\n if (!mainClip.attachments) {\n mainClip.attachments = [];\n }\n\n // Extract animation effect\n const animationEffect = attachmentClip.effects?.find(\n (e) => e.effectType === 'animation'\n ) as AnimationEffect | undefined;\n\n // Extract target dimensions from metadata\n const targetWidth = attachmentClip.metadata?.targetWidth as number | undefined;\n const targetHeight = attachmentClip.metadata?.targetHeight as number | undefined;\n\n const attachmentData: Record<string, unknown> = {\n animation: animationEffect?.params,\n overlayClipStartUs: attachmentClip.startUs,\n mainClipStartUs: mainClip.startUs,\n ...(targetWidth !== undefined && { targetWidth }),\n ...(targetHeight !== undefined && { targetHeight }),\n };\n\n // Add resourceId if exists (trackKind not yet set, check field directly)\n if ('resourceId' in attachmentClip && attachmentClip.resourceId) {\n attachmentData.resourceId = attachmentClip.resourceId;\n }\n\n // Add text if exists (trackKind not yet set, check field directly)\n if ('text' in attachmentClip && attachmentClip.text) {\n attachmentData.text = attachmentClip.text;\n }\n\n mainClip.attachments.push({\n id: `${attachmentKind}-${attachmentClip.id}-${mainClip.id}`,\n kind: attachmentKind,\n startUs: overlap.clipRelativeStart,\n durationUs: overlap.duration,\n data: attachmentData,\n });\n }\n }\n }\n }\n\n private getTimeOverlap(\n attachmentClip: Clip,\n mainClip: Clip\n ): { clipRelativeStart: number; duration: number } | null {\n const attachmentStart = attachmentClip.startUs;\n const attachmentEnd = attachmentClip.startUs + attachmentClip.durationUs;\n const mainStart = mainClip.startUs;\n const mainEnd = mainClip.startUs + mainClip.durationUs;\n\n if (attachmentEnd <= mainStart || attachmentStart >= mainEnd) {\n return null;\n }\n\n const overlapStart = Math.max(attachmentStart, mainStart);\n const overlapEnd = Math.min(attachmentEnd, mainEnd);\n const duration = overlapEnd - overlapStart;\n const clipRelativeStart = overlapStart - mainStart;\n\n return { clipRelativeStart, duration };\n }\n}\n"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"CompositionModel.js","sources":["../../src/model/CompositionModel.ts"],"sourcesContent":["import {\n CompositionModelData,\n Track,\n Clip,\n Resource,\n TimeUs,\n AnimationEffect,\n hasResourceId,\n} from './types';\nimport { binarySearchRange, binarySearchOverlapping } from '../utils/binary-search';\nimport { validateCompositionStructure } from './validation';\n\nexport class CompositionModel {\n public readonly version = '1.0' as const;\n public readonly fps: 24 | 25 | 30 | 60;\n public durationUs!: TimeUs; // Assigned in buildIndexes()\n public readonly mainTrackId: string;\n public tracks: Track[];\n public readonly resources: Map<string, Resource>;\n\n private readonly trackMap: Map<string, Track>;\n private readonly clipMap: Map<string, Clip>;\n private readonly resourceRefCount: Map<string, number>;\n\n public readonly renderConfig?: {\n width: number;\n height: number;\n backgroundColor?: string;\n };\n\n public readonly ext?: Record<string, unknown>;\n\n constructor(data: CompositionModelData) {\n const errors = validateCompositionStructure(data);\n if (errors.length > 0) {\n throw new Error(\n `Validation failed:\\n${errors.map((e) => `${e.path}: ${e.message}`).join('\\n')}`\n );\n }\n\n this.fps = data.fps;\n this.mainTrackId = data.mainTrackId ?? 'main';\n this.tracks = data.tracks;\n this.resources = new Map(Object.entries(data.resources));\n this.renderConfig = data.renderConfig;\n this.ext = data.ext;\n\n // Build indexes\n this.trackMap = new Map();\n this.clipMap = new Map();\n this.resourceRefCount = new Map();\n\n this.buildIndexes();\n\n console.log(\n 'CompositionModel constructed',\n this.tracks.find((t) => t.id === this.mainTrackId)?.clips[0]\n );\n }\n\n // Track operations\n findTrack(id: string): Track | null {\n return this.trackMap.get(id) || null;\n }\n\n getTracksByKind(kind: 'video' | 'audio' | 'caption' | 'overlay' | 'fx'): Track[] {\n return this.tracks.filter((track) => track.kind === kind);\n }\n\n // Clip operations with binary search optimization\n findClip(id: string): Clip | null {\n return this.clipMap.get(id) || null;\n }\n\n getClipsAtTime(timeUs: TimeUs, trackId?: string): Clip[] {\n const tracks = trackId ? [this.findTrack(trackId)] : this.tracks;\n const clips: Clip[] = [];\n\n for (const track of tracks) {\n if (!track) continue;\n // Use binary search for single point lookup\n const clip = binarySearchRange(track.clips, timeUs, (entry) => ({\n start: entry.startUs,\n end: entry.startUs + entry.durationUs,\n }));\n\n if (clip) {\n clips.push(clip);\n }\n }\n\n return clips;\n }\n\n getActiveClips(startUs: TimeUs, endUs: TimeUs): Clip[] {\n const clips: Clip[] = [];\n\n for (const track of this.tracks) {\n // Use binary search for range overlap\n const overlappingClips = binarySearchOverlapping(track.clips, startUs, endUs, (clip) => ({\n start: clip.startUs,\n end: clip.startUs + clip.durationUs,\n }));\n\n clips.push(...overlappingClips);\n }\n\n return clips;\n }\n\n getClipIdsByResourceId(resourceId: string): string[] {\n const resource = this.resources.get(resourceId);\n return resource?.clipIds || [];\n }\n\n getClipIdAtTime(trackId: string, timeUs: TimeUs): string | undefined {\n const track = this.findTrack(trackId);\n if (!track) {\n return undefined;\n }\n\n const clip = binarySearchRange(track.clips, timeUs, (entry) => ({\n start: entry.startUs,\n end: entry.startUs + entry.durationUs,\n }));\n\n return clip?.id;\n }\n\n /**\n * Get neighboring clips (Prev/Current/Next) at a specific time for video tracks\n * Returns prev, current, and next clip IDs\n */\n getNeighboringClips(timeUs: TimeUs): { prev?: string; current?: string; next?: string } {\n const videoTracks = this.getTracksByKind('video');\n const result: { prev?: string; current?: string; next?: string } = {};\n\n for (const track of videoTracks) {\n const clips = track.clips;\n\n for (let i = 0; i < clips.length; i++) {\n const clip = clips[i];\n if (!clip) continue;\n\n const clipEndUs = clip.startUs + clip.durationUs;\n\n if (clip.startUs <= timeUs && timeUs < clipEndUs) {\n if (!result.current) {\n result.current = clip.id;\n }\n\n if (i > 0 && !result.prev) {\n const prevClip = clips[i - 1];\n if (prevClip) {\n result.prev = prevClip.id;\n }\n }\n\n if (i < clips.length - 1 && !result.next) {\n const nextClip = clips[i + 1];\n if (nextClip) {\n result.next = nextClip.id;\n }\n }\n }\n }\n }\n\n return result;\n }\n\n /**\n * Get all clip IDs that should be cached using adaptive strategy\n * - Short clips (≤ maxDuration): cache Current + Next (smooth transitions)\n * - Long clips (> maxDuration): cache Current only (memory control)\n * @param timeUs - Current playback time\n * @param maxDuration - Max duration for 2-clip strategy (default 5s)\n */\n getClipsToCacheAtTime(timeUs: TimeUs, maxDuration = 5_000_000): Set<string> {\n const { current, next } = this.getNeighboringClips(timeUs);\n const clipIds = new Set<string>();\n\n if (!current) return clipIds;\n clipIds.add(current);\n\n // Only cache next clip if current clip is short enough\n const currentClip = this.findClip(current);\n if (currentClip && currentClip.durationUs <= maxDuration && next) {\n clipIds.add(next);\n }\n\n return clipIds;\n }\n\n // Resource operations\n getResource(id: string): Resource | null {\n return this.resources.get(id) || null;\n }\n\n updateResourceState(id: string, state: 'pending' | 'loading' | 'ready' | 'error'): void {\n const resource = this.resources.get(id);\n if (resource) {\n resource.state = state;\n }\n }\n\n getUnusedResources(): Resource[] {\n const unused: Resource[] = [];\n\n for (const [id, resource] of this.resources) {\n if (!this.resourceRefCount.has(id) || this.resourceRefCount.get(id) === 0) {\n unused.push(resource);\n }\n }\n\n return unused;\n }\n\n // Time operations\n getDuration(): TimeUs {\n return this.durationUs;\n }\n\n getTrackDuration(trackId: string): TimeUs {\n const track = this.findTrack(trackId);\n if (!track || track.clips.length === 0) return 0;\n\n // Since clips are sorted, last clip determines duration\n const lastClip = track.clips[track.clips.length - 1];\n return (lastClip?.startUs ?? 0) + (lastClip?.durationUs ?? 0);\n }\n\n buildIndexes(options?: {\n incremental?: boolean;\n trackId?: string;\n clipId?: string;\n operation?: 'add' | 'update' | 'remove';\n }): void {\n const track = options?.trackId ? this.findTrack(options.trackId) : undefined;\n const isAttachmentTrack = track && track.kind !== 'video' && track.kind !== 'audio';\n\n // Incremental update for video/audio track clip operations\n if (options?.incremental && !isAttachmentTrack && options.clipId && options.operation) {\n const clip = this.clipMap.get(options.clipId);\n\n if (options.operation === 'add' && clip) {\n this.addClipToIndexes(clip);\n } else if (options.operation === 'remove' && clip) {\n this.removeClipFromIndexes(clip);\n } else if (options.operation === 'update' && clip) {\n // Handle resource change during update\n if (clip.oldResourceId) {\n this.updateClipResourceIndex(\n options.clipId,\n clip.oldResourceId,\n hasResourceId(clip) ? clip.resourceId : undefined\n );\n }\n }\n\n // Recalculate duration only if affected main track\n if (track?.id === this.mainTrackId) {\n this.recalculateDuration();\n }\n return;\n }\n\n // Full rebuild: needed for attachment tracks or initial load\n this.fullRebuildIndexes();\n }\n\n private fullRebuildIndexes(): void {\n // Clear existing indexes\n this.trackMap.clear();\n this.clipMap.clear();\n this.resourceRefCount.clear();\n\n // Step 1: Sink attachment tracks to main track (preserves original tracks)\n this.sinkAttachmentTracks();\n\n let maxEndUs = 0;\n\n // Step 2: Build all indexes in one pass (track, clip, resource)\n for (const track of this.tracks) {\n this.trackMap.set(track.id, track);\n\n for (const clip of track.clips) {\n (clip as Clip).trackId = track.id;\n (clip as Clip).trackKind = track.kind;\n this.clipMap.set(clip.id, clip);\n\n // Main track resource index (only for clips with resourceId)\n if (hasResourceId(clip)) {\n const resource = this.resources.get(clip.resourceId);\n if (resource) {\n resource.clipIds = [...(resource.clipIds || []), clip.id];\n }\n const count = this.resourceRefCount.get(clip.resourceId) || 0;\n this.resourceRefCount.set(clip.resourceId, count + 1);\n }\n\n // Attachment resource indexes (attachments are already sunk)\n const attachments = clip.attachments ?? [];\n for (const attachment of attachments) {\n const attachmentResourceId = attachment.data?.resourceId;\n if (attachmentResourceId && typeof attachmentResourceId === 'string') {\n const attachmentResource = this.resources.get(attachmentResourceId);\n if (attachmentResource) {\n const clipIds = attachmentResource.clipIds || [];\n if (!clipIds.includes(clip.id)) {\n attachmentResource.clipIds = [...clipIds, clip.id];\n }\n }\n const attachmentCount = this.resourceRefCount.get(attachmentResourceId) || 0;\n this.resourceRefCount.set(attachmentResourceId, attachmentCount + 1);\n }\n }\n\n // Calculate max end time\n const clipEndUs = clip.startUs + clip.durationUs;\n if (clipEndUs > maxEndUs) {\n maxEndUs = clipEndUs;\n }\n }\n }\n\n this.durationUs = maxEndUs;\n }\n\n private addClipToIndexes(clip: Clip): void {\n this.clipMap.set(clip.id, clip);\n\n if (hasResourceId(clip)) {\n const resource = this.resources.get(clip.resourceId);\n if (resource) {\n if (!resource.clipIds) {\n resource.clipIds = [];\n }\n if (!resource.clipIds.includes(clip.id)) {\n resource.clipIds.push(clip.id);\n }\n }\n const count = this.resourceRefCount.get(clip.resourceId) || 0;\n this.resourceRefCount.set(clip.resourceId, count + 1);\n }\n }\n\n private removeClipFromIndexes(clip: Clip): void {\n this.clipMap.delete(clip.id);\n\n const resourceId = clip.oldResourceId || (hasResourceId(clip) ? clip.resourceId : undefined);\n if (resourceId) {\n const resource = this.resources.get(resourceId);\n if (resource?.clipIds) {\n resource.clipIds = resource.clipIds.filter((id) => id !== clip.id);\n }\n const count = this.resourceRefCount.get(resourceId) || 0;\n this.resourceRefCount.set(resourceId, Math.max(0, count - 1));\n }\n }\n\n /**\n * Incrementally update resource index when clip's resourceId changes\n */\n updateClipResourceIndex(\n clipId: string,\n oldResourceId: string | undefined,\n newResourceId: string | undefined\n ): void {\n // Remove from old resource\n if (oldResourceId) {\n const oldResource = this.resources.get(oldResourceId);\n if (oldResource?.clipIds) {\n oldResource.clipIds = oldResource.clipIds.filter((id) => id !== clipId);\n }\n const oldCount = this.resourceRefCount.get(oldResourceId) || 0;\n this.resourceRefCount.set(oldResourceId, Math.max(0, oldCount - 1));\n }\n\n // Add to new resource\n if (newResourceId) {\n const newResource = this.resources.get(newResourceId);\n if (newResource) {\n if (!newResource.clipIds) {\n newResource.clipIds = [];\n }\n if (!newResource.clipIds.includes(clipId)) {\n newResource.clipIds.push(clipId);\n }\n }\n const newCount = this.resourceRefCount.get(newResourceId) || 0;\n this.resourceRefCount.set(newResourceId, newCount + 1);\n }\n }\n\n /**\n * Recalculate total duration based on main track\n */\n recalculateDuration(): void {\n const mainTrack = this.findTrack(this.mainTrackId);\n if (!mainTrack || mainTrack.clips.length === 0) {\n this.durationUs = 0;\n return;\n }\n\n // Since clips are sorted, last clip determines duration\n const lastClip = mainTrack.clips[mainTrack.clips.length - 1];\n this.durationUs = (lastClip?.startUs ?? 0) + (lastClip?.durationUs ?? 0);\n }\n\n private sinkAttachmentTracks(): void {\n let mainTrack: Track | undefined;\n const attachmentTracks = [];\n\n // Sort all tracks\n for (const track of this.tracks) {\n track.clips.sort((a, b) => a.startUs - b.startUs);\n\n if (track.id === this.mainTrackId) {\n mainTrack = track;\n continue;\n }\n // Collect attachment tracks for sinking (caption, overlay, fx)\n // Video and audio tracks are not sunk\n if (track.kind === 'caption' || track.kind === 'overlay' || track.kind === 'fx') {\n attachmentTracks.push(track);\n }\n }\n\n if (!mainTrack) {\n throw new Error('Main track not found');\n }\n\n // Clear existing attachments before sinking (in case of rebuild)\n for (const clip of mainTrack.clips) {\n clip.attachments = [];\n }\n\n for (const attachmentTrack of attachmentTracks) {\n for (const attachmentClip of attachmentTrack.clips) {\n // Use track.kind directly as attachment kind\n const attachmentKind = attachmentTrack.kind;\n\n for (const mainClip of mainTrack.clips) {\n const overlap = this.getTimeOverlap(attachmentClip, mainClip);\n if (!overlap) continue;\n\n if (!mainClip.attachments) {\n mainClip.attachments = [];\n }\n\n // Extract animation effect\n const animationEffect = attachmentClip.effects?.find(\n (e) => e.effectType === 'animation'\n ) as AnimationEffect | undefined;\n\n // Extract target dimensions from metadata\n const targetWidth = attachmentClip.metadata?.targetWidth as number | undefined;\n const targetHeight = attachmentClip.metadata?.targetHeight as number | undefined;\n\n const attachmentData: Record<string, unknown> = {\n animation: animationEffect?.params,\n overlayClipStartUs: attachmentClip.startUs,\n mainClipStartUs: mainClip.startUs,\n ...(targetWidth !== undefined && { targetWidth }),\n ...(targetHeight !== undefined && { targetHeight }),\n };\n\n // Add resourceId if exists (trackKind not yet set, check field directly)\n if ('resourceId' in attachmentClip && attachmentClip.resourceId) {\n attachmentData.resourceId = attachmentClip.resourceId;\n }\n\n // Add text if exists (trackKind not yet set, check field directly)\n if ('text' in attachmentClip && attachmentClip.text) {\n attachmentData.text = attachmentClip.text;\n }\n\n mainClip.attachments.push({\n id: `${attachmentKind}-${attachmentClip.id}-${mainClip.id}`,\n kind: attachmentKind,\n startUs: overlap.clipRelativeStart,\n durationUs: overlap.duration,\n data: attachmentData,\n });\n }\n }\n }\n }\n\n private getTimeOverlap(\n attachmentClip: Clip,\n mainClip: Clip\n ): { clipRelativeStart: number; duration: number } | null {\n const attachmentStart = attachmentClip.startUs;\n const attachmentEnd = attachmentClip.startUs + attachmentClip.durationUs;\n const mainStart = mainClip.startUs;\n const mainEnd = mainClip.startUs + mainClip.durationUs;\n\n if (attachmentEnd <= mainStart || attachmentStart >= mainEnd) {\n return null;\n }\n\n const overlapStart = Math.max(attachmentStart, mainStart);\n const overlapEnd = Math.min(attachmentEnd, mainEnd);\n const duration = overlapEnd - overlapStart;\n const clipRelativeStart = overlapStart - mainStart;\n\n return { clipRelativeStart, duration };\n }\n}\n"],"names":[],"mappings":"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"} |
@@ -9,4 +9,2 @@ import { TimeUs } from './types'; | ||
| sizeEstimate?: number; | ||
| gopSerial?: number; | ||
| isKeyframe?: boolean; | ||
| } | ||
@@ -29,4 +27,2 @@ type UseHandler<T> = (frame: VideoFrame) => T | Promise<T>; | ||
| get sizeEstimate(): number; | ||
| get gopSerial(): number | undefined; | ||
| get isKeyframe(): boolean | undefined; | ||
| use<T>(handler: UseHandler<T>): Promise<T>; | ||
@@ -33,0 +29,0 @@ close(): Promise<void>; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"RcFrame.d.ts","sourceRoot":"","sources":["../../src/model/RcFrame.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEtC,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAID,KAAK,UAAU,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,UAAU,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;AAE3D,qBAAa,OAAO;IAClB,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAa;IACvC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkB;IAC3C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAa;IAErC,OAAO,CAAC,YAAY,CAA8B;IAClD,OAAO,CAAC,YAAY,CAA6B;IAEjD,OAAO;IAMP,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO;IAIvF,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,IAAI,YAAY,IAAI,MAAM,CAUzB;IAED,IAAI,SAAS,IAAI,MAAM,GAAG,SAAS,CAElC;IAED,IAAI,UAAU,IAAI,OAAO,GAAG,SAAS,CAEpC;IAEK,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAmB1C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAkC7B"} | ||
| {"version":3,"file":"RcFrame.d.ts","sourceRoot":"","sources":["../../src/model/RcFrame.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEtC,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAID,KAAK,UAAU,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,UAAU,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;AAE3D,qBAAa,OAAO;IAClB,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAa;IACvC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkB;IAC3C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAa;IAErC,OAAO,CAAC,YAAY,CAA8B;IAClD,OAAO,CAAC,YAAY,CAA6B;IAEjD,OAAO;IAMP,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO;IAIvF,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,IAAI,YAAY,IAAI,MAAM,CAUzB;IAEK,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAmB1C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAkC7B"} |
@@ -42,8 +42,2 @@ const DEFAULT_SIZE_ESTIMATE = 1920 * 1080 * 4; | ||
| } | ||
| get gopSerial() { | ||
| return this.metadata.gopSerial; | ||
| } | ||
| get isKeyframe() { | ||
| return this.metadata.isKeyframe; | ||
| } | ||
| async use(handler) { | ||
@@ -50,0 +44,0 @@ if (this.closing) { |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"RcFrame.js","sources":["../../src/model/RcFrame.ts"],"sourcesContent":["import type { TimeUs } from './types';\n\nexport interface RcFrameMetadata {\n trackId: string;\n clipId: string;\n timestampUs: TimeUs;\n durationUs: TimeUs;\n sizeEstimate?: number;\n gopSerial?: number;\n isKeyframe?: boolean;\n}\n\nconst DEFAULT_SIZE_ESTIMATE = 1920 * 1080 * 4;\nconst CLOSE_TIMEOUT_MS = 3_000;\ntype UseHandler<T> = (frame: VideoFrame) => T | Promise<T>;\n\nexport class RcFrame {\n private activeUses = 0;\n private closing = false;\n private readonly baseFrame: VideoFrame;\n private readonly metadata: RcFrameMetadata;\n private readonly onZero?: () => void;\n\n private closePromise: Promise<void> | null = null;\n private closeResolve: (() => void) | null = null;\n\n private constructor(frame: VideoFrame, metadata: RcFrameMetadata, onZero?: () => void) {\n this.baseFrame = frame;\n this.metadata = metadata;\n this.onZero = onZero;\n }\n\n static wrap(frame: VideoFrame, metadata: RcFrameMetadata, onZero?: () => void): RcFrame {\n return new RcFrame(frame, metadata, onZero);\n }\n\n get timestampUs(): TimeUs {\n return this.metadata.timestampUs;\n }\n\n get durationUs(): TimeUs {\n return this.metadata.durationUs;\n }\n\n get trackId(): string {\n return this.metadata.trackId;\n }\n\n get clipId(): string {\n return this.metadata.clipId;\n }\n\n get sizeEstimate(): number {\n if (typeof this.metadata.sizeEstimate === 'number') {\n return this.metadata.sizeEstimate;\n }\n const width = (this.baseFrame as any).codedWidth || (this.baseFrame as any).displayWidth;\n const height = (this.baseFrame as any).codedHeight || (this.baseFrame as any).displayHeight;\n if (width && height) {\n return width * height * 4;\n }\n return DEFAULT_SIZE_ESTIMATE;\n }\n\n get gopSerial(): number | undefined {\n return this.metadata.gopSerial;\n }\n\n get isKeyframe(): boolean | undefined {\n return this.metadata.isKeyframe;\n }\n\n async use<T>(handler: UseHandler<T>): Promise<T> {\n if (this.closing) {\n throw new Error('RcFrame is closing, cannot use frame');\n }\n\n const cloned = this.baseFrame.clone();\n this.activeUses += 1;\n\n try {\n return await handler(cloned);\n } finally {\n cloned.close();\n this.activeUses -= 1;\n if (this.activeUses === 0 && this.closing && this.closeResolve) {\n this.closeResolve();\n }\n }\n }\n\n async close(): Promise<void> {\n if (this.closing) {\n return this.closePromise ?? Promise.resolve();\n }\n\n this.closing = true;\n\n if (this.activeUses === 0) {\n this.baseFrame.close();\n this.onZero?.();\n return Promise.resolve();\n }\n\n if (!this.closePromise) {\n this.closePromise = new Promise<void>((resolve) => {\n this.closeResolve = () => {\n this.baseFrame.close();\n this.onZero?.();\n resolve();\n };\n });\n\n setTimeout(() => {\n if (this.closeResolve) {\n this.baseFrame.close();\n this.onZero?.();\n this.closeResolve();\n this.closeResolve = null;\n }\n }, CLOSE_TIMEOUT_MS).unref?.();\n }\n\n return this.closePromise;\n }\n}\n"],"names":[],"mappings":"AAYA,MAAM,wBAAwB,OAAO,OAAO;AAC5C,MAAM,mBAAmB;AAGlB,MAAM,QAAQ;AAAA,EACX,aAAa;AAAA,EACb,UAAU;AAAA,EACD;AAAA,EACA;AAAA,EACA;AAAA,EAET,eAAqC;AAAA,EACrC,eAAoC;AAAA,EAEpC,YAAY,OAAmB,UAA2B,QAAqB;AACrF,SAAK,YAAY;AACjB,SAAK,WAAW;AAChB,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,OAAO,KAAK,OAAmB,UAA2B,QAA8B;AACtF,WAAO,IAAI,QAAQ,OAAO,UAAU,MAAM;AAAA,EAC5C;AAAA,EAEA,IAAI,cAAsB;AACxB,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA,EAEA,IAAI,aAAqB;AACvB,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA,EAEA,IAAI,UAAkB;AACpB,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA,EAEA,IAAI,SAAiB;AACnB,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA,EAEA,IAAI,eAAuB;AACzB,QAAI,OAAO,KAAK,SAAS,iBAAiB,UAAU;AAClD,aAAO,KAAK,SAAS;AAAA,IACvB;AACA,UAAM,QAAS,KAAK,UAAkB,cAAe,KAAK,UAAkB;AAC5E,UAAM,SAAU,KAAK,UAAkB,eAAgB,KAAK,UAAkB;AAC9E,QAAI,SAAS,QAAQ;AACnB,aAAO,QAAQ,SAAS;AAAA,IAC1B;AACA,WAAO;AAAA,EACT;AAAA,EAEA,IAAI,YAAgC;AAClC,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA,EAEA,IAAI,aAAkC;AACpC,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA,EAEA,MAAM,IAAO,SAAoC;AAC/C,QAAI,KAAK,SAAS;AAChB,YAAM,IAAI,MAAM,sCAAsC;AAAA,IACxD;AAEA,UAAM,SAAS,KAAK,UAAU,MAAA;AAC9B,SAAK,cAAc;AAEnB,QAAI;AACF,aAAO,MAAM,QAAQ,MAAM;AAAA,IAC7B,UAAA;AACE,aAAO,MAAA;AACP,WAAK,cAAc;AACnB,UAAI,KAAK,eAAe,KAAK,KAAK,WAAW,KAAK,cAAc;AAC9D,aAAK,aAAA;AAAA,MACP;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,QAAuB;AAC3B,QAAI,KAAK,SAAS;AAChB,aAAO,KAAK,gBAAgB,QAAQ,QAAA;AAAA,IACtC;AAEA,SAAK,UAAU;AAEf,QAAI,KAAK,eAAe,GAAG;AACzB,WAAK,UAAU,MAAA;AACf,WAAK,SAAA;AACL,aAAO,QAAQ,QAAA;AAAA,IACjB;AAEA,QAAI,CAAC,KAAK,cAAc;AACtB,WAAK,eAAe,IAAI,QAAc,CAAC,YAAY;AACjD,aAAK,eAAe,MAAM;AACxB,eAAK,UAAU,MAAA;AACf,eAAK,SAAA;AACL,kBAAA;AAAA,QACF;AAAA,MACF,CAAC;AAED,iBAAW,MAAM;AACf,YAAI,KAAK,cAAc;AACrB,eAAK,UAAU,MAAA;AACf,eAAK,SAAA;AACL,eAAK,aAAA;AACL,eAAK,eAAe;AAAA,QACtB;AAAA,MACF,GAAG,gBAAgB,EAAE,QAAA;AAAA,IACvB;AAEA,WAAO,KAAK;AAAA,EACd;AACF;"} | ||
| {"version":3,"file":"RcFrame.js","sources":["../../src/model/RcFrame.ts"],"sourcesContent":["import type { TimeUs } from './types';\n\nexport interface RcFrameMetadata {\n trackId: string;\n clipId: string;\n timestampUs: TimeUs;\n durationUs: TimeUs;\n sizeEstimate?: number;\n}\n\nconst DEFAULT_SIZE_ESTIMATE = 1920 * 1080 * 4;\nconst CLOSE_TIMEOUT_MS = 3_000;\ntype UseHandler<T> = (frame: VideoFrame) => T | Promise<T>;\n\nexport class RcFrame {\n private activeUses = 0;\n private closing = false;\n private readonly baseFrame: VideoFrame;\n private readonly metadata: RcFrameMetadata;\n private readonly onZero?: () => void;\n\n private closePromise: Promise<void> | null = null;\n private closeResolve: (() => void) | null = null;\n\n private constructor(frame: VideoFrame, metadata: RcFrameMetadata, onZero?: () => void) {\n this.baseFrame = frame;\n this.metadata = metadata;\n this.onZero = onZero;\n }\n\n static wrap(frame: VideoFrame, metadata: RcFrameMetadata, onZero?: () => void): RcFrame {\n return new RcFrame(frame, metadata, onZero);\n }\n\n get timestampUs(): TimeUs {\n return this.metadata.timestampUs;\n }\n\n get durationUs(): TimeUs {\n return this.metadata.durationUs;\n }\n\n get trackId(): string {\n return this.metadata.trackId;\n }\n\n get clipId(): string {\n return this.metadata.clipId;\n }\n\n get sizeEstimate(): number {\n if (typeof this.metadata.sizeEstimate === 'number') {\n return this.metadata.sizeEstimate;\n }\n const width = (this.baseFrame as any).codedWidth || (this.baseFrame as any).displayWidth;\n const height = (this.baseFrame as any).codedHeight || (this.baseFrame as any).displayHeight;\n if (width && height) {\n return width * height * 4;\n }\n return DEFAULT_SIZE_ESTIMATE;\n }\n\n async use<T>(handler: UseHandler<T>): Promise<T> {\n if (this.closing) {\n throw new Error('RcFrame is closing, cannot use frame');\n }\n\n const cloned = this.baseFrame.clone();\n this.activeUses += 1;\n\n try {\n return await handler(cloned);\n } finally {\n cloned.close();\n this.activeUses -= 1;\n if (this.activeUses === 0 && this.closing && this.closeResolve) {\n this.closeResolve();\n }\n }\n }\n\n async close(): Promise<void> {\n if (this.closing) {\n return this.closePromise ?? Promise.resolve();\n }\n\n this.closing = true;\n\n if (this.activeUses === 0) {\n this.baseFrame.close();\n this.onZero?.();\n return Promise.resolve();\n }\n\n if (!this.closePromise) {\n this.closePromise = new Promise<void>((resolve) => {\n this.closeResolve = () => {\n this.baseFrame.close();\n this.onZero?.();\n resolve();\n };\n });\n\n setTimeout(() => {\n if (this.closeResolve) {\n this.baseFrame.close();\n this.onZero?.();\n this.closeResolve();\n this.closeResolve = null;\n }\n }, CLOSE_TIMEOUT_MS).unref?.();\n }\n\n return this.closePromise;\n }\n}\n"],"names":[],"mappings":"AAUA,MAAM,wBAAwB,OAAO,OAAO;AAC5C,MAAM,mBAAmB;AAGlB,MAAM,QAAQ;AAAA,EACX,aAAa;AAAA,EACb,UAAU;AAAA,EACD;AAAA,EACA;AAAA,EACA;AAAA,EAET,eAAqC;AAAA,EACrC,eAAoC;AAAA,EAEpC,YAAY,OAAmB,UAA2B,QAAqB;AACrF,SAAK,YAAY;AACjB,SAAK,WAAW;AAChB,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,OAAO,KAAK,OAAmB,UAA2B,QAA8B;AACtF,WAAO,IAAI,QAAQ,OAAO,UAAU,MAAM;AAAA,EAC5C;AAAA,EAEA,IAAI,cAAsB;AACxB,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA,EAEA,IAAI,aAAqB;AACvB,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA,EAEA,IAAI,UAAkB;AACpB,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA,EAEA,IAAI,SAAiB;AACnB,WAAO,KAAK,SAAS;AAAA,EACvB;AAAA,EAEA,IAAI,eAAuB;AACzB,QAAI,OAAO,KAAK,SAAS,iBAAiB,UAAU;AAClD,aAAO,KAAK,SAAS;AAAA,IACvB;AACA,UAAM,QAAS,KAAK,UAAkB,cAAe,KAAK,UAAkB;AAC5E,UAAM,SAAU,KAAK,UAAkB,eAAgB,KAAK,UAAkB;AAC9E,QAAI,SAAS,QAAQ;AACnB,aAAO,QAAQ,SAAS;AAAA,IAC1B;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,IAAO,SAAoC;AAC/C,QAAI,KAAK,SAAS;AAChB,YAAM,IAAI,MAAM,sCAAsC;AAAA,IACxD;AAEA,UAAM,SAAS,KAAK,UAAU,MAAA;AAC9B,SAAK,cAAc;AAEnB,QAAI;AACF,aAAO,MAAM,QAAQ,MAAM;AAAA,IAC7B,UAAA;AACE,aAAO,MAAA;AACP,WAAK,cAAc;AACnB,UAAI,KAAK,eAAe,KAAK,KAAK,WAAW,KAAK,cAAc;AAC9D,aAAK,aAAA;AAAA,MACP;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,QAAuB;AAC3B,QAAI,KAAK,SAAS;AAChB,aAAO,KAAK,gBAAgB,QAAQ,QAAA;AAAA,IACtC;AAEA,SAAK,UAAU;AAEf,QAAI,KAAK,eAAe,GAAG;AACzB,WAAK,UAAU,MAAA;AACf,WAAK,SAAA;AACL,aAAO,QAAQ,QAAA;AAAA,IACjB;AAEA,QAAI,CAAC,KAAK,cAAc;AACtB,WAAK,eAAe,IAAI,QAAc,CAAC,YAAY;AACjD,aAAK,eAAe,MAAM;AACxB,eAAK,UAAU,MAAA;AACf,eAAK,SAAA;AACL,kBAAA;AAAA,QACF;AAAA,MACF,CAAC;AAED,iBAAW,MAAM;AACf,YAAI,KAAK,cAAc;AACrB,eAAK,UAAU,MAAA;AACf,eAAK,SAAA;AACL,eAAK,aAAA;AACL,eAAK,eAAe;AAAA,QACtB;AAAA,MACF,GAAG,gBAAgB,EAAE,QAAA;AAAA,IACvB;AAEA,WAAO,KAAK;AAAA,EACd;AACF;"} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"Orchestrator.d.ts","sourceRoot":"","sources":["../../src/orchestrator/Orchestrator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAElD,OAAO,EAAyB,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACtF,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAErD,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AACrF,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAY,MAAM,EAAE,OAAO,EAAQ,MAAM,UAAU,CAAC;AAE/F,OAAO,EAAgB,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAG1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAIxC,qBAAa,YAAa,YAAW,aAAa;IAChD,OAAO,EAAE,UAAU,CAAC;IACpB,QAAQ,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC;IACpC,gBAAgB,EAAE,gBAAgB,GAAG,IAAI,CAAQ;IACjD,cAAc,EAAE,cAAc,CAAC;IAC/B,YAAY,EAAE,YAAY,CAAC;IAC3B,OAAO,EAAE,kBAAkB,CAAC;IAC5B,YAAY,EAAE,kBAAkB,CAAC;IACjC,UAAU,EAAE,UAAU,CAAC;IAEvB,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,MAAM,CAA0C;IACxD,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,wBAAwB,CAAuB;IACvD,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAO;IAChD,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,IAAI,GAAG,KAAK,GAAG,MAAM,CAAC,CAAC;gBAE5D,MAAM,EAAE,kBAAkB;IAqEtC,IAAI,YAAY,IAAI,YAAY,CAsB/B;IAEK,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IASjC,EAAE,CAAC,CAAC,SAAS,MAAM,eAAe,EAChC,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC,KAAK,IAAI,GAC7C,IAAI;IAIP,GAAG,CAAC,CAAC,SAAS,MAAM,eAAe,EACjC,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC,KAAK,IAAI,GAC7C,IAAI;IAIP,IAAI,CAAC,CAAC,SAAS,MAAM,eAAe,EAClC,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC,KAAK,IAAI,GAC7C,IAAI;IAID,mBAAmB,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB3D,UAAU,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAoDxD,OAAO,CAAC,yBAAyB;IAiD3B,aAAa,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA+B/D,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;YA4D1E,iBAAiB;YAiBjB,YAAY;IA0C1B;;;;;OAKG;IACG,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,UAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IA2BvE;;;OAGG;IACG,gBAAgB,CACpB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GACvD,OAAO,CAAC,OAAO,CAAC;IA0Bb,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;YAoCzC,aAAa;IAyGrB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB9B,OAAO,CAAC,kBAAkB;IA2CpB,MAAM,CAAC,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;CAG7E"} | ||
| {"version":3,"file":"Orchestrator.d.ts","sourceRoot":"","sources":["../../src/orchestrator/Orchestrator.ts"],"names":[],"mappings":"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"} |
@@ -202,5 +202,5 @@ import { EventBus } from "../event/EventBus.js"; | ||
| const session = this.clipSessionManager.getSession(clipId); | ||
| const visualWorker = session?.visualWorkerHandle; | ||
| if (visualWorker) { | ||
| visualWorker.send("install_instructions", instructions); | ||
| const composeWorker = session?.composeWorker; | ||
| if (composeWorker) { | ||
| composeWorker.send("install_instructions", instructions); | ||
| } | ||
@@ -422,39 +422,28 @@ } | ||
| callbacks: { | ||
| onComposedStreamReady: (stream, fps) => { | ||
| if (options?.forL2Only) { | ||
| stream.cancel(); | ||
| } else { | ||
| this.cacheManager.receiveComposedFrames(stream, { | ||
| clipId, | ||
| trackId: this.compositionModel.mainTrackId, | ||
| fps, | ||
| clipStartUs: clip.startUs, | ||
| onFrame: () => { | ||
| } | ||
| }); | ||
| } | ||
| onComposedStreamReady: async (stream, fps) => { | ||
| await this.cacheManager.receiveComposedFrames(stream, { | ||
| clipId, | ||
| trackId: this.compositionModel.mainTrackId, | ||
| fps, | ||
| clipStartUs: clip.startUs, | ||
| onFrame: () => { | ||
| } | ||
| }); | ||
| }, | ||
| onEncodedStreamReady: (stream, track) => { | ||
| if (options?.forL2Only) { | ||
| this.cacheManager.receiveEncodedChunks(stream, clipId, track, { | ||
| onComplete: () => { | ||
| session.dispose(); | ||
| if (track === "video" && options.onL2Complete) { | ||
| options.onL2Complete(); | ||
| } | ||
| }, | ||
| onError: (error) => { | ||
| console.error( | ||
| `[Orchestrator] L2 encode stream error for ${clipId} ${track}:`, | ||
| error | ||
| ); | ||
| session.dispose(); | ||
| if (options.onL2Error) { | ||
| options.onL2Error(error); | ||
| } | ||
| onEncodedStreamReady: async (stream, track) => { | ||
| await this.cacheManager.receiveEncodedChunks(stream, clipId, track, { | ||
| onComplete: () => { | ||
| session.dispose(); | ||
| if (track === "video" && options?.onL2Complete) { | ||
| options.onL2Complete(); | ||
| } | ||
| }); | ||
| } else { | ||
| stream.cancel(); | ||
| } | ||
| }, | ||
| onError: (error) => { | ||
| console.error(`[Orchestrator] L2 encode stream error for ${clipId} ${track}:`, error); | ||
| session.dispose(); | ||
| if (options?.onL2Error) { | ||
| options.onL2Error(error); | ||
| } | ||
| } | ||
| }); | ||
| }, | ||
@@ -461,0 +450,0 @@ onAudioStreamReady: (stream, metadata) => { |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"Orchestrator.js","sources":["../../src/orchestrator/Orchestrator.ts"],"sourcesContent":["import { EventBus } from '../event/EventBus';\nimport { WorkerPool } from '../worker/WorkerPool';\nimport { applyPatch as applyModelPatch } from '../model/patch';\nimport { ResourceConflictError, ResourceLoader } from '../stages/load/ResourceLoader';\nimport { CacheManager } from '../cache/CacheManager';\nimport { ConfigLoader } from '../config/ConfigLoader';\nimport type { IOrchestrator, OrchestratorConfig, RenderFrameOptions } from './types';\nimport { WorkerStatus, WorkerType } from '../worker/types';\nimport { CompositionModel, CompositionPatch, Resource, TimeUs, RcFrame, Clip } from '../model';\nimport { hasResourceId } from '../model/types';\nimport { MeframeEvent, type EventPayloadMap } from '../event/events';\nimport { CompositionPlanner } from './CompositionPlanner';\nimport { VideoClipSession } from './VideoClipSession';\nimport { ClipSessionManager } from './ClipSessionManager';\nimport { GlobalAudioSession } from './GlobalAudioSession';\nimport { MuxManager } from '../stages/mux/MuxManager';\nimport { ExportOptions } from '@/types';\nimport { VideoChunkDecoder } from '../stages/decode/VideoChunkDecoder';\nimport { quantizeTimestampToFrame } from '../utils/time-utils';\n\nexport class Orchestrator implements IOrchestrator {\n workers: WorkerPool;\n eventBus: EventBus<EventPayloadMap>;\n compositionModel: CompositionModel | null = null;\n resourceLoader: ResourceLoader;\n cacheManager: CacheManager;\n planner: CompositionPlanner;\n audioSession: GlobalAudioSession;\n muxManager: MuxManager;\n\n private activeClips = new Set<string>();\n private isInitialized = false;\n private config = ConfigLoader.getInstance().getConfig();\n private clipSessionManager: ClipSessionManager;\n private currentClipId: string | null = null;\n private ensureCacheDebounceTimer: number | null = null;\n private readonly ensureCacheDebounceDelay = 150;\n readonly events: Pick<EventBus<EventPayloadMap>, 'on' | 'off' | 'once'>;\n\n constructor(config: OrchestratorConfig) {\n // Use provided eventBus or create a new one\n this.eventBus = config.eventBus || new EventBus<EventPayloadMap>();\n this.events = this.eventBus.asReadonly();\n\n // Initialize config first\n this.config = ConfigLoader.getInstance().getConfig();\n\n const workerConfigs = this.buildWorkerConfigs();\n\n // Initialize WorkerPool with worker path from config\n this.workers = new WorkerPool({\n eventBus: this.eventBus,\n workerConfigs,\n workerPath: config.workerPath,\n workerExtension: config.workerExtension,\n });\n\n this.resourceLoader = new ResourceLoader({\n orchestrator: this as any,\n eventBus: this.eventBus,\n config: {\n maxConcurrent: config.maxWorkers || (this.config.load as any)?.retry?.maxAttempts || 4,\n },\n onStateChange: (resourceId, state) => this.handleResourceStateChange(resourceId, state),\n });\n\n this.planner = new CompositionPlanner();\n\n const cacheConfig = config.cacheConfig || this.config.cache;\n this.cacheManager = new CacheManager(\n {\n l1: {\n maxMemoryMB:\n (cacheConfig as any)?.l1Size || (cacheConfig as any)?.l1?.maxMemoryMB || 1024,\n maxGOPs: (this.config.decode as any)?.video?.maxGOPs || 4,\n },\n l2: {\n maxSizeMB: (cacheConfig as any)?.l2Size || (cacheConfig as any)?.l2?.maxSizeMB || 2048,\n projectId: config.projectId || this.config.global.projectId || 'default',\n },\n },\n this.eventBus\n );\n\n this.clipSessionManager = new ClipSessionManager({\n maxConcurrent: 2,\n factory: {\n createSession: (clipId) => this.createSession(clipId),\n },\n cacheManager: this.cacheManager,\n });\n\n this.audioSession = new GlobalAudioSession({\n cacheManager: this.cacheManager,\n workers: this.workers,\n resourceLoader: this.resourceLoader,\n eventBus: this.eventBus,\n getModel: () => this.compositionModel,\n buildWorkerConfigs: () => this.buildWorkerConfigs(),\n });\n\n this.muxManager = new MuxManager(\n this.cacheManager,\n this.audioSession,\n this.config.encode.audio as AudioEncoderConfig\n );\n }\n\n get workerStatus(): WorkerStatus {\n const status = this.workers.status;\n const result: WorkerStatus = {} as WorkerStatus;\n\n const workerTypes: WorkerType[] = [\n 'videoDemux',\n 'audioDemux',\n 'videoDecode',\n 'audioDecode',\n 'videoCompose',\n 'audioCompose',\n 'videoEncode',\n ];\n\n for (const type of workerTypes) {\n result[type] = status[type] || {\n state: 'idle',\n taskCount: 0,\n };\n }\n\n return result;\n }\n\n async initialize(): Promise<void> {\n if (this.isInitialized) return;\n\n await this.cacheManager.init();\n\n this.isInitialized = true;\n }\n\n // Event methods - forward to eventBus\n on<K extends keyof EventPayloadMap>(\n event: K,\n handler: (payload: EventPayloadMap[K]) => void\n ): void {\n this.eventBus.on(event, handler);\n }\n\n off<K extends keyof EventPayloadMap>(\n event: K,\n handler: (payload: EventPayloadMap[K]) => void\n ): void {\n this.eventBus.off(event, handler);\n }\n\n once<K extends keyof EventPayloadMap>(\n event: K,\n handler: (payload: EventPayloadMap[K]) => void\n ): void {\n this.eventBus.once(event, handler);\n }\n\n async setCompositionModel(model: CompositionModel): Promise<void> {\n this.compositionModel = model;\n this.planner.setModel(model);\n this.currentClipId = null;\n\n this.eventBus.emit(MeframeEvent.ModelSet, model);\n\n this.eventBus.emit(MeframeEvent.CompositionUpdated, {\n trackCount: model.tracks.length,\n clipCount: model.tracks.reduce((acc: number, track: any) => acc + track.clips.length, 0),\n durationUs: model.durationUs,\n });\n\n await this.audioSession.activateAllAudioClips();\n }\n\n async applyPatch(patch: CompositionPatch): Promise<void> {\n if (!this.compositionModel) {\n throw new Error('No composition model set');\n }\n\n // Apply patch and get affected clip IDs (simplified for 2-Clip strategy)\n // Note: addTrack/removeTrack already call buildIndexes() in patch.ts\n const affectedClipIds = applyModelPatch(this.compositionModel, patch);\n const clipUpdates = this.planner.applyPatch(patch, affectedClipIds);\n this.eventBus.emit(MeframeEvent.PatchApplied, {\n operations: patch.operations.length,\n affectedClips: Array.from(affectedClipIds),\n });\n\n // Process clip updates\n for (const update of clipUpdates) {\n if (update.type === 'remove') {\n this.activeClips.delete(update.clipId);\n }\n\n this.cacheManager.invalidateClip(update.clipId);\n await this.clipSessionManager.handlePlannerUpdate(update.clipId, update);\n }\n\n // Reactivate updated audio clips\n const reactivatedAudioClips: string[] = [];\n const reactivatedVideoClips: string[] = [];\n for (const clipId of affectedClipIds) {\n const clip = this.compositionModel.findClip(clipId);\n if (clip?.trackKind === 'audio') {\n await this.audioSession.deactivateClip(clipId);\n reactivatedAudioClips.push(clipId);\n } else if (clip?.trackKind === 'video') {\n // Video clip audio will be restarted via VideoClipSession pipeline restart\n reactivatedVideoClips.push(clipId);\n }\n }\n\n // Activate all audio clips (including reactivated ones)\n await this.audioSession.activateAllAudioClips();\n\n // Restart playback for affected clips (give pipeline time to decode first frame)\n const allReactivatedClips = [...reactivatedAudioClips, ...reactivatedVideoClips];\n if (allReactivatedClips.length > 0) {\n setTimeout(() => {\n for (const clipId of allReactivatedClips) {\n this.audioSession.restartPlayingClip(clipId);\n }\n }, 150);\n }\n }\n\n private handleResourceStateChange(resourceId: string, state: Resource['state']): void {\n if (!this.compositionModel) {\n return;\n }\n\n this.compositionModel.updateResourceState(resourceId, state ?? 'pending');\n\n if (state !== 'ready') {\n return;\n }\n\n const resource = this.compositionModel.getResource(resourceId);\n if (!resource) {\n return;\n }\n\n // Main video/audio resources: data will flow naturally into pipeline\n if (resource.type === 'video' || resource.type === 'audio') {\n return;\n }\n\n // Attachment resources (fonts, images): update instructions for active clips\n const clipIds = this.compositionModel.getClipIdsByResourceId(resourceId);\n for (const clipId of clipIds) {\n // Only update active clips (in 2-Clip window)\n if (!this.clipSessionManager.isClipActive(clipId)) {\n continue;\n }\n\n const clip = this.compositionModel.findClip(clipId);\n if (!clip) {\n continue;\n }\n\n // Rebuild instructions with updated resource status\n const instructions = this.planner.getInstructions(clipId);\n if (!instructions) {\n continue;\n }\n\n // Send updated instructions to worker (no pipeline restart needed)\n const session = this.clipSessionManager.getSession(clipId);\n const visualWorker = session?.visualWorkerHandle;\n if (visualWorker) {\n visualWorker.send('install_instructions', instructions);\n }\n }\n }\n\n async restartWorker(type: WorkerType, clipId?: string): Promise<void> {\n const clipLocalTypes: WorkerType[] = [\n 'videoDemux',\n 'audioDemux',\n 'videoDecode',\n 'audioDecode',\n 'videoCompose',\n 'videoEncode',\n ];\n\n if (clipLocalTypes.includes(type) && !clipId) {\n throw new Error(`clipId required for restarting ${type} worker`);\n }\n\n this.workers.terminate(type, clipId);\n const worker = await this.workers.getOrCreate(type, clipId);\n\n this.eventBus.emit(MeframeEvent.WorkerRestarted, {\n type,\n workerId: worker.getWorkerId(),\n reason: 'Manual restart',\n });\n\n if (clipId) {\n const session = this.clipSessionManager.getSession(clipId);\n if (session) {\n await session.activate();\n }\n }\n }\n\n async renderFrame(timeUs: TimeUs, options?: RenderFrameOptions): Promise<RcFrame | null> {\n const signal = options?.signal;\n const immediate = options?.immediate ?? true;\n\n if (!this.compositionModel) {\n throw new Error('No composition model set');\n }\n\n const clip = this.compositionModel.getClipsAtTime(timeUs, this.compositionModel.mainTrackId)[0];\n if (!clip) {\n return null;\n }\n\n // Detect clip change and proactively ensure cache for current/next\n if (this.currentClipId !== clip.id) {\n this.currentClipId = clip.id;\n void this.ensureClipCache(timeUs, immediate);\n }\n\n // Calculate clip-relative time for cache lookup (global time - clip start time)\n let relativeTimeUs = options?.relativeTimeUs ?? timeUs - clip.startUs;\n\n // Quantize to frame boundary to handle timestamp precision issues\n relativeTimeUs = quantizeTimestampToFrame(relativeTimeUs, 0, this.compositionModel.fps);\n\n // Clamp to clip duration to handle edge cases where quantization pushes beyond clip end\n // This can happen when clip duration is not exactly aligned to frame boundaries\n relativeTimeUs = Math.min(relativeTimeUs, clip.durationUs - 1);\n\n const cachedFrame = this.cacheManager.getFrame(relativeTimeUs, clip.id);\n if (cachedFrame) {\n this.eventBus.emit(MeframeEvent.CacheHit, {\n timeUs,\n level: 'L1',\n key: `${clip.id}-${relativeTimeUs}`,\n });\n return cachedFrame;\n }\n\n this.eventBus.emit(MeframeEvent.CacheMiss, {\n timeUs,\n level: 'L1',\n key: `${clip.id}-${relativeTimeUs}`,\n });\n\n if (signal?.aborted) {\n throw new DOMException('Render aborted', 'AbortError');\n }\n\n // L1 miss - try decode from L2\n const l2Frame = await this.decodeFromL2(relativeTimeUs, clip);\n if (l2Frame) {\n // If we are serving video from L2, opportunistically ensure audio from L2 as well\n void this.ensureAudioFromL2(clip.id);\n return l2Frame;\n }\n\n return null;\n }\n\n private async ensureAudioFromL2(clipId: string): Promise<void> {\n try {\n // Skip if PCM already present\n if (this.cacheManager.hasClipPCM(clipId)) {\n return;\n }\n // Check if L2 has encoded audio for this clip\n const hasAudio = await this.cacheManager.hasClipInL2(clipId, 'audio');\n if (!hasAudio) return;\n // Decode from L2 into PCM cache (non-blocking for render)\n await this.audioSession.ensureClipAudioFromL2(clipId);\n } catch (error) {\n console.warn('[Orchestrator] ensureAudioFromL2IfNeeded error:', error);\n }\n }\n\n // TODO: move to @ClipSessionManager\n private async decodeFromL2(timeUs: TimeUs, clip: Clip): Promise<RcFrame | null> {\n const { id, trackId, startUs } = clip;\n const [chunkStream, metadata] = await Promise.all([\n this.cacheManager.l2Cache.createReadStream(id, 'video'),\n this.cacheManager.l2Cache.getClipMetadata(id, 'video'),\n ]);\n\n if (!chunkStream || !metadata?.codec) {\n return null;\n }\n\n const decoder = new VideoChunkDecoder(`l2-temp-${id}`, {\n codec: metadata.codec,\n width: metadata.codedWidth,\n height: metadata.codedHeight,\n description: metadata.description,\n hardwareAcceleration: metadata.hardwareAcceleration || 'no-preference',\n });\n\n try {\n const decodeStream = chunkStream.pipeThrough(decoder.createStream());\n\n let targetFrame: RcFrame | null = null;\n\n await this.cacheManager.receiveComposedFrames(decodeStream, {\n clipId: id,\n trackId: trackId || this.compositionModel?.mainTrackId || 'main',\n fps: this.compositionModel?.fps ?? 30,\n clipStartUs: startUs,\n onFrame: (info) => {\n if (info.timeUs === timeUs) {\n targetFrame = this.cacheManager.getFrame(timeUs, id);\n }\n },\n });\n\n return targetFrame;\n } finally {\n await decoder.close();\n }\n }\n\n /**\n * Ensure clips are cached using 2-Clip strategy\n * Debounced to avoid excessive session activation during fast seek\n * @param timeUs - Target time for cache window\n * @param immediate - Skip debounce if true (used for initial load)\n */\n async ensureClipCache(timeUs: TimeUs, immediate = false): Promise<void> {\n const executeCache = async (): Promise<void> => {\n if (!this.compositionModel) return;\n\n const clipIds = this.compositionModel.getClipsToCacheAtTime(timeUs);\n if (clipIds.size === 0) return;\n await this.clipSessionManager.ensureClips(clipIds);\n };\n\n if (this.ensureCacheDebounceTimer !== null) {\n clearTimeout(this.ensureCacheDebounceTimer);\n this.ensureCacheDebounceTimer = null;\n }\n\n if (immediate) {\n return executeCache();\n }\n\n return new Promise((resolve) => {\n this.ensureCacheDebounceTimer = setTimeout(async () => {\n this.ensureCacheDebounceTimer = null;\n await executeCache();\n resolve();\n }, this.ensureCacheDebounceDelay) as unknown as number;\n });\n }\n\n /**\n * Wait for clip cache to be ready for playback\n * Returns true if minimum cache is ready, false if timeout\n */\n async waitForClipReady(\n timeUs: TimeUs,\n options?: { minFrameCount?: number; timeoutMs?: number }\n ): Promise<boolean> {\n if (!this.compositionModel) {\n return false;\n }\n\n const clips = this.compositionModel.getClipsAtTime(timeUs, this.compositionModel.mainTrackId);\n if (clips.length === 0) {\n return true;\n }\n\n const currentClip = clips[0];\n if (!currentClip) {\n return true;\n }\n\n // For buffering scenario, we just need ANY frames in the clip\n // Don't restrict to frames after a specific startTimeUs, as the pipeline\n // might be rendering from the beginning while playback is in the middle\n return this.cacheManager.waitForClipReady(currentClip.id, {\n minFrameCount: options?.minFrameCount ?? 5,\n timeoutMs: options?.timeoutMs ?? 5_000,\n // Don't pass startTimeUs - count all frames in the clip\n });\n }\n\n // TODO: move to @ClipSessionManager\n async renderClipForL2(clipId: string): Promise<boolean> {\n const sessionId = `${clipId}#l2`;\n let session: VideoClipSession | null = null;\n\n return new Promise<boolean>((resolve, reject) => {\n this.createSession(sessionId, {\n forL2Only: true,\n clipId: clipId,\n onL2Complete: () => {\n resolve(true);\n },\n onL2Error: (error) => {\n console.error('[Orchestrator] L2 rendering failed for', clipId, error);\n reject(error);\n },\n })\n .then((s) => {\n session = s;\n return session.activate();\n })\n .catch(async (error) => {\n // Clean up partial session on any error to avoid worker state pollution\n if (session) {\n await session.dispose();\n }\n\n if (error instanceof ResourceConflictError) {\n resolve(false);\n } else {\n reject(error);\n }\n });\n });\n }\n\n // TODO: move to @ClipSessionManager\n private async createSession(\n sessionId: string,\n options?: {\n forL2Only?: boolean;\n clipId?: string;\n onL2Complete?: () => void;\n onL2Error?: (error: Error) => void;\n }\n ): Promise<VideoClipSession> {\n const clipId = options?.clipId ?? sessionId;\n const clip = this.compositionModel?.findClip(clipId);\n if (!clip) {\n throw new Error(`Clip ${clipId} not found`);\n }\n\n const session = await VideoClipSession.create({\n clipId,\n sessionId,\n forL2Only: options?.forL2Only ?? false,\n planner: this.planner,\n workerPool: this.workers,\n cacheManager: this.cacheManager,\n compositionModel: this.compositionModel!,\n workerConfigs: this.buildWorkerConfigs(),\n resourceLoader: this.resourceLoader,\n callbacks: {\n onComposedStreamReady: (stream, fps) => {\n if (options?.forL2Only) {\n // L2 channel: don't need L1, cancel stream\n stream.cancel();\n } else {\n // Preview channel: fill L1\n this.cacheManager.receiveComposedFrames(stream, {\n clipId: clipId,\n trackId: this.compositionModel!.mainTrackId,\n fps,\n clipStartUs: clip.startUs,\n onFrame: () => {},\n });\n }\n },\n onEncodedStreamReady: (stream, track) => {\n if (options?.forL2Only) {\n // L2 channel: write to L2 using clipId, notify on complete\n this.cacheManager.receiveEncodedChunks(stream, clipId, track, {\n onComplete: () => {\n session.dispose();\n // Only notify completion for video track (audio is optional)\n if (track === 'video' && options.onL2Complete) {\n options.onL2Complete();\n }\n },\n onError: (error) => {\n console.error(\n `[Orchestrator] L2 encode stream error for ${clipId} ${track}:`,\n error\n );\n session.dispose();\n if (options.onL2Error) {\n options.onL2Error(error);\n }\n },\n });\n } else {\n // Preview channel: don't write to L2, cancel stream\n stream.cancel();\n }\n },\n onAudioStreamReady: (stream, metadata) => {\n if (options?.forL2Only) {\n stream.cancel();\n } else {\n this.audioSession.handleAudioStream(stream, metadata);\n }\n },\n onPipelineReady: async (attachmentResourceIds?: string[]) => {\n // 1. Load main track resource\n const clip = this.compositionModel?.findClip(clipId);\n if (clip && hasResourceId(clip)) {\n await this.resourceLoader.fetch(clip.resourceId, {\n priority: options?.forL2Only ? 'low' : 'high',\n sessionId: sessionId,\n trackId: clip.trackId,\n isMainTrack: true,\n });\n }\n\n // 2. Load attachment resources (global shared Blob cache)\n if (attachmentResourceIds && attachmentResourceIds.length > 0) {\n for (const resourceId of attachmentResourceIds) {\n await this.resourceLoader.fetch(resourceId, {\n priority: 'normal',\n sessionId: sessionId,\n isMainTrack: false,\n });\n }\n }\n },\n },\n });\n\n this.activeClips.add(sessionId);\n return session;\n }\n\n async dispose(): Promise<void> {\n if (this.ensureCacheDebounceTimer !== null) {\n clearTimeout(this.ensureCacheDebounceTimer);\n this.ensureCacheDebounceTimer = null;\n }\n\n this.resourceLoader.dispose();\n await this.clipSessionManager.dispose();\n await this.cacheManager.clear();\n\n this.currentClipId = null;\n this.activeClips.clear();\n\n this.workers.terminateAll();\n this.compositionModel = null;\n this.eventBus.dispose();\n }\n\n private buildWorkerConfigs(): Record<WorkerType, any> {\n const config = this.config as any;\n const defaultCanvasWidth = config.global?.defaultCanvasWidth ?? 720;\n const defaultCanvasHeight = config.global?.defaultCanvasHeight ?? 1280;\n const defaultFps = config.global?.defaultFps ?? 30;\n return {\n videoDemux: {\n highWaterMark: config.demux?.backpressure?.highWaterMark ?? 10,\n },\n audioDemux: {\n highWaterMark: config.demux?.backpressure?.highWaterMark ?? 10,\n },\n videoDecode: config.decode?.video,\n audioDecode: config.decode?.audio,\n videoCompose: {\n width: config.compose?.canvas?.width ?? defaultCanvasWidth,\n height: config.compose?.canvas?.height ?? defaultCanvasHeight,\n fps: config.global?.defaultFps ?? defaultFps,\n backgroundColor: config.compose?.canvas?.backgroundColor ?? '#000000',\n enableSmoothing: config.compose?.visual?.enableSmoothing ?? true,\n enableHardwareAcceleration: config.compose?.visual?.enableHardwareAcceleration ?? true,\n fonts: config.global?.fonts,\n },\n audioCompose: {\n ducking: config.compose?.audio?.ducking,\n mixing: config.compose?.audio?.mixing,\n },\n videoEncode: {\n codec: 'avc1.42002A',\n width: config.compose?.canvas?.width || defaultCanvasWidth,\n height: config.compose?.canvas?.height || defaultCanvasHeight,\n bitrate: config.encode?.video?.bitrateKbps\n ? config.encode.video.bitrateKbps * 1000\n : 12_000_000,\n framerate: config.encode?.video?.framerate || defaultFps,\n latencyMode: 'quality',\n bitrateMode: 'variable',\n hardwareAcceleration: 'no-preference',\n ...(config.encode?.video as any),\n },\n };\n }\n\n async export(model: CompositionModel, options: ExportOptions): Promise<Blob> {\n return this.muxManager.export(model, options);\n }\n}\n"],"names":["applyModelPatch","clip"],"mappings":"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"} | ||
| {"version":3,"file":"Orchestrator.js","sources":["../../src/orchestrator/Orchestrator.ts"],"sourcesContent":["import { EventBus } from '../event/EventBus';\nimport { WorkerPool } from '../worker/WorkerPool';\nimport { applyPatch as applyModelPatch } from '../model/patch';\nimport { ResourceConflictError, ResourceLoader } from '../stages/load/ResourceLoader';\nimport { CacheManager } from '../cache/CacheManager';\nimport { ConfigLoader } from '../config/ConfigLoader';\nimport type { IOrchestrator, OrchestratorConfig, RenderFrameOptions } from './types';\nimport { WorkerStatus, WorkerType } from '../worker/types';\nimport { CompositionModel, CompositionPatch, Resource, TimeUs, RcFrame, Clip } from '../model';\nimport { hasResourceId } from '../model/types';\nimport { MeframeEvent, type EventPayloadMap } from '../event/events';\nimport { CompositionPlanner } from './CompositionPlanner';\nimport { VideoClipSession } from './VideoClipSession';\nimport { ClipSessionManager } from './ClipSessionManager';\nimport { GlobalAudioSession } from './GlobalAudioSession';\nimport { MuxManager } from '../stages/mux/MuxManager';\nimport { ExportOptions } from '@/types';\nimport { VideoChunkDecoder } from '../stages/decode/VideoChunkDecoder';\nimport { quantizeTimestampToFrame } from '../utils/time-utils';\n\nexport class Orchestrator implements IOrchestrator {\n workers: WorkerPool;\n eventBus: EventBus<EventPayloadMap>;\n compositionModel: CompositionModel | null = null;\n resourceLoader: ResourceLoader;\n cacheManager: CacheManager;\n planner: CompositionPlanner;\n audioSession: GlobalAudioSession;\n muxManager: MuxManager;\n\n private activeClips = new Set<string>();\n private isInitialized = false;\n private config = ConfigLoader.getInstance().getConfig();\n private clipSessionManager: ClipSessionManager;\n private currentClipId: string | null = null;\n private ensureCacheDebounceTimer: number | null = null;\n private readonly ensureCacheDebounceDelay = 150;\n readonly events: Pick<EventBus<EventPayloadMap>, 'on' | 'off' | 'once'>;\n\n constructor(config: OrchestratorConfig) {\n // Use provided eventBus or create a new one\n this.eventBus = config.eventBus || new EventBus<EventPayloadMap>();\n this.events = this.eventBus.asReadonly();\n\n // Initialize config first\n this.config = ConfigLoader.getInstance().getConfig();\n\n const workerConfigs = this.buildWorkerConfigs();\n\n // Initialize WorkerPool with worker path from config\n this.workers = new WorkerPool({\n eventBus: this.eventBus,\n workerConfigs,\n workerPath: config.workerPath,\n workerExtension: config.workerExtension,\n });\n\n this.resourceLoader = new ResourceLoader({\n orchestrator: this as any,\n eventBus: this.eventBus,\n config: {\n maxConcurrent: config.maxWorkers || (this.config.load as any)?.retry?.maxAttempts || 4,\n },\n onStateChange: (resourceId, state) => this.handleResourceStateChange(resourceId, state),\n });\n\n this.planner = new CompositionPlanner();\n\n const cacheConfig = config.cacheConfig || this.config.cache;\n this.cacheManager = new CacheManager(\n {\n l1: {\n maxMemoryMB:\n (cacheConfig as any)?.l1Size || (cacheConfig as any)?.l1?.maxMemoryMB || 1024,\n maxGOPs: (this.config.decode as any)?.video?.maxGOPs || 4,\n },\n l2: {\n maxSizeMB: (cacheConfig as any)?.l2Size || (cacheConfig as any)?.l2?.maxSizeMB || 2048,\n projectId: config.projectId || this.config.global.projectId || 'default',\n },\n },\n this.eventBus\n );\n\n this.clipSessionManager = new ClipSessionManager({\n maxConcurrent: 2,\n factory: {\n createSession: (clipId) => this.createSession(clipId),\n },\n cacheManager: this.cacheManager,\n });\n\n this.audioSession = new GlobalAudioSession({\n cacheManager: this.cacheManager,\n workers: this.workers,\n resourceLoader: this.resourceLoader,\n eventBus: this.eventBus,\n getModel: () => this.compositionModel,\n buildWorkerConfigs: () => this.buildWorkerConfigs(),\n });\n\n this.muxManager = new MuxManager(\n this.cacheManager,\n this.audioSession,\n this.config.encode.audio as AudioEncoderConfig\n );\n }\n\n get workerStatus(): WorkerStatus {\n const status = this.workers.status;\n const result: WorkerStatus = {} as WorkerStatus;\n\n const workerTypes: WorkerType[] = [\n 'videoDemux',\n 'audioDemux',\n 'videoDecode',\n 'audioDecode',\n 'videoCompose',\n 'audioCompose',\n 'videoEncode',\n ];\n\n for (const type of workerTypes) {\n result[type] = status[type] || {\n state: 'idle',\n taskCount: 0,\n };\n }\n\n return result;\n }\n\n async initialize(): Promise<void> {\n if (this.isInitialized) return;\n\n await this.cacheManager.init();\n\n this.isInitialized = true;\n }\n\n // Event methods - forward to eventBus\n on<K extends keyof EventPayloadMap>(\n event: K,\n handler: (payload: EventPayloadMap[K]) => void\n ): void {\n this.eventBus.on(event, handler);\n }\n\n off<K extends keyof EventPayloadMap>(\n event: K,\n handler: (payload: EventPayloadMap[K]) => void\n ): void {\n this.eventBus.off(event, handler);\n }\n\n once<K extends keyof EventPayloadMap>(\n event: K,\n handler: (payload: EventPayloadMap[K]) => void\n ): void {\n this.eventBus.once(event, handler);\n }\n\n async setCompositionModel(model: CompositionModel): Promise<void> {\n this.compositionModel = model;\n this.planner.setModel(model);\n this.currentClipId = null;\n\n this.eventBus.emit(MeframeEvent.ModelSet, model);\n\n this.eventBus.emit(MeframeEvent.CompositionUpdated, {\n trackCount: model.tracks.length,\n clipCount: model.tracks.reduce((acc: number, track: any) => acc + track.clips.length, 0),\n durationUs: model.durationUs,\n });\n\n await this.audioSession.activateAllAudioClips();\n }\n\n async applyPatch(patch: CompositionPatch): Promise<void> {\n if (!this.compositionModel) {\n throw new Error('No composition model set');\n }\n\n // Apply patch and get affected clip IDs (simplified for 2-Clip strategy)\n // Note: addTrack/removeTrack already call buildIndexes() in patch.ts\n const affectedClipIds = applyModelPatch(this.compositionModel, patch);\n const clipUpdates = this.planner.applyPatch(patch, affectedClipIds);\n this.eventBus.emit(MeframeEvent.PatchApplied, {\n operations: patch.operations.length,\n affectedClips: Array.from(affectedClipIds),\n });\n\n // Process clip updates\n for (const update of clipUpdates) {\n if (update.type === 'remove') {\n this.activeClips.delete(update.clipId);\n }\n\n this.cacheManager.invalidateClip(update.clipId);\n await this.clipSessionManager.handlePlannerUpdate(update.clipId, update);\n }\n\n // Reactivate updated audio clips\n const reactivatedAudioClips: string[] = [];\n const reactivatedVideoClips: string[] = [];\n for (const clipId of affectedClipIds) {\n const clip = this.compositionModel.findClip(clipId);\n if (clip?.trackKind === 'audio') {\n await this.audioSession.deactivateClip(clipId);\n reactivatedAudioClips.push(clipId);\n } else if (clip?.trackKind === 'video') {\n // Video clip audio will be restarted via VideoClipSession pipeline restart\n reactivatedVideoClips.push(clipId);\n }\n }\n\n // Activate all audio clips (including reactivated ones)\n await this.audioSession.activateAllAudioClips();\n\n // Restart playback for affected clips (give pipeline time to decode first frame)\n const allReactivatedClips = [...reactivatedAudioClips, ...reactivatedVideoClips];\n if (allReactivatedClips.length > 0) {\n setTimeout(() => {\n for (const clipId of allReactivatedClips) {\n this.audioSession.restartPlayingClip(clipId);\n }\n }, 150);\n }\n }\n\n private handleResourceStateChange(resourceId: string, state: Resource['state']): void {\n if (!this.compositionModel) {\n return;\n }\n\n this.compositionModel.updateResourceState(resourceId, state ?? 'pending');\n\n if (state !== 'ready') {\n return;\n }\n\n const resource = this.compositionModel.getResource(resourceId);\n if (!resource) {\n return;\n }\n\n // Main video/audio resources: data will flow naturally into pipeline\n if (resource.type === 'video' || resource.type === 'audio') {\n return;\n }\n\n // Attachment resources (fonts, images): update instructions for active clips\n const clipIds = this.compositionModel.getClipIdsByResourceId(resourceId);\n for (const clipId of clipIds) {\n // Only update active clips (in 2-Clip window)\n if (!this.clipSessionManager.isClipActive(clipId)) {\n continue;\n }\n\n const clip = this.compositionModel.findClip(clipId);\n if (!clip) {\n continue;\n }\n\n // Rebuild instructions with updated resource status\n const instructions = this.planner.getInstructions(clipId);\n if (!instructions) {\n continue;\n }\n\n // Send updated instructions to worker (no pipeline restart needed)\n const session = this.clipSessionManager.getSession(clipId);\n const composeWorker = session?.composeWorker;\n if (composeWorker) {\n composeWorker.send('install_instructions', instructions);\n }\n }\n }\n\n async restartWorker(type: WorkerType, clipId?: string): Promise<void> {\n const clipLocalTypes: WorkerType[] = [\n 'videoDemux',\n 'audioDemux',\n 'videoDecode',\n 'audioDecode',\n 'videoCompose',\n 'videoEncode',\n ];\n\n if (clipLocalTypes.includes(type) && !clipId) {\n throw new Error(`clipId required for restarting ${type} worker`);\n }\n\n this.workers.terminate(type, clipId);\n const worker = await this.workers.getOrCreate(type, clipId);\n\n this.eventBus.emit(MeframeEvent.WorkerRestarted, {\n type,\n workerId: worker.getWorkerId(),\n reason: 'Manual restart',\n });\n\n if (clipId) {\n const session = this.clipSessionManager.getSession(clipId);\n if (session) {\n await session.activate();\n }\n }\n }\n\n async renderFrame(timeUs: TimeUs, options?: RenderFrameOptions): Promise<RcFrame | null> {\n const signal = options?.signal;\n const immediate = options?.immediate ?? true;\n\n if (!this.compositionModel) {\n throw new Error('No composition model set');\n }\n\n const clip = this.compositionModel.getClipsAtTime(timeUs, this.compositionModel.mainTrackId)[0];\n if (!clip) {\n return null;\n }\n\n // Detect clip change and proactively ensure cache for current/next\n if (this.currentClipId !== clip.id) {\n this.currentClipId = clip.id;\n void this.ensureClipCache(timeUs, immediate);\n }\n\n // Calculate clip-relative time for cache lookup (global time - clip start time)\n let relativeTimeUs = options?.relativeTimeUs ?? timeUs - clip.startUs;\n\n // Quantize to frame boundary to handle timestamp precision issues\n relativeTimeUs = quantizeTimestampToFrame(relativeTimeUs, 0, this.compositionModel.fps);\n\n // Clamp to clip duration to handle edge cases where quantization pushes beyond clip end\n // This can happen when clip duration is not exactly aligned to frame boundaries\n relativeTimeUs = Math.min(relativeTimeUs, clip.durationUs - 1);\n\n const cachedFrame = this.cacheManager.getFrame(relativeTimeUs, clip.id);\n if (cachedFrame) {\n this.eventBus.emit(MeframeEvent.CacheHit, {\n timeUs,\n level: 'L1',\n key: `${clip.id}-${relativeTimeUs}`,\n });\n return cachedFrame;\n }\n\n this.eventBus.emit(MeframeEvent.CacheMiss, {\n timeUs,\n level: 'L1',\n key: `${clip.id}-${relativeTimeUs}`,\n });\n\n if (signal?.aborted) {\n throw new DOMException('Render aborted', 'AbortError');\n }\n\n // L1 miss - try decode from L2\n const l2Frame = await this.decodeFromL2(relativeTimeUs, clip);\n if (l2Frame) {\n // If we are serving video from L2, opportunistically ensure audio from L2 as well\n void this.ensureAudioFromL2(clip.id);\n return l2Frame;\n }\n\n return null;\n }\n\n private async ensureAudioFromL2(clipId: string): Promise<void> {\n try {\n // Skip if PCM already present\n if (this.cacheManager.hasClipPCM(clipId)) {\n return;\n }\n // Check if L2 has encoded audio for this clip\n const hasAudio = await this.cacheManager.hasClipInL2(clipId, 'audio');\n if (!hasAudio) return;\n // Decode from L2 into PCM cache (non-blocking for render)\n await this.audioSession.ensureClipAudioFromL2(clipId);\n } catch (error) {\n console.warn('[Orchestrator] ensureAudioFromL2IfNeeded error:', error);\n }\n }\n\n // TODO: move to @ClipSessionManager\n private async decodeFromL2(timeUs: TimeUs, clip: Clip): Promise<RcFrame | null> {\n const { id, trackId, startUs } = clip;\n const [chunkStream, metadata] = await Promise.all([\n this.cacheManager.l2Cache.createReadStream(id, 'video'),\n this.cacheManager.l2Cache.getClipMetadata(id, 'video'),\n ]);\n\n if (!chunkStream || !metadata?.codec) {\n return null;\n }\n\n const decoder = new VideoChunkDecoder(`l2-temp-${id}`, {\n codec: metadata.codec,\n width: metadata.codedWidth,\n height: metadata.codedHeight,\n description: metadata.description,\n hardwareAcceleration: metadata.hardwareAcceleration || 'no-preference',\n });\n\n try {\n const decodeStream = chunkStream.pipeThrough(decoder.createStream());\n\n let targetFrame: RcFrame | null = null;\n\n await this.cacheManager.receiveComposedFrames(decodeStream, {\n clipId: id,\n trackId: trackId || this.compositionModel?.mainTrackId || 'main',\n fps: this.compositionModel?.fps ?? 30,\n clipStartUs: startUs,\n onFrame: (info) => {\n if (info.timeUs === timeUs) {\n targetFrame = this.cacheManager.getFrame(timeUs, id);\n }\n },\n });\n\n return targetFrame;\n } finally {\n await decoder.close();\n }\n }\n\n /**\n * Ensure clips are cached using 2-Clip strategy\n * Debounced to avoid excessive session activation during fast seek\n * @param timeUs - Target time for cache window\n * @param immediate - Skip debounce if true (used for initial load)\n */\n async ensureClipCache(timeUs: TimeUs, immediate = false): Promise<void> {\n const executeCache = async (): Promise<void> => {\n if (!this.compositionModel) return;\n\n const clipIds = this.compositionModel.getClipsToCacheAtTime(timeUs);\n if (clipIds.size === 0) return;\n await this.clipSessionManager.ensureClips(clipIds);\n };\n\n if (this.ensureCacheDebounceTimer !== null) {\n clearTimeout(this.ensureCacheDebounceTimer);\n this.ensureCacheDebounceTimer = null;\n }\n\n if (immediate) {\n return executeCache();\n }\n\n return new Promise((resolve) => {\n this.ensureCacheDebounceTimer = setTimeout(async () => {\n this.ensureCacheDebounceTimer = null;\n await executeCache();\n resolve();\n }, this.ensureCacheDebounceDelay) as unknown as number;\n });\n }\n\n /**\n * Wait for clip cache to be ready for playback\n * Returns true if minimum cache is ready, false if timeout\n */\n async waitForClipReady(\n timeUs: TimeUs,\n options?: { minFrameCount?: number; timeoutMs?: number }\n ): Promise<boolean> {\n if (!this.compositionModel) {\n return false;\n }\n\n const clips = this.compositionModel.getClipsAtTime(timeUs, this.compositionModel.mainTrackId);\n if (clips.length === 0) {\n return true;\n }\n\n const currentClip = clips[0];\n if (!currentClip) {\n return true;\n }\n\n // For buffering scenario, we just need ANY frames in the clip\n // Don't restrict to frames after a specific startTimeUs, as the pipeline\n // might be rendering from the beginning while playback is in the middle\n return this.cacheManager.waitForClipReady(currentClip.id, {\n minFrameCount: options?.minFrameCount ?? 5,\n timeoutMs: options?.timeoutMs ?? 5_000,\n // Don't pass startTimeUs - count all frames in the clip\n });\n }\n\n // TODO: move to @ClipSessionManager\n async renderClipForL2(clipId: string): Promise<boolean> {\n const sessionId = `${clipId}#l2`;\n let session: VideoClipSession | null = null;\n\n return new Promise<boolean>((resolve, reject) => {\n this.createSession(sessionId, {\n forL2Only: true,\n clipId: clipId,\n onL2Complete: () => {\n resolve(true);\n },\n onL2Error: (error) => {\n console.error('[Orchestrator] L2 rendering failed for', clipId, error);\n reject(error);\n },\n })\n .then((s) => {\n session = s;\n return session.activate();\n })\n .catch(async (error) => {\n // Clean up partial session on any error to avoid worker state pollution\n if (session) {\n await session.dispose();\n }\n\n if (error instanceof ResourceConflictError) {\n resolve(false);\n } else {\n reject(error);\n }\n });\n });\n }\n\n // TODO: move to @ClipSessionManager\n private async createSession(\n sessionId: string,\n options?: {\n forL2Only?: boolean;\n clipId?: string;\n onL2Complete?: () => void;\n onL2Error?: (error: Error) => void;\n }\n ): Promise<VideoClipSession> {\n const clipId = options?.clipId ?? sessionId;\n const clip = this.compositionModel?.findClip(clipId);\n if (!clip) {\n throw new Error(`Clip ${clipId} not found`);\n }\n\n const session = await VideoClipSession.create({\n clipId,\n sessionId,\n forL2Only: options?.forL2Only ?? false,\n planner: this.planner,\n workerPool: this.workers,\n cacheManager: this.cacheManager,\n compositionModel: this.compositionModel!,\n workerConfigs: this.buildWorkerConfigs(),\n resourceLoader: this.resourceLoader,\n callbacks: {\n onComposedStreamReady: async (stream, fps) => {\n // Only preview channel calls this (L2 sends directly to encode worker)\n await this.cacheManager.receiveComposedFrames(stream, {\n clipId: clipId,\n trackId: this.compositionModel!.mainTrackId,\n fps,\n clipStartUs: clip.startUs,\n onFrame: () => {},\n });\n },\n onEncodedStreamReady: async (stream, track) => {\n // Only L2 channel calls this (preview has no encode worker)\n await this.cacheManager.receiveEncodedChunks(stream, clipId, track, {\n onComplete: () => {\n session.dispose();\n // Only notify completion for video track (audio is optional)\n if (track === 'video' && options?.onL2Complete) {\n options.onL2Complete();\n }\n },\n onError: (error) => {\n console.error(`[Orchestrator] L2 encode stream error for ${clipId} ${track}:`, error);\n session.dispose();\n if (options?.onL2Error) {\n options.onL2Error(error);\n }\n },\n });\n },\n onAudioStreamReady: (stream, metadata) => {\n if (options?.forL2Only) {\n stream.cancel();\n } else {\n this.audioSession.handleAudioStream(stream, metadata);\n }\n },\n onPipelineReady: async (attachmentResourceIds?: string[]) => {\n // 1. Load main track resource\n const clip = this.compositionModel?.findClip(clipId);\n if (clip && hasResourceId(clip)) {\n await this.resourceLoader.fetch(clip.resourceId, {\n priority: options?.forL2Only ? 'low' : 'high',\n sessionId: sessionId,\n trackId: clip.trackId,\n isMainTrack: true,\n });\n }\n\n // 2. Load attachment resources (global shared Blob cache)\n if (attachmentResourceIds && attachmentResourceIds.length > 0) {\n for (const resourceId of attachmentResourceIds) {\n await this.resourceLoader.fetch(resourceId, {\n priority: 'normal',\n sessionId: sessionId,\n isMainTrack: false,\n });\n }\n }\n },\n },\n });\n\n this.activeClips.add(sessionId);\n return session;\n }\n\n async dispose(): Promise<void> {\n if (this.ensureCacheDebounceTimer !== null) {\n clearTimeout(this.ensureCacheDebounceTimer);\n this.ensureCacheDebounceTimer = null;\n }\n\n this.resourceLoader.dispose();\n await this.clipSessionManager.dispose();\n await this.cacheManager.clear();\n\n this.currentClipId = null;\n this.activeClips.clear();\n\n this.workers.terminateAll();\n this.compositionModel = null;\n this.eventBus.dispose();\n }\n\n private buildWorkerConfigs(): Record<WorkerType, any> {\n const config = this.config as any;\n const defaultCanvasWidth = config.global?.defaultCanvasWidth ?? 720;\n const defaultCanvasHeight = config.global?.defaultCanvasHeight ?? 1280;\n const defaultFps = config.global?.defaultFps ?? 30;\n return {\n videoDemux: {\n highWaterMark: config.demux?.backpressure?.highWaterMark ?? 10,\n },\n audioDemux: {\n highWaterMark: config.demux?.backpressure?.highWaterMark ?? 10,\n },\n videoDecode: config.decode?.video,\n audioDecode: config.decode?.audio,\n videoCompose: {\n width: config.compose?.canvas?.width ?? defaultCanvasWidth,\n height: config.compose?.canvas?.height ?? defaultCanvasHeight,\n fps: config.global?.defaultFps ?? defaultFps,\n backgroundColor: config.compose?.canvas?.backgroundColor ?? '#000000',\n enableSmoothing: config.compose?.visual?.enableSmoothing ?? true,\n enableHardwareAcceleration: config.compose?.visual?.enableHardwareAcceleration ?? true,\n fonts: config.global?.fonts,\n },\n audioCompose: {\n ducking: config.compose?.audio?.ducking,\n mixing: config.compose?.audio?.mixing,\n },\n videoEncode: {\n codec: 'avc1.42002A',\n width: config.compose?.canvas?.width || defaultCanvasWidth,\n height: config.compose?.canvas?.height || defaultCanvasHeight,\n bitrate: config.encode?.video?.bitrateKbps\n ? config.encode.video.bitrateKbps * 1000\n : 12_000_000,\n framerate: config.encode?.video?.framerate || defaultFps,\n latencyMode: 'quality',\n bitrateMode: 'variable',\n hardwareAcceleration: 'no-preference',\n ...(config.encode?.video as any),\n },\n };\n }\n\n async export(model: CompositionModel, options: ExportOptions): Promise<Blob> {\n return this.muxManager.export(model, options);\n }\n}\n"],"names":["applyModelPatch","clip"],"mappings":"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"} |
@@ -10,7 +10,7 @@ import { WorkerPool } from '../worker/WorkerPool'; | ||
| interface VideoClipSessionCallbacks { | ||
| onComposedStreamReady(stream: ReadableStream<VideoFrame>, fps: number): void; | ||
| onComposedStreamReady(stream: ReadableStream<VideoFrame>, fps: number): Promise<void>; | ||
| onEncodedStreamReady(stream: ReadableStream<{ | ||
| chunk: EncodedVideoChunk; | ||
| metadata: EncodedVideoChunkMetadata; | ||
| }>, track: 'video' | 'audio'): void; | ||
| }>, track: 'video' | 'audio'): Promise<void>; | ||
| onAudioStreamReady?(stream: ReadableStream<AudioData>, metadata: { | ||
@@ -48,3 +48,3 @@ sessionId: string; | ||
| private instructionContext; | ||
| private visualWorker; | ||
| composeWorker: BaseWorker | null; | ||
| private decodeWorker; | ||
@@ -55,3 +55,3 @@ private videoEncodeWorker; | ||
| private visualStream; | ||
| private visualToEncodeChannel; | ||
| private composeToEncodeChannel; | ||
| private decodeToVisualChannel; | ||
@@ -68,3 +68,2 @@ private demuxToDecodeChannel; | ||
| handlePlannerUpdate(update: ClipUpdateResult): Promise<void>; | ||
| get visualWorkerHandle(): BaseWorker | null; | ||
| private getClip; | ||
@@ -71,0 +70,0 @@ private getResource; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"VideoClipSession.d.ts","sourceRoot":"","sources":["../../src/orchestrator/VideoClipSession.ts"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"VideoClipSession.d.ts","sourceRoot":"","sources":["../../src/orchestrator/VideoClipSession.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,KAAK,EAAE,gBAAgB,EAAQ,MAAM,UAAU,CAAC;AACvD,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAEjF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAGpE,UAAU,yBAAyB;IACjC,qBAAqB,CAAC,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtF,oBAAoB,CAClB,MAAM,EAAE,cAAc,CAAC;QAAE,KAAK,EAAE,iBAAiB,CAAC;QAAC,QAAQ,EAAE,yBAAyB,CAAA;KAAE,CAAC,EACzF,KAAK,EAAE,OAAO,GAAG,OAAO,GACvB,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,kBAAkB,CAAC,CACjB,MAAM,EAAE,cAAc,CAAC,SAAS,CAAC,EACjC,QAAQ,EAAE;QACR,SAAS,EAAE,MAAM,CAAC;QAClB,WAAW,EAAE,MAAM,CAAC;QACpB,cAAc,EAAE,MAAM,CAAC;KACxB,GACA,IAAI,CAAC;IACR,gBAAgB,CAAC,IAAI,IAAI,CAAC;IAC1B,eAAe,CAAC,CAAC,qBAAqB,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnE;AAED,UAAU,sBAAsB;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,kBAAkB,CAAC;IAC5B,UAAU,EAAE,UAAU,CAAC;IACvB,YAAY,EAAE,YAAY,CAAC;IAC3B,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,aAAa,EAAE,MAAM,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;IACvC,SAAS,EAAE,yBAAyB,CAAC;IACrC,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAQD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAU;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;IACxC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;IAC5C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAmB;IACpD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA0B;IACxD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA4B;IACtD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAiB;IAEjD,OAAO,CAAC,kBAAkB,CAAmC;IAC7D,aAAa,EAAE,UAAU,GAAG,IAAI,CAAQ;IACxC,OAAO,CAAC,YAAY,CAA2B;IAC/C,OAAO,CAAC,iBAAiB,CAA2B;IACpD,OAAO,CAAC,gBAAgB,CAA2B;IACnD,OAAO,CAAC,iBAAiB,CAA2B;IACpD,OAAO,CAAC,YAAY,CAA2C;IAC/D,OAAO,CAAC,sBAAsB,CAA+B;IAC7D,OAAO,CAAC,qBAAqB,CAA+B;IAC5D,OAAO,CAAC,oBAAoB,CAA+B;IAC3D,OAAO,CAAC,yBAAyB,CAA+B;IAChE,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,UAAU,CAAS;WAEd,MAAM,CAAC,MAAM,EAAE,sBAAsB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAI9E,OAAO;IAaD,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAiCzB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAO3B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAOxB,mBAAmB,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IA6BlE,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,+BAA+B;YAsBzB,kBAAkB;YAQlB,kBAAkB;YAKlB,oBAAoB;YAqDpB,kBAAkB;YAKlB,cAAc;YA8Ed,oBAAoB;YA4JpB,mBAAmB;YAYnB,eAAe;IAuEvB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;CAG3C"} |
@@ -14,3 +14,3 @@ import { hasResourceId } from "../model/types.js"; | ||
| instructionContext = null; | ||
| visualWorker = null; | ||
| composeWorker = null; | ||
| decodeWorker = null; | ||
@@ -21,3 +21,3 @@ videoEncodeWorker = null; | ||
| visualStream = null; | ||
| visualToEncodeChannel = null; | ||
| composeToEncodeChannel = null; | ||
| decodeToVisualChannel = null; | ||
@@ -104,5 +104,2 @@ demuxToDecodeChannel = null; | ||
| } | ||
| get visualWorkerHandle() { | ||
| return this.visualWorker; | ||
| } | ||
| getClip() { | ||
@@ -146,3 +143,3 @@ return this.compositionModel?.findClip?.(this.clipId) ?? null; | ||
| async connectImagePipeline() { | ||
| if (!this.visualWorker) { | ||
| if (!this.composeWorker) { | ||
| throw new Error("Pipeline workers not ready"); | ||
@@ -152,12 +149,12 @@ } | ||
| if (this.videoEncodeWorker) { | ||
| this.visualToEncodeChannel = new MessageChannel(); | ||
| await this.visualWorker.send( | ||
| this.composeToEncodeChannel = new MessageChannel(); | ||
| await this.composeWorker.send( | ||
| "connect", | ||
| { | ||
| direction: "downstream", | ||
| port: this.visualToEncodeChannel.port1, | ||
| port: this.composeToEncodeChannel.port1, | ||
| streamType: "video", | ||
| sessionId | ||
| }, | ||
| { transfer: [this.visualToEncodeChannel.port1] } | ||
| { transfer: [this.composeToEncodeChannel.port1] } | ||
| ); | ||
@@ -168,13 +165,13 @@ await this.videoEncodeWorker.send( | ||
| direction: "upstream", | ||
| port: this.visualToEncodeChannel.port2, | ||
| port: this.composeToEncodeChannel.port2, | ||
| streamType: "video", | ||
| sessionId | ||
| }, | ||
| { transfer: [this.visualToEncodeChannel.port2] } | ||
| { transfer: [this.composeToEncodeChannel.port2] } | ||
| ); | ||
| } | ||
| this.visualWorker.receiveStream((stream) => { | ||
| this.composeWorker.receiveStream(async (stream) => { | ||
| const visualConfig = this.workerConfigs.videoCompose ?? {}; | ||
| const fps = this.compositionModel.fps ?? visualConfig.fps ?? 30; | ||
| this.callbacks.onComposedStreamReady(stream, fps); | ||
| await this.callbacks.onComposedStreamReady(stream, fps); | ||
| }); | ||
@@ -221,3 +218,3 @@ if (this.videoEncodeWorker) { | ||
| } | ||
| this.visualWorker = await this.workerPool.getOrCreate("videoCompose", this.sessionId, { | ||
| this.composeWorker = await this.workerPool.getOrCreate("videoCompose", this.sessionId, { | ||
| lazy: true | ||
@@ -233,3 +230,3 @@ }); | ||
| }; | ||
| await this.visualWorker.send("configure", { | ||
| await this.composeWorker.send("configure", { | ||
| initial: true, | ||
@@ -258,3 +255,3 @@ clipId: clip.id, | ||
| async connectVideoPipeline() { | ||
| if (!this.visualWorker || !this.decodeWorker || !this.videoDemuxWorker) { | ||
| if (!this.composeWorker || !this.decodeWorker || !this.videoDemuxWorker) { | ||
| throw new Error("Pipeline workers not ready"); | ||
@@ -264,12 +261,12 @@ } | ||
| if (this.videoEncodeWorker) { | ||
| this.visualToEncodeChannel = new MessageChannel(); | ||
| await this.visualWorker.send( | ||
| this.composeToEncodeChannel = new MessageChannel(); | ||
| await this.composeWorker.send( | ||
| "connect", | ||
| { | ||
| direction: "downstream", | ||
| port: this.visualToEncodeChannel.port1, | ||
| port: this.composeToEncodeChannel.port1, | ||
| streamType: "video", | ||
| sessionId | ||
| }, | ||
| { transfer: [this.visualToEncodeChannel.port1] } | ||
| { transfer: [this.composeToEncodeChannel.port1] } | ||
| ); | ||
@@ -280,7 +277,7 @@ await this.videoEncodeWorker.send( | ||
| direction: "upstream", | ||
| port: this.visualToEncodeChannel.port2, | ||
| port: this.composeToEncodeChannel.port2, | ||
| streamType: "video", | ||
| sessionId | ||
| }, | ||
| { transfer: [this.visualToEncodeChannel.port2] } | ||
| { transfer: [this.composeToEncodeChannel.port2] } | ||
| ); | ||
@@ -299,3 +296,3 @@ } | ||
| ); | ||
| await this.visualWorker.send( | ||
| await this.composeWorker.send( | ||
| "connect", | ||
@@ -331,6 +328,6 @@ { | ||
| ); | ||
| this.visualWorker.receiveStream((stream) => { | ||
| this.composeWorker.receiveStream(async (stream) => { | ||
| const visualConfig = this.workerConfigs.videoCompose ?? {}; | ||
| const fps = this.compositionModel.fps ?? visualConfig.fps ?? 30; | ||
| this.callbacks.onComposedStreamReady(stream, fps); | ||
| await this.callbacks.onComposedStreamReady(stream, fps); | ||
| }); | ||
@@ -400,4 +397,4 @@ if (this.videoEncodeWorker) { | ||
| }; | ||
| if (this.visualWorker) { | ||
| await this.visualWorker.send("install_instructions", instructions); | ||
| if (this.composeWorker) { | ||
| await this.composeWorker.send("install_instructions", instructions); | ||
| } | ||
@@ -421,4 +418,4 @@ } | ||
| } | ||
| if (this.visualWorker && this.instructionContext) { | ||
| await this.visualWorker.notify("dispose_clip", { | ||
| if (this.composeWorker && this.instructionContext) { | ||
| await this.composeWorker.notify("dispose_clip", { | ||
| sessionId: this.sessionId, | ||
@@ -440,5 +437,5 @@ revision: this.instructionContext.revision | ||
| } | ||
| if (this.visualWorker) { | ||
| if (this.composeWorker) { | ||
| this.workerPool.terminate("videoCompose", this.sessionId); | ||
| this.visualWorker = null; | ||
| this.composeWorker = null; | ||
| } | ||
@@ -453,4 +450,4 @@ if (this.videoEncodeWorker) { | ||
| } | ||
| this.visualToEncodeChannel?.port1.close(); | ||
| this.visualToEncodeChannel?.port2.close(); | ||
| this.composeToEncodeChannel?.port1.close(); | ||
| this.composeToEncodeChannel?.port2.close(); | ||
| this.decodeToVisualChannel?.port1.close(); | ||
@@ -462,3 +459,3 @@ this.decodeToVisualChannel?.port2.close(); | ||
| this.demuxAudioToDecodeChannel?.port2.close(); | ||
| this.visualToEncodeChannel = null; | ||
| this.composeToEncodeChannel = null; | ||
| this.decodeToVisualChannel = null; | ||
@@ -465,0 +462,0 @@ this.demuxToDecodeChannel = null; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"VideoClipSession.js","sources":["../../src/orchestrator/VideoClipSession.ts"],"sourcesContent":["import type { WorkerPool } from '../worker/WorkerPool';\nimport type { CacheManager } from '../cache/CacheManager';\nimport type { CompositionModel, Clip } from '../model';\nimport type { CompositionPlanner, ClipUpdateResult } from './CompositionPlanner';\nimport type { ClipInstructionSet } from '../stages/compose/instructions';\nimport type { BaseWorker } from '../worker/BaseWorker';\nimport type { WorkerType } from '../worker/types';\nimport type { ResourceLoader } from '../stages/load/ResourceLoader';\nimport { hasResourceId } from '../model/types';\n\ninterface VideoClipSessionCallbacks {\n onComposedStreamReady(stream: ReadableStream<VideoFrame>, fps: number): void;\n onEncodedStreamReady(\n stream: ReadableStream<{ chunk: EncodedVideoChunk; metadata: EncodedVideoChunkMetadata }>,\n track: 'video' | 'audio'\n ): void;\n onAudioStreamReady?(\n stream: ReadableStream<AudioData>,\n metadata: {\n sessionId: string;\n clipStartUs: number;\n clipDurationUs: number;\n }\n ): void;\n onStreamDisposed?(): void;\n onPipelineReady?(attachmentResourceIds?: string[]): Promise<void>;\n}\n\ninterface VideoClipSessionConfig {\n clipId: string;\n sessionId?: string;\n forL2Only?: boolean;\n planner: CompositionPlanner;\n workerPool: WorkerPool;\n cacheManager: CacheManager;\n compositionModel: CompositionModel;\n workerConfigs: Record<WorkerType, any>;\n callbacks: VideoClipSessionCallbacks;\n resourceLoader?: ResourceLoader;\n}\n\ninterface InstructionContext {\n revision: number;\n instructions: ClipInstructionSet;\n status: ClipInstructionSet['status'];\n}\n\nexport class VideoClipSession {\n private readonly clipId: string;\n private readonly sessionId: string;\n private readonly forL2Only: boolean;\n private readonly planner: CompositionPlanner;\n private readonly workerPool: WorkerPool;\n private readonly cacheManager: CacheManager;\n private readonly compositionModel: CompositionModel;\n private readonly workerConfigs: Record<WorkerType, any>;\n private readonly callbacks: VideoClipSessionCallbacks;\n private readonly resourceLoader?: ResourceLoader;\n\n private instructionContext: InstructionContext | null = null;\n private visualWorker: BaseWorker | null = null;\n private decodeWorker: BaseWorker | null = null;\n private videoEncodeWorker: BaseWorker | null = null;\n private videoDemuxWorker: BaseWorker | null = null;\n private audioDecodeWorker: BaseWorker | null = null;\n private visualStream: ReadableStream<VideoFrame> | null = null;\n private visualToEncodeChannel: MessageChannel | null = null;\n private decodeToVisualChannel: MessageChannel | null = null;\n private demuxToDecodeChannel: MessageChannel | null = null;\n private demuxAudioToDecodeChannel: MessageChannel | null = null;\n private isActive = false;\n private isDisposed = false;\n\n static async create(config: VideoClipSessionConfig): Promise<VideoClipSession> {\n return new VideoClipSession(config);\n }\n\n private constructor(config: VideoClipSessionConfig) {\n this.clipId = config.clipId;\n this.sessionId = config.sessionId ?? config.clipId;\n this.forL2Only = config.forL2Only ?? false;\n this.planner = config.planner;\n this.workerPool = config.workerPool;\n this.cacheManager = config.cacheManager;\n this.compositionModel = config.compositionModel;\n this.workerConfigs = config.workerConfigs;\n this.callbacks = config.callbacks;\n this.resourceLoader = config.resourceLoader;\n }\n\n async activate(): Promise<void> {\n if (this.isActive || this.isDisposed) return;\n\n const clip = this.getClip();\n const resource = this.getResource();\n if (!clip || !resource) {\n console.warn('[VideoClipSession] activate: clip or resource not found', this.sessionId);\n return;\n }\n if (resource.type === 'video') {\n await this.setupVideoPipeline(clip, resource);\n } else if (resource.type === 'image') {\n await this.setupImagePipeline(clip);\n } else {\n console.warn(\n '[VideoClipSession] Unknown resource type:',\n resource.type,\n 'for',\n this.sessionId\n );\n }\n\n await this.ensureInstructions(clip);\n\n this.isActive = true;\n\n // Notify that pipeline is ready - triggers resource loading\n const attachmentResources = this.extractAttachmentImageResources();\n if (this.callbacks.onPipelineReady) {\n await this.callbacks.onPipelineReady(attachmentResources);\n }\n }\n\n async deactivate(): Promise<void> {\n if (!this.isActive || this.isDisposed) return;\n this.isActive = false;\n\n await this.releasePipeline();\n }\n\n async dispose(): Promise<void> {\n if (this.isDisposed) return;\n await this.deactivate();\n this.planner.releaseClip(this.clipId);\n this.isDisposed = true;\n }\n\n async handlePlannerUpdate(update: ClipUpdateResult): Promise<void> {\n if (this.isDisposed) {\n return;\n }\n\n if (update.type === 'remove') {\n await this.dispose();\n return;\n }\n\n const instructions = update.instructions;\n if (!instructions) {\n return;\n }\n\n const clip = this.getClip();\n const resource = this.getResource();\n if (!clip || !resource || (resource.type !== 'video' && resource.type !== 'image')) {\n return;\n }\n\n // Any update requires pipeline restart (stream is closed after cache eviction)\n if (this.isActive) {\n await this.releasePipeline();\n this.isActive = false;\n }\n await this.activate();\n }\n\n get visualWorkerHandle(): BaseWorker | null {\n return this.visualWorker;\n }\n\n private getClip(): Clip | null {\n return this.compositionModel?.findClip?.(this.clipId) ?? null;\n }\n\n private getResource() {\n const clip = this.getClip();\n if (!clip || !hasResourceId(clip)) return null;\n return this.compositionModel.getResource(clip.resourceId) ?? null;\n }\n\n private extractAttachmentImageResources(): string[] {\n if (!this.instructionContext?.instructions) return [];\n\n const resourceIdSet = new Set<string>();\n for (const layer of this.instructionContext.instructions.layers) {\n // Only process attachment layers (with attachmentId)\n if (!layer.payload.attachmentId) continue;\n\n if (layer.type === 'image') {\n const imagePayload = layer.payload;\n if (imagePayload.oldResourceId) {\n resourceIdSet.add(imagePayload.oldResourceId);\n }\n const resourceId = imagePayload.resourceId;\n if (resourceId) {\n resourceIdSet.add(resourceId);\n }\n }\n }\n return Array.from(resourceIdSet);\n }\n\n private async ensureInstructions(_clip: Clip): Promise<void> {\n const instructions = this.planner.getInstructions(this.clipId);\n if (!instructions) {\n throw new Error(`No instructions for clip ${this.clipId}`);\n }\n await this.installInstructions(instructions);\n }\n\n private async setupImagePipeline(clip: Clip): Promise<void> {\n await this.acquireWorkers(clip, false);\n await this.connectImagePipeline();\n }\n\n private async connectImagePipeline(): Promise<void> {\n if (!this.visualWorker) {\n throw new Error('Pipeline workers not ready');\n }\n\n // Use sessionId for worker communication\n const sessionId = this.sessionId;\n\n // Connect visualWorker to encodeWorker (only if L2 mode)\n if (this.videoEncodeWorker) {\n this.visualToEncodeChannel = new MessageChannel();\n await this.visualWorker.send(\n 'connect',\n {\n direction: 'downstream',\n port: this.visualToEncodeChannel.port1,\n streamType: 'video',\n sessionId,\n },\n { transfer: [this.visualToEncodeChannel.port1] }\n );\n await this.videoEncodeWorker.send(\n 'connect',\n {\n direction: 'upstream',\n port: this.visualToEncodeChannel.port2,\n streamType: 'video',\n sessionId,\n },\n { transfer: [this.visualToEncodeChannel.port2] }\n );\n }\n\n this.visualWorker.receiveStream((stream) => {\n const visualConfig = this.workerConfigs.videoCompose ?? {};\n const fps = this.compositionModel.fps ?? visualConfig.fps ?? 30;\n this.callbacks.onComposedStreamReady(stream, fps);\n });\n\n // Receive encoded chunks from EncodeWorker (only in L2 mode)\n if (this.videoEncodeWorker) {\n this.videoEncodeWorker.receiveStream((stream, metadata) => {\n this.callbacks.onEncodedStreamReady(\n stream as ReadableStream<{\n chunk: EncodedVideoChunk;\n metadata: EncodedVideoChunkMetadata;\n }>,\n metadata?.streamType ?? 'video'\n );\n });\n }\n }\n\n private async setupVideoPipeline(clip: Clip, _resource: { id: string }): Promise<void> {\n await this.acquireWorkers(clip, true);\n await this.connectVideoPipeline();\n }\n\n private async acquireWorkers(clip: Clip, isVideo: boolean): Promise<void> {\n // Acquire video pipeline workers\n if (isVideo) {\n // VideoDemuxWorker\n this.videoDemuxWorker = await this.workerPool.getOrCreate('videoDemux', this.sessionId, {\n lazy: true,\n });\n const demuxConfig = this.workerConfigs.videoDemux ?? {};\n await this.videoDemuxWorker.send('configure', {\n initial: true,\n config: {\n ...demuxConfig,\n emitAudioToMain: this.forL2Only,\n },\n });\n\n // VideoDecodeWorker\n this.decodeWorker = await this.workerPool.getOrCreate('videoDecode', this.sessionId, {\n lazy: true,\n });\n const decodeConfig = this.workerConfigs.videoDecode ?? {};\n await this.decodeWorker.send('configure', {\n initial: true,\n config: decodeConfig,\n });\n\n // AudioDemuxWorker (reuse VideoDemuxWorker output)\n // Note: We don't create a separate AudioDemuxWorker for video files,\n // VideoDemuxWorker will output both video and audio streams\n\n if (!this.forL2Only) {\n // AudioDecodeWorker\n // Note: No initial configure needed - codec info will be sent via\n // VideoDemuxWorker's configure message after demux ready\n this.audioDecodeWorker = await this.workerPool.getOrCreate('audioDecode', this.sessionId, {\n lazy: true,\n });\n }\n }\n\n // VideoComposeWorker (always needed)\n this.visualWorker = await this.workerPool.getOrCreate('videoCompose', this.sessionId, {\n lazy: true,\n });\n const visualConfig = this.workerConfigs.videoCompose ?? {};\n const renderOverrides = this.compositionModel.renderConfig ?? {};\n const timeline = {\n clipId: clip.id,\n clipStartUs: clip.startUs,\n clipEndUs: clip.startUs + clip.durationUs,\n compositionFps: this.compositionModel.fps ?? visualConfig.fps ?? 30,\n };\n await this.visualWorker.send('configure', {\n initial: true,\n clipId: clip.id,\n config: { ...visualConfig, ...renderOverrides, timeline },\n });\n\n // VideoEncodeWorker (only for L2)\n if (this.forL2Only) {\n this.videoEncodeWorker = await this.workerPool.getOrCreate('videoEncode', this.sessionId, {\n lazy: true,\n });\n const encodeConfig = this.workerConfigs.videoEncode ?? {};\n const encoderConfig = { ...encodeConfig };\n if (this.compositionModel.renderConfig?.width) {\n encoderConfig.width = this.compositionModel.renderConfig.width;\n }\n if (this.compositionModel.renderConfig?.height) {\n encoderConfig.height = this.compositionModel.renderConfig.height;\n }\n await this.videoEncodeWorker.send('configure', {\n initial: true,\n config: encoderConfig,\n });\n }\n }\n\n private async connectVideoPipeline(): Promise<void> {\n if (!this.visualWorker || !this.decodeWorker || !this.videoDemuxWorker) {\n throw new Error('Pipeline workers not ready');\n }\n\n // Use sessionId for worker communication\n const sessionId = this.sessionId;\n\n // Connect visualWorker to encodeWorker (only if L2 mode)\n if (this.videoEncodeWorker) {\n this.visualToEncodeChannel = new MessageChannel();\n await this.visualWorker.send(\n 'connect',\n {\n direction: 'downstream',\n port: this.visualToEncodeChannel.port1,\n streamType: 'video',\n sessionId,\n },\n { transfer: [this.visualToEncodeChannel.port1] }\n );\n await this.videoEncodeWorker.send(\n 'connect',\n {\n direction: 'upstream',\n port: this.visualToEncodeChannel.port2,\n streamType: 'video',\n sessionId,\n },\n { transfer: [this.visualToEncodeChannel.port2] }\n );\n }\n\n this.decodeToVisualChannel = new MessageChannel();\n await this.decodeWorker.send(\n 'connect',\n {\n direction: 'downstream',\n port: this.decodeToVisualChannel.port1,\n streamType: 'video',\n sessionId,\n },\n { transfer: [this.decodeToVisualChannel.port1] }\n );\n await this.visualWorker.send(\n 'connect',\n {\n direction: 'upstream',\n port: this.decodeToVisualChannel.port2,\n streamType: 'video',\n sessionId,\n },\n { transfer: [this.decodeToVisualChannel.port2] }\n );\n\n this.demuxToDecodeChannel = new MessageChannel();\n await this.videoDemuxWorker.send(\n 'connect',\n {\n direction: 'downstream',\n port: this.demuxToDecodeChannel.port1,\n streamType: 'video',\n sessionId,\n },\n { transfer: [this.demuxToDecodeChannel.port1] }\n );\n await this.decodeWorker.send(\n 'connect',\n {\n direction: 'upstream',\n port: this.demuxToDecodeChannel.port2,\n streamType: 'video',\n sessionId,\n },\n { transfer: [this.demuxToDecodeChannel.port2] }\n );\n\n // Setup stream receivers\n this.visualWorker.receiveStream((stream) => {\n const visualConfig = this.workerConfigs.videoCompose ?? {};\n const fps = this.compositionModel.fps ?? visualConfig.fps ?? 30;\n this.callbacks.onComposedStreamReady(stream, fps);\n });\n\n // Receive encoded chunks from EncodeWorker (only in L2 mode)\n if (this.videoEncodeWorker) {\n this.videoEncodeWorker.receiveStream((stream, metadata) => {\n this.callbacks.onEncodedStreamReady(\n stream as ReadableStream<{\n chunk: EncodedVideoChunk;\n metadata: EncodedVideoChunkMetadata;\n }>,\n metadata?.streamType ?? 'video'\n );\n });\n }\n\n // Connect audio pipeline (VideoDemux audio output → AudioDecode)\n if (this.audioDecodeWorker) {\n this.demuxAudioToDecodeChannel = new MessageChannel();\n\n await this.videoDemuxWorker.send(\n 'connect',\n {\n direction: 'downstream',\n port: this.demuxAudioToDecodeChannel.port1,\n streamType: 'audio',\n sessionId,\n },\n { transfer: [this.demuxAudioToDecodeChannel.port1] }\n );\n\n const clip = this.getClip();\n await this.audioDecodeWorker.send(\n 'connect',\n {\n direction: 'upstream',\n port: this.demuxAudioToDecodeChannel.port2,\n streamType: 'audio',\n sessionId,\n clipStartUs: clip?.startUs || 0,\n clipDurationUs: clip?.durationUs || 0,\n },\n { transfer: [this.demuxAudioToDecodeChannel.port2] }\n );\n\n // Receive audio stream from AudioDecodeWorker\n this.audioDecodeWorker.receiveStream((stream) => {\n if (this.callbacks.onAudioStreamReady && clip) {\n this.callbacks.onAudioStreamReady(stream as ReadableStream<AudioData>, {\n sessionId,\n clipStartUs: clip.startUs,\n clipDurationUs: clip.durationUs,\n });\n }\n });\n }\n\n // In L2-only mode, receive encoded audio directly from demux and write to L2\n if (this.forL2Only && this.videoDemuxWorker) {\n this.videoDemuxWorker.receiveStream((stream, metadata) => {\n if (metadata?.streamType === 'audio') {\n this.cacheManager\n .receiveEncodedChunks(stream as any, this.clipId, 'audio', {\n onError: (err) => {\n console.error('[VideoClipSession] L2 audio write error:', err);\n },\n })\n .catch((err) => {\n console.error('[VideoClipSession] L2 audio stream error:', err);\n });\n }\n });\n }\n }\n\n private async installInstructions(instructions: ClipInstructionSet): Promise<void> {\n this.instructionContext = {\n revision: instructions.revision,\n instructions,\n status: instructions.status,\n };\n\n if (this.visualWorker) {\n await this.visualWorker.send('install_instructions', instructions);\n }\n }\n\n private async releasePipeline(): Promise<void> {\n if (!this.forL2Only) {\n const clip = this.getClip();\n if (clip && this.resourceLoader) {\n if (clip.oldResourceId) {\n this.resourceLoader.cancel(clip.oldResourceId);\n }\n if (hasResourceId(clip)) {\n this.resourceLoader.cancel(clip.resourceId);\n }\n\n const attachmentResources = this.extractAttachmentImageResources();\n for (const resourceId of attachmentResources) {\n this.resourceLoader.cancel(resourceId);\n }\n }\n }\n\n if (this.visualWorker && this.instructionContext) {\n await this.visualWorker.notify('dispose_clip', {\n sessionId: this.sessionId,\n revision: this.instructionContext.revision,\n });\n }\n\n if (this.visualStream && this.callbacks.onStreamDisposed) {\n this.callbacks.onStreamDisposed();\n }\n this.visualStream = null;\n\n // Terminate clip-local workers\n if (this.videoDemuxWorker) {\n this.workerPool.terminate('videoDemux', this.sessionId);\n this.videoDemuxWorker = null;\n }\n\n if (this.decodeWorker) {\n this.workerPool.terminate('videoDecode', this.sessionId);\n this.decodeWorker = null;\n }\n\n if (this.visualWorker) {\n this.workerPool.terminate('videoCompose', this.sessionId);\n this.visualWorker = null;\n }\n\n if (this.videoEncodeWorker) {\n this.workerPool.terminate('videoEncode', this.sessionId);\n this.videoEncodeWorker = null;\n }\n\n if (this.audioDecodeWorker) {\n this.workerPool.terminate('audioDecode', this.sessionId);\n this.audioDecodeWorker = null;\n }\n\n this.visualToEncodeChannel?.port1.close();\n this.visualToEncodeChannel?.port2.close();\n this.decodeToVisualChannel?.port1.close();\n this.decodeToVisualChannel?.port2.close();\n this.demuxToDecodeChannel?.port1.close();\n this.demuxToDecodeChannel?.port2.close();\n this.demuxAudioToDecodeChannel?.port1.close();\n this.demuxAudioToDecodeChannel?.port2.close();\n\n this.visualToEncodeChannel = null;\n this.decodeToVisualChannel = null;\n this.demuxToDecodeChannel = null;\n this.demuxAudioToDecodeChannel = null;\n }\n\n async invalidateClipCache(): Promise<void> {\n await this.cacheManager.invalidateClip(this.clipId);\n }\n}\n"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"VideoClipSession.js","sources":["../../src/orchestrator/VideoClipSession.ts"],"sourcesContent":["import type { WorkerPool } from '../worker/WorkerPool';\nimport type { CacheManager } from '../cache/CacheManager';\nimport type { CompositionModel, Clip } from '../model';\nimport type { CompositionPlanner, ClipUpdateResult } from './CompositionPlanner';\nimport type { ClipInstructionSet } from '../stages/compose/instructions';\nimport type { BaseWorker } from '../worker/BaseWorker';\nimport type { WorkerType } from '../worker/types';\nimport type { ResourceLoader } from '../stages/load/ResourceLoader';\nimport { hasResourceId } from '../model/types';\n\ninterface VideoClipSessionCallbacks {\n onComposedStreamReady(stream: ReadableStream<VideoFrame>, fps: number): Promise<void>;\n onEncodedStreamReady(\n stream: ReadableStream<{ chunk: EncodedVideoChunk; metadata: EncodedVideoChunkMetadata }>,\n track: 'video' | 'audio'\n ): Promise<void>;\n onAudioStreamReady?(\n stream: ReadableStream<AudioData>,\n metadata: {\n sessionId: string;\n clipStartUs: number;\n clipDurationUs: number;\n }\n ): void;\n onStreamDisposed?(): void;\n onPipelineReady?(attachmentResourceIds?: string[]): Promise<void>;\n}\n\ninterface VideoClipSessionConfig {\n clipId: string;\n sessionId?: string;\n forL2Only?: boolean;\n planner: CompositionPlanner;\n workerPool: WorkerPool;\n cacheManager: CacheManager;\n compositionModel: CompositionModel;\n workerConfigs: Record<WorkerType, any>;\n callbacks: VideoClipSessionCallbacks;\n resourceLoader?: ResourceLoader;\n}\n\ninterface InstructionContext {\n revision: number;\n instructions: ClipInstructionSet;\n status: ClipInstructionSet['status'];\n}\n\nexport class VideoClipSession {\n private readonly clipId: string;\n private readonly sessionId: string;\n private readonly forL2Only: boolean;\n private readonly planner: CompositionPlanner;\n private readonly workerPool: WorkerPool;\n private readonly cacheManager: CacheManager;\n private readonly compositionModel: CompositionModel;\n private readonly workerConfigs: Record<WorkerType, any>;\n private readonly callbacks: VideoClipSessionCallbacks;\n private readonly resourceLoader?: ResourceLoader;\n\n private instructionContext: InstructionContext | null = null;\n composeWorker: BaseWorker | null = null;\n private decodeWorker: BaseWorker | null = null;\n private videoEncodeWorker: BaseWorker | null = null;\n private videoDemuxWorker: BaseWorker | null = null;\n private audioDecodeWorker: BaseWorker | null = null;\n private visualStream: ReadableStream<VideoFrame> | null = null;\n private composeToEncodeChannel: MessageChannel | null = null;\n private decodeToVisualChannel: MessageChannel | null = null;\n private demuxToDecodeChannel: MessageChannel | null = null;\n private demuxAudioToDecodeChannel: MessageChannel | null = null;\n private isActive = false;\n private isDisposed = false;\n\n static async create(config: VideoClipSessionConfig): Promise<VideoClipSession> {\n return new VideoClipSession(config);\n }\n\n private constructor(config: VideoClipSessionConfig) {\n this.clipId = config.clipId;\n this.sessionId = config.sessionId ?? config.clipId;\n this.forL2Only = config.forL2Only ?? false;\n this.planner = config.planner;\n this.workerPool = config.workerPool;\n this.cacheManager = config.cacheManager;\n this.compositionModel = config.compositionModel;\n this.workerConfigs = config.workerConfigs;\n this.callbacks = config.callbacks;\n this.resourceLoader = config.resourceLoader;\n }\n\n async activate(): Promise<void> {\n if (this.isActive || this.isDisposed) return;\n\n const clip = this.getClip();\n const resource = this.getResource();\n if (!clip || !resource) {\n console.warn('[VideoClipSession] activate: clip or resource not found', this.sessionId);\n return;\n }\n if (resource.type === 'video') {\n await this.setupVideoPipeline(clip, resource);\n } else if (resource.type === 'image') {\n await this.setupImagePipeline(clip);\n } else {\n console.warn(\n '[VideoClipSession] Unknown resource type:',\n resource.type,\n 'for',\n this.sessionId\n );\n }\n\n await this.ensureInstructions(clip);\n\n this.isActive = true;\n\n // Notify that pipeline is ready - triggers resource loading\n const attachmentResources = this.extractAttachmentImageResources();\n if (this.callbacks.onPipelineReady) {\n await this.callbacks.onPipelineReady(attachmentResources);\n }\n }\n\n async deactivate(): Promise<void> {\n if (!this.isActive || this.isDisposed) return;\n this.isActive = false;\n\n await this.releasePipeline();\n }\n\n async dispose(): Promise<void> {\n if (this.isDisposed) return;\n await this.deactivate();\n this.planner.releaseClip(this.clipId);\n this.isDisposed = true;\n }\n\n async handlePlannerUpdate(update: ClipUpdateResult): Promise<void> {\n if (this.isDisposed) {\n return;\n }\n\n if (update.type === 'remove') {\n await this.dispose();\n return;\n }\n\n const instructions = update.instructions;\n if (!instructions) {\n return;\n }\n\n const clip = this.getClip();\n const resource = this.getResource();\n if (!clip || !resource || (resource.type !== 'video' && resource.type !== 'image')) {\n return;\n }\n\n // Any update requires pipeline restart (stream is closed after cache eviction)\n if (this.isActive) {\n await this.releasePipeline();\n this.isActive = false;\n }\n await this.activate();\n }\n\n private getClip(): Clip | null {\n return this.compositionModel?.findClip?.(this.clipId) ?? null;\n }\n\n private getResource() {\n const clip = this.getClip();\n if (!clip || !hasResourceId(clip)) return null;\n return this.compositionModel.getResource(clip.resourceId) ?? null;\n }\n\n private extractAttachmentImageResources(): string[] {\n if (!this.instructionContext?.instructions) return [];\n\n const resourceIdSet = new Set<string>();\n for (const layer of this.instructionContext.instructions.layers) {\n // Only process attachment layers (with attachmentId)\n if (!layer.payload.attachmentId) continue;\n\n if (layer.type === 'image') {\n const imagePayload = layer.payload;\n if (imagePayload.oldResourceId) {\n resourceIdSet.add(imagePayload.oldResourceId);\n }\n const resourceId = imagePayload.resourceId;\n if (resourceId) {\n resourceIdSet.add(resourceId);\n }\n }\n }\n return Array.from(resourceIdSet);\n }\n\n private async ensureInstructions(_clip: Clip): Promise<void> {\n const instructions = this.planner.getInstructions(this.clipId);\n if (!instructions) {\n throw new Error(`No instructions for clip ${this.clipId}`);\n }\n await this.installInstructions(instructions);\n }\n\n private async setupImagePipeline(clip: Clip): Promise<void> {\n await this.acquireWorkers(clip, false);\n await this.connectImagePipeline();\n }\n\n private async connectImagePipeline(): Promise<void> {\n if (!this.composeWorker) {\n throw new Error('Pipeline workers not ready');\n }\n\n // Use sessionId for worker communication\n const sessionId = this.sessionId;\n\n // Connect visualWorker to encodeWorker (only if L2 mode)\n if (this.videoEncodeWorker) {\n this.composeToEncodeChannel = new MessageChannel();\n await this.composeWorker.send(\n 'connect',\n {\n direction: 'downstream',\n port: this.composeToEncodeChannel.port1,\n streamType: 'video',\n sessionId,\n },\n { transfer: [this.composeToEncodeChannel.port1] }\n );\n await this.videoEncodeWorker.send(\n 'connect',\n {\n direction: 'upstream',\n port: this.composeToEncodeChannel.port2,\n streamType: 'video',\n sessionId,\n },\n { transfer: [this.composeToEncodeChannel.port2] }\n );\n }\n\n this.composeWorker.receiveStream(async (stream) => {\n const visualConfig = this.workerConfigs.videoCompose ?? {};\n const fps = this.compositionModel.fps ?? visualConfig.fps ?? 30;\n await this.callbacks.onComposedStreamReady(stream, fps);\n });\n\n // Receive encoded chunks from EncodeWorker (only in L2 mode)\n if (this.videoEncodeWorker) {\n this.videoEncodeWorker.receiveStream((stream, metadata) => {\n this.callbacks.onEncodedStreamReady(\n stream as ReadableStream<{\n chunk: EncodedVideoChunk;\n metadata: EncodedVideoChunkMetadata;\n }>,\n metadata?.streamType ?? 'video'\n );\n });\n }\n }\n\n private async setupVideoPipeline(clip: Clip, _resource: { id: string }): Promise<void> {\n await this.acquireWorkers(clip, true);\n await this.connectVideoPipeline();\n }\n\n private async acquireWorkers(clip: Clip, isVideo: boolean): Promise<void> {\n // Acquire video pipeline workers\n if (isVideo) {\n // VideoDemuxWorker\n this.videoDemuxWorker = await this.workerPool.getOrCreate('videoDemux', this.sessionId, {\n lazy: true,\n });\n const demuxConfig = this.workerConfigs.videoDemux ?? {};\n await this.videoDemuxWorker.send('configure', {\n initial: true,\n config: {\n ...demuxConfig,\n emitAudioToMain: this.forL2Only,\n },\n });\n\n // VideoDecodeWorker\n this.decodeWorker = await this.workerPool.getOrCreate('videoDecode', this.sessionId, {\n lazy: true,\n });\n const decodeConfig = this.workerConfigs.videoDecode ?? {};\n await this.decodeWorker.send('configure', {\n initial: true,\n config: decodeConfig,\n });\n\n // AudioDemuxWorker (reuse VideoDemuxWorker output)\n // Note: We don't create a separate AudioDemuxWorker for video files,\n // VideoDemuxWorker will output both video and audio streams\n\n if (!this.forL2Only) {\n // AudioDecodeWorker\n // Note: No initial configure needed - codec info will be sent via\n // VideoDemuxWorker's configure message after demux ready\n this.audioDecodeWorker = await this.workerPool.getOrCreate('audioDecode', this.sessionId, {\n lazy: true,\n });\n }\n }\n\n // VideoComposeWorker (always needed)\n this.composeWorker = await this.workerPool.getOrCreate('videoCompose', this.sessionId, {\n lazy: true,\n });\n const visualConfig = this.workerConfigs.videoCompose ?? {};\n const renderOverrides = this.compositionModel.renderConfig ?? {};\n const timeline = {\n clipId: clip.id,\n clipStartUs: clip.startUs,\n clipEndUs: clip.startUs + clip.durationUs,\n compositionFps: this.compositionModel.fps ?? visualConfig.fps ?? 30,\n };\n await this.composeWorker.send('configure', {\n initial: true,\n clipId: clip.id,\n config: { ...visualConfig, ...renderOverrides, timeline },\n });\n\n // VideoEncodeWorker (only for L2)\n if (this.forL2Only) {\n this.videoEncodeWorker = await this.workerPool.getOrCreate('videoEncode', this.sessionId, {\n lazy: true,\n });\n const encodeConfig = this.workerConfigs.videoEncode ?? {};\n const encoderConfig = { ...encodeConfig };\n if (this.compositionModel.renderConfig?.width) {\n encoderConfig.width = this.compositionModel.renderConfig.width;\n }\n if (this.compositionModel.renderConfig?.height) {\n encoderConfig.height = this.compositionModel.renderConfig.height;\n }\n await this.videoEncodeWorker.send('configure', {\n initial: true,\n config: encoderConfig,\n });\n }\n }\n\n private async connectVideoPipeline(): Promise<void> {\n if (!this.composeWorker || !this.decodeWorker || !this.videoDemuxWorker) {\n throw new Error('Pipeline workers not ready');\n }\n\n // Use sessionId for worker communication\n const sessionId = this.sessionId;\n\n // Connect visualWorker to encodeWorker (only if L2 mode)\n if (this.videoEncodeWorker) {\n this.composeToEncodeChannel = new MessageChannel();\n await this.composeWorker.send(\n 'connect',\n {\n direction: 'downstream',\n port: this.composeToEncodeChannel.port1,\n streamType: 'video',\n sessionId,\n },\n { transfer: [this.composeToEncodeChannel.port1] }\n );\n await this.videoEncodeWorker.send(\n 'connect',\n {\n direction: 'upstream',\n port: this.composeToEncodeChannel.port2,\n streamType: 'video',\n sessionId,\n },\n { transfer: [this.composeToEncodeChannel.port2] }\n );\n }\n\n this.decodeToVisualChannel = new MessageChannel();\n await this.decodeWorker.send(\n 'connect',\n {\n direction: 'downstream',\n port: this.decodeToVisualChannel.port1,\n streamType: 'video',\n sessionId,\n },\n { transfer: [this.decodeToVisualChannel.port1] }\n );\n await this.composeWorker.send(\n 'connect',\n {\n direction: 'upstream',\n port: this.decodeToVisualChannel.port2,\n streamType: 'video',\n sessionId,\n },\n { transfer: [this.decodeToVisualChannel.port2] }\n );\n\n this.demuxToDecodeChannel = new MessageChannel();\n await this.videoDemuxWorker.send(\n 'connect',\n {\n direction: 'downstream',\n port: this.demuxToDecodeChannel.port1,\n streamType: 'video',\n sessionId,\n },\n { transfer: [this.demuxToDecodeChannel.port1] }\n );\n await this.decodeWorker.send(\n 'connect',\n {\n direction: 'upstream',\n port: this.demuxToDecodeChannel.port2,\n streamType: 'video',\n sessionId,\n },\n { transfer: [this.demuxToDecodeChannel.port2] }\n );\n\n // Setup stream receivers\n this.composeWorker.receiveStream(async (stream) => {\n const visualConfig = this.workerConfigs.videoCompose ?? {};\n const fps = this.compositionModel.fps ?? visualConfig.fps ?? 30;\n await this.callbacks.onComposedStreamReady(stream, fps);\n });\n\n // Receive encoded chunks from EncodeWorker (only in L2 mode)\n if (this.videoEncodeWorker) {\n this.videoEncodeWorker.receiveStream((stream, metadata) => {\n this.callbacks.onEncodedStreamReady(\n stream as ReadableStream<{\n chunk: EncodedVideoChunk;\n metadata: EncodedVideoChunkMetadata;\n }>,\n metadata?.streamType ?? 'video'\n );\n });\n }\n\n // Connect audio pipeline (VideoDemux audio output → AudioDecode)\n if (this.audioDecodeWorker) {\n this.demuxAudioToDecodeChannel = new MessageChannel();\n\n await this.videoDemuxWorker.send(\n 'connect',\n {\n direction: 'downstream',\n port: this.demuxAudioToDecodeChannel.port1,\n streamType: 'audio',\n sessionId,\n },\n { transfer: [this.demuxAudioToDecodeChannel.port1] }\n );\n\n const clip = this.getClip();\n await this.audioDecodeWorker.send(\n 'connect',\n {\n direction: 'upstream',\n port: this.demuxAudioToDecodeChannel.port2,\n streamType: 'audio',\n sessionId,\n clipStartUs: clip?.startUs || 0,\n clipDurationUs: clip?.durationUs || 0,\n },\n { transfer: [this.demuxAudioToDecodeChannel.port2] }\n );\n\n // Receive audio stream from AudioDecodeWorker\n this.audioDecodeWorker.receiveStream((stream) => {\n if (this.callbacks.onAudioStreamReady && clip) {\n this.callbacks.onAudioStreamReady(stream as ReadableStream<AudioData>, {\n sessionId,\n clipStartUs: clip.startUs,\n clipDurationUs: clip.durationUs,\n });\n }\n });\n }\n\n // In L2-only mode, receive encoded audio directly from demux and write to L2\n if (this.forL2Only && this.videoDemuxWorker) {\n this.videoDemuxWorker.receiveStream((stream, metadata) => {\n if (metadata?.streamType === 'audio') {\n this.cacheManager\n .receiveEncodedChunks(stream as any, this.clipId, 'audio', {\n onError: (err) => {\n console.error('[VideoClipSession] L2 audio write error:', err);\n },\n })\n .catch((err) => {\n console.error('[VideoClipSession] L2 audio stream error:', err);\n });\n }\n });\n }\n }\n\n private async installInstructions(instructions: ClipInstructionSet): Promise<void> {\n this.instructionContext = {\n revision: instructions.revision,\n instructions,\n status: instructions.status,\n };\n\n if (this.composeWorker) {\n await this.composeWorker.send('install_instructions', instructions);\n }\n }\n\n private async releasePipeline(): Promise<void> {\n if (!this.forL2Only) {\n const clip = this.getClip();\n if (clip && this.resourceLoader) {\n if (clip.oldResourceId) {\n this.resourceLoader.cancel(clip.oldResourceId);\n }\n if (hasResourceId(clip)) {\n this.resourceLoader.cancel(clip.resourceId);\n }\n\n const attachmentResources = this.extractAttachmentImageResources();\n for (const resourceId of attachmentResources) {\n this.resourceLoader.cancel(resourceId);\n }\n }\n }\n\n if (this.composeWorker && this.instructionContext) {\n await this.composeWorker.notify('dispose_clip', {\n sessionId: this.sessionId,\n revision: this.instructionContext.revision,\n });\n }\n\n if (this.visualStream && this.callbacks.onStreamDisposed) {\n this.callbacks.onStreamDisposed();\n }\n this.visualStream = null;\n\n // Terminate clip-local workers\n if (this.videoDemuxWorker) {\n this.workerPool.terminate('videoDemux', this.sessionId);\n this.videoDemuxWorker = null;\n }\n\n if (this.decodeWorker) {\n this.workerPool.terminate('videoDecode', this.sessionId);\n this.decodeWorker = null;\n }\n\n if (this.composeWorker) {\n this.workerPool.terminate('videoCompose', this.sessionId);\n this.composeWorker = null;\n }\n\n if (this.videoEncodeWorker) {\n this.workerPool.terminate('videoEncode', this.sessionId);\n this.videoEncodeWorker = null;\n }\n\n if (this.audioDecodeWorker) {\n this.workerPool.terminate('audioDecode', this.sessionId);\n this.audioDecodeWorker = null;\n }\n\n this.composeToEncodeChannel?.port1.close();\n this.composeToEncodeChannel?.port2.close();\n this.decodeToVisualChannel?.port1.close();\n this.decodeToVisualChannel?.port2.close();\n this.demuxToDecodeChannel?.port1.close();\n this.demuxToDecodeChannel?.port2.close();\n this.demuxAudioToDecodeChannel?.port1.close();\n this.demuxAudioToDecodeChannel?.port2.close();\n\n this.composeToEncodeChannel = null;\n this.decodeToVisualChannel = null;\n this.demuxToDecodeChannel = null;\n this.demuxAudioToDecodeChannel = null;\n }\n\n async invalidateClipCache(): Promise<void> {\n await this.cacheManager.invalidateClip(this.clipId);\n }\n}\n"],"names":[],"mappings":"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"} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"LayerRenderer.d.ts","sourceRoot":"","sources":["../../../src/stages/compose/LayerRenderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAA8D,MAAM,SAAS,CAAC;AAMjG;;;GAGG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,GAAG,CAAoC;IAC/C,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,GAAG,CAAc;gBAGvB,GAAG,EAAE,iCAAiC,EACtC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,GAAG,GAAE,MAAW;IASlB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAIpC,OAAO,CAAC,0BAA0B;IAKlC;;OAEG;IACG,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC;IA0C9C,OAAO,CAAC,cAAc;IAqBtB,OAAO,CAAC,kBAAkB;IA+C1B,OAAO,CAAC,cAAc;YA2BR,gBAAgB;YAuChB,gBAAgB;YAiFhB,eAAe;IAuC7B,OAAO,CAAC,SAAS;IAkBjB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;CAKtD"} | ||
| {"version":3,"file":"LayerRenderer.d.ts","sourceRoot":"","sources":["../../../src/stages/compose/LayerRenderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAA8D,MAAM,SAAS,CAAC;AAMjG;;;GAGG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,GAAG,CAAoC;IAC/C,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,GAAG,CAAc;gBAGvB,GAAG,EAAE,iCAAiC,EACtC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,GAAG,GAAE,MAAW;IASlB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAIpC,OAAO,CAAC,0BAA0B;IAKlC;;OAEG;IACG,WAAW,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC;IA0C9C,OAAO,CAAC,cAAc;IAqBtB,OAAO,CAAC,kBAAkB;IA+C1B,OAAO,CAAC,cAAc;YA2BR,gBAAgB;YAwChB,gBAAgB;YAiFhB,eAAe;IAuC7B,OAAO,CAAC,SAAS;IAkBjB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;CAKtD"} |
@@ -1,3 +0,1 @@ | ||
| import { ClipInstructionStatus } from './instructions'; | ||
| export interface AudioComposeConfig { | ||
@@ -270,10 +268,4 @@ sampleRate: number; | ||
| export interface ComposeResult { | ||
| frame: VideoFrame | ImageData; | ||
| frame: VideoFrame; | ||
| timeUs: number; | ||
| metadata?: { | ||
| layerCount: number; | ||
| renderTime: number; | ||
| gpuAccelerated: boolean; | ||
| } & Record<string, unknown>; | ||
| instructionStatus?: ClipInstructionStatus; | ||
| } | ||
@@ -280,0 +272,0 @@ export interface VideoComposeMessage { |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/stages/compose/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAC5D,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC;IACnB,MAAM,CAAC,EAAE,YAAY,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;CAC/C;AAED,KAAK,MAAM,GAAG,MAAM,CAAC;AAErB,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,OAAO,CAAC,EAAE,UAAU,CAAC;IACrB,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,KAAK,GAAG,OAAO,GAAG,KAAK,GAAG,OAAO,CAAC;IACxC,MAAM,EAAE,gBAAgB,CAAC;CAC1B;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,QAAQ,GAAG,aAAa,GAAG,aAAa,GAAG,QAAQ,CAAC;CAC7D;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,IAAI,GAAG,QAAQ,GAAG,OAAO,GAAG,YAAY,GAAG,SAAS,CAAC;IAC3D,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC7B;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,KAAK,CAAC;QACZ,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,SAAS,CAAC;QACrB,MAAM,EAAE,gBAAgB,CAAC;QACzB,IAAI,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;QACzB,UAAU,EAAE,MAAM,CAAC;QACnB,gBAAgB,EAAE,MAAM,CAAC;QACzB,eAAe,CAAC,EAAE,YAAY,CAAC;KAChC,CAAC,CAAC;IACH,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,SAAS,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,WAAW,GAAG,KAAK,GAAG,UAAU,GAAG,aAAa,GAAG,aAAa,GAAG,cAAc,GAAG,OAAO,CAAC;IAClG,OAAO,EAAE,GAAG,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EACA,YAAY,GACZ,OAAO,GACP,YAAY,GACZ,cAAc,GACd,cAAc,GACd,gBAAgB,GAChB,OAAO,GACP,OAAO,CAAC;IACZ,OAAO,EAAE,GAAG,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,KAAK,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC,CAAC;IACH,aAAa,EAAE,KAAK,CAAC;QACnB,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;KACf,CAAC,CAAC;IACH,UAAU,EAAE,YAAY,CAAC;IACzB,SAAS,EAAE,YAAY,CAAC;CACzB;AAED,MAAM,WAAW,iBAAiB;IAChC,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;CACjB;AAID,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,sBAAsB,CAAC;IAClC,QAAQ,CAAC,EAAE,UAAU,GAAG,OAAO,GAAG,QAAQ,CAAC;IAC3C,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChD;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,wBAAwB,CAAC;IACrC,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,WAAW;IAC1B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,OAAO,GAAG,WAAW,GAAG,OAAO,CAAC;IACtC,MAAM,CAAC,EAAE,WAAW,GAAG,UAAU,CAAC;IAClC,KAAK,CAAC,EAAE,QAAQ,GAAG,WAAW,GAAG,SAAS,CAAC;IAC3C,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACzC,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EACA,MAAM,GACN,YAAY,GACZ,UAAU,GACV,WAAW,GACX,YAAY,GACZ,UAAU,GACV,OAAO,GACP,QAAQ,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,SAAU,SAAQ,KAAK;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;IACxC,aAAa,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC5C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE;QACP,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE;QACX,SAAS,EAAE;YACT,QAAQ,EAAE,MAAM,CAAC;YACjB,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC;YAC5B,UAAU,EAAE,MAAM,CAAC;YACnB,IAAI,EAAE,MAAM,CAAC;YACb,MAAM,CAAC,EAAE,MAAM,CAAC;YAChB,WAAW,CAAC,EAAE,MAAM,CAAC;YACrB,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;YAChC,UAAU,CAAC,EAAE,MAAM,CAAC;YACpB,UAAU,CAAC,EAAE,MAAM,CAAC;YACpB,cAAc,CAAC,EAAE,cAAc,CAAC;YAChC,aAAa,CAAC,EAAE,aAAa,CAAC;SAC/B,CAAC;QACF,cAAc,CAAC,EAAE;YACf,KAAK,CAAC,EAAE,MAAM,CAAC;YACf,eAAe,CAAC,EAAE,MAAM,CAAC;YACzB,OAAO,CAAC,EAAE,MAAM,CAAC;YACjB,YAAY,CAAC,EAAE,MAAM,CAAC;SACvB,CAAC;QACF,cAAc,CAAC,EAAE;YACf,QAAQ,CAAC,EAAE,UAAU,CAAC;YACtB,GAAG,CAAC,EAAE,MAAM,CAAC;YACb,MAAM,CAAC,EAAE,MAAM,CAAC;YAChB,IAAI,CAAC,EAAE,MAAM,CAAC;YACd,KAAK,CAAC,EAAE,MAAM,CAAC;YACf,OAAO,CAAC,EAAE,MAAM,CAAC;YACjB,UAAU,CAAC,EAAE,MAAM,CAAC;YACpB,cAAc,CAAC,EAAE,MAAM,CAAC;SACzB,CAAC;KACH,CAAC;IACF,WAAW,CAAC,EAAE,KAAK,CAAC;QAClB,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;KACf,CAAC,CAAC;IACH,UAAU,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC;CACzC;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EACA,MAAM,GACN,MAAM,GACN,OAAO,GACP,YAAY,GACZ,MAAM,GACN,QAAQ,GACR,YAAY,GACZ,cAAc,GACd,iBAAiB,GACjB,mBAAmB,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,UAAU,GAAG,aAAa,CAAC;IAC3D,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kBAAkB,CAAC,EAAE;QACnB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AAED,MAAM,WAAW,UAAW,SAAQ,KAAK;IACvC,IAAI,EAAE,OAAO,CAAC;IACd,UAAU,EAAE,UAAU,CAAC;IACvB,IAAI,CAAC,EAAE;QACL,CAAC,EAAE,MAAM,CAAC;QACV,CAAC,EAAE,MAAM,CAAC;QACV,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,MAAM,WAAW,UAAW,SAAQ,KAAK;IACvC,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,IAAI,CAAC;IACvC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE;QACL,CAAC,EAAE,MAAM,CAAC;QACV,CAAC,EAAE,MAAM,CAAC;QACV,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,GAAG,QAAQ,CAAC;IACrF,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,GAAG,KAAK,CAAC;IAC5D,MAAM,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,UAAU,GAAG,aAAa,CAAC;IAC3D,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC;CAC3C;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,UAAU,GAAG,SAAS,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE;QACT,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,cAAc,EAAE,OAAO,CAAC;KACzB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5B,iBAAiB,CAAC,EAAE,qBAAqB,CAAC;CAC3C;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EACA,WAAW,GACX,SAAS,GACT,UAAU,GACV,aAAa,GACb,aAAa,GACb,iBAAiB,GACjB,OAAO,GACP,SAAS,CAAC;IACd,OAAO,EAAE,GAAG,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EACA,YAAY,GACZ,UAAU,GACV,YAAY,GACZ,cAAc,GACd,cAAc,GACd,mBAAmB,GACnB,OAAO,GACP,UAAU,GACV,OAAO,CAAC;IACZ,OAAO,EAAE,GAAG,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,iBAAiB;IAChC,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;CAC1B"} | ||
| {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/stages/compose/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC;IACnB,MAAM,CAAC,EAAE,YAAY,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;CAC/C;AAED,KAAK,MAAM,GAAG,MAAM,CAAC;AAErB,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,OAAO,CAAC,EAAE,UAAU,CAAC;IACrB,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,KAAK,GAAG,OAAO,GAAG,KAAK,GAAG,OAAO,CAAC;IACxC,MAAM,EAAE,gBAAgB,CAAC;CAC1B;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,QAAQ,GAAG,aAAa,GAAG,aAAa,GAAG,QAAQ,CAAC;CAC7D;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,IAAI,GAAG,QAAQ,GAAG,OAAO,GAAG,YAAY,GAAG,SAAS,CAAC;IAC3D,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC7B;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,KAAK,CAAC;QACZ,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,SAAS,CAAC;QACrB,MAAM,EAAE,gBAAgB,CAAC;QACzB,IAAI,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;QACzB,UAAU,EAAE,MAAM,CAAC;QACnB,gBAAgB,EAAE,MAAM,CAAC;QACzB,eAAe,CAAC,EAAE,YAAY,CAAC;KAChC,CAAC,CAAC;IACH,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,SAAS,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,WAAW,GAAG,KAAK,GAAG,UAAU,GAAG,aAAa,GAAG,aAAa,GAAG,cAAc,GAAG,OAAO,CAAC;IAClG,OAAO,EAAE,GAAG,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EACA,YAAY,GACZ,OAAO,GACP,YAAY,GACZ,cAAc,GACd,cAAc,GACd,gBAAgB,GAChB,OAAO,GACP,OAAO,CAAC;IACZ,OAAO,EAAE,GAAG,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,KAAK,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC,CAAC;IACH,aAAa,EAAE,KAAK,CAAC;QACnB,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;KACf,CAAC,CAAC;IACH,UAAU,EAAE,YAAY,CAAC;IACzB,SAAS,EAAE,YAAY,CAAC;CACzB;AAED,MAAM,WAAW,iBAAiB;IAChC,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;CACjB;AAID,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,sBAAsB,CAAC;IAClC,QAAQ,CAAC,EAAE,UAAU,GAAG,OAAO,GAAG,QAAQ,CAAC;IAC3C,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChD;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,wBAAwB,CAAC;IACrC,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,WAAW;IAC1B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,OAAO,GAAG,WAAW,GAAG,OAAO,CAAC;IACtC,MAAM,CAAC,EAAE,WAAW,GAAG,UAAU,CAAC;IAClC,KAAK,CAAC,EAAE,QAAQ,GAAG,WAAW,GAAG,SAAS,CAAC;IAC3C,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACzC,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EACA,MAAM,GACN,YAAY,GACZ,UAAU,GACV,WAAW,GACX,YAAY,GACZ,UAAU,GACV,OAAO,GACP,QAAQ,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,SAAU,SAAQ,KAAK;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;IACxC,aAAa,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC5C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE;QACP,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE;QACX,SAAS,EAAE;YACT,QAAQ,EAAE,MAAM,CAAC;YACjB,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC;YAC5B,UAAU,EAAE,MAAM,CAAC;YACnB,IAAI,EAAE,MAAM,CAAC;YACb,MAAM,CAAC,EAAE,MAAM,CAAC;YAChB,WAAW,CAAC,EAAE,MAAM,CAAC;YACrB,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;YAChC,UAAU,CAAC,EAAE,MAAM,CAAC;YACpB,UAAU,CAAC,EAAE,MAAM,CAAC;YACpB,cAAc,CAAC,EAAE,cAAc,CAAC;YAChC,aAAa,CAAC,EAAE,aAAa,CAAC;SAC/B,CAAC;QACF,cAAc,CAAC,EAAE;YACf,KAAK,CAAC,EAAE,MAAM,CAAC;YACf,eAAe,CAAC,EAAE,MAAM,CAAC;YACzB,OAAO,CAAC,EAAE,MAAM,CAAC;YACjB,YAAY,CAAC,EAAE,MAAM,CAAC;SACvB,CAAC;QACF,cAAc,CAAC,EAAE;YACf,QAAQ,CAAC,EAAE,UAAU,CAAC;YACtB,GAAG,CAAC,EAAE,MAAM,CAAC;YACb,MAAM,CAAC,EAAE,MAAM,CAAC;YAChB,IAAI,CAAC,EAAE,MAAM,CAAC;YACd,KAAK,CAAC,EAAE,MAAM,CAAC;YACf,OAAO,CAAC,EAAE,MAAM,CAAC;YACjB,UAAU,CAAC,EAAE,MAAM,CAAC;YACpB,cAAc,CAAC,EAAE,MAAM,CAAC;SACzB,CAAC;KACH,CAAC;IACF,WAAW,CAAC,EAAE,KAAK,CAAC;QAClB,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;KACf,CAAC,CAAC;IACH,UAAU,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC;CACzC;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EACA,MAAM,GACN,MAAM,GACN,OAAO,GACP,YAAY,GACZ,MAAM,GACN,QAAQ,GACR,YAAY,GACZ,cAAc,GACd,iBAAiB,GACjB,mBAAmB,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,UAAU,GAAG,aAAa,CAAC;IAC3D,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kBAAkB,CAAC,EAAE;QACnB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AAED,MAAM,WAAW,UAAW,SAAQ,KAAK;IACvC,IAAI,EAAE,OAAO,CAAC;IACd,UAAU,EAAE,UAAU,CAAC;IACvB,IAAI,CAAC,EAAE;QACL,CAAC,EAAE,MAAM,CAAC;QACV,CAAC,EAAE,MAAM,CAAC;QACV,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,MAAM,WAAW,UAAW,SAAQ,KAAK;IACvC,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,IAAI,CAAC;IACvC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE;QACL,CAAC,EAAE,MAAM,CAAC;QACV,CAAC,EAAE,MAAM,CAAC;QACV,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,GAAG,QAAQ,CAAC;IACrF,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,GAAG,KAAK,CAAC;IAC5D,MAAM,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,UAAU,GAAG,aAAa,CAAC;IAC3D,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC;CAC3C;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,UAAU,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EACA,WAAW,GACX,SAAS,GACT,UAAU,GACV,aAAa,GACb,aAAa,GACb,iBAAiB,GACjB,OAAO,GACP,SAAS,CAAC;IACd,OAAO,EAAE,GAAG,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EACA,YAAY,GACZ,UAAU,GACV,YAAY,GACZ,cAAc,GACd,cAAc,GACd,mBAAmB,GACnB,OAAO,GACP,UAAU,GACV,OAAO,CAAC;IACZ,OAAO,EAAE,GAAG,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,iBAAiB;IAChC,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;CAC1B"} |
| import { VideoComposeConfig, ComposeRequest, ComposeResult, TransitionEffect } from './types'; | ||
| import { ClipInstructionSet } from './instructions'; | ||
| interface ComposeStreams { | ||
| composeStream: WritableStream<ComposeRequest>; | ||
| cacheStream: ReadableStream<VideoFrame>; | ||
| encodeStream: ReadableStream<VideoFrame>; | ||
| } | ||
| /** | ||
@@ -22,3 +17,3 @@ * VideoComposer - Main visual composition orchestrator | ||
| private applyDefaults; | ||
| createStreams(_instruction?: ClipInstructionSet): ComposeStreams; | ||
| createStreams(instruction?: ClipInstructionSet): TransformStream<ComposeRequest, VideoFrame>; | ||
| composeFrame(request: ComposeRequest): Promise<ComposeResult>; | ||
@@ -32,3 +27,2 @@ composeTransition(fromRequest: ComposeRequest, toRequest: ComposeRequest, transition: TransitionEffect): Promise<ComposeResult>; | ||
| } | ||
| export {}; | ||
| //# sourceMappingURL=VideoComposer.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"VideoComposer.d.ts","sourceRoot":"","sources":["../../../src/stages/compose/VideoComposer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,cAAc,EACd,aAAa,EACb,gBAAgB,EAGjB,MAAM,SAAS,CAAC;AAKjB,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEpD,UAAU,cAAc;IACtB,aAAa,EAAE,cAAc,CAAC,cAAc,CAAC,CAAC;IAC9C,WAAW,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IACxC,YAAY,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;CAC1C;AAED;;GAEG;AACH,qBAAa,aAAa;IACxB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,kBAAkB,CAAC,CAAC;IAC9C,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IAEjC,OAAO,CAAC,GAAG,CAAoC;IAC/C,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,mBAAmB,CAAsB;IACjD,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,eAAe,CAAyB;gBAEpC,MAAM,EAAE,kBAAkB;IAgCtC,OAAO,CAAC,aAAa;IAwBrB,aAAa,CAAC,YAAY,CAAC,EAAE,kBAAkB,GAAG,cAAc;IAqC1D,YAAY,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;IAqE7D,iBAAiB,CACrB,WAAW,EAAE,cAAc,EAC3B,SAAS,EAAE,cAAc,EACzB,UAAU,EAAE,gBAAgB,GAC3B,OAAO,CAAC,aAAa,CAAC;IAWzB,OAAO,CAAC,WAAW;YAaL,iBAAiB;YAWjB,SAAS;IAkBvB,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,kBAAkB,CAAC,GAAG,IAAI;IAuBvD,OAAO,IAAI,IAAI;CAGhB"} | ||
| {"version":3,"file":"VideoComposer.d.ts","sourceRoot":"","sources":["../../../src/stages/compose/VideoComposer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,cAAc,EACd,aAAa,EACb,gBAAgB,EAIjB,MAAM,SAAS,CAAC;AAKjB,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AASpD;;GAEG;AACH,qBAAa,aAAa;IACxB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,kBAAkB,CAAC,CAAC;IAC9C,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IAEjC,OAAO,CAAC,GAAG,CAAoC;IAC/C,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,mBAAmB,CAAsB;IACjD,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,eAAe,CAAyB;gBAEpC,MAAM,EAAE,kBAAkB;IAgCtC,OAAO,CAAC,aAAa;IAwBrB,aAAa,CAAC,WAAW,CAAC,EAAE,kBAAkB,GAAG,eAAe,CAAC,cAAc,EAAE,UAAU,CAAC;IA+BtF,YAAY,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;IAqD7D,iBAAiB,CACrB,WAAW,EAAE,cAAc,EAC3B,SAAS,EAAE,cAAc,EACzB,UAAU,EAAE,gBAAgB,GAC3B,OAAO,CAAC,aAAa,CAAC;IAWzB,OAAO,CAAC,WAAW;YAaL,iBAAiB;YAWjB,SAAS;IAkBvB,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,kBAAkB,CAAC,GAAG,IAAI;IAuBvD,OAAO,IAAI,IAAI;CAGhB"} |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"BaseDecoder.d.ts","sourceRoot":"","sources":["../../../src/stages/decode/BaseDecoder.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAEpF,8BAAsB,WAAW,CAC/B,QAAQ,SAAS,YAAY,GAAG,YAAY,EAC5C,OAAO,SAAS,kBAAkB,GAAG,kBAAkB,EACvD,MAAM,SAAS,iBAAiB,GAAG,iBAAiB,EACpD,OAAO,SAAS,UAAU,GAAG,SAAS,GAAG,iBAAiB;IAE1D,SAAS,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC;IAC7B,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC;IAC1B,SAAS,CAAC,UAAU,EAAE,gCAAgC,CAAC,OAAO,CAAC,GAAG,IAAI,CAAQ;gBAElE,MAAM,EAAE,OAAO;IAIrB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAmB3B,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAuBpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAMtB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAW5B,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,SAAS,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;IAmB3C,SAAS,CAAC,WAAW,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI;IAKhD,OAAO,CAAC,eAAe;IAMvB,SAAS,CAAC,QAAQ,CAAC,iBAAiB,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC;IACtF,SAAS,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,EAAE,WAAW,GAAG,QAAQ;IAC7D,SAAS,CAAC,QAAQ,CAAC,gBAAgB,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IACnE,SAAS,CAAC,QAAQ,CAAC,cAAc,IAAI,MAAM;IAC3C,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAC9C,SAAS,CAAC,OAAO,IAAI,IAAI;IAGzB,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAClD,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IAEzD,YAAY,IAAI,eAAe,CAAC,MAAM,EAAE,OAAO,CAAC;CAyCjD;AAED,UAAU,WAAW;IACnB,MAAM,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IAC5B,KAAK,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;CACtC"} | ||
| {"version":3,"file":"BaseDecoder.d.ts","sourceRoot":"","sources":["../../../src/stages/decode/BaseDecoder.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAEpF,8BAAsB,WAAW,CAC/B,QAAQ,SAAS,YAAY,GAAG,YAAY,EAC5C,OAAO,SAAS,kBAAkB,GAAG,kBAAkB,EACvD,MAAM,SAAS,iBAAiB,GAAG,iBAAiB,EACpD,OAAO,SAAS,UAAU,GAAG,SAAS,GAAG,iBAAiB;IAE1D,SAAS,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC;IAC7B,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC;IAC1B,SAAS,CAAC,UAAU,EAAE,gCAAgC,CAAC,OAAO,CAAC,GAAG,IAAI,CAAQ;gBAElE,MAAM,EAAE,OAAO;IAIrB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAmB3B,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAuBpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAMtB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAW5B,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,SAAS,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;IAyB3C,SAAS,CAAC,WAAW,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI;IAKhD,OAAO,CAAC,eAAe;IAMvB,SAAS,CAAC,QAAQ,CAAC,iBAAiB,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC;IACtF,SAAS,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,EAAE,WAAW,GAAG,QAAQ;IAC7D,SAAS,CAAC,QAAQ,CAAC,gBAAgB,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IACnE,SAAS,CAAC,QAAQ,CAAC,cAAc,IAAI,MAAM;IAC3C,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAC9C,SAAS,CAAC,OAAO,IAAI,IAAI;IAGzB,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAClD,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IAEzD,YAAY,IAAI,eAAe,CAAC,MAAM,EAAE,OAAO,CAAC;CAyCjD;AAED,UAAU,WAAW;IACnB,MAAM,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IAC5B,KAAK,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;CACtC"} |
@@ -71,2 +71,7 @@ class BaseDecoder { | ||
| this.controller.enqueue(data); | ||
| if (data instanceof VideoFrame) { | ||
| setTimeout(() => { | ||
| data.close(); | ||
| }, 500); | ||
| } | ||
| } catch (error) { | ||
@@ -73,0 +78,0 @@ if (error instanceof TypeError) { |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"BaseDecoder.js","sources":["../../../src/stages/decode/BaseDecoder.ts"],"sourcesContent":["/**\n * Base decoder class abstracting common WebCodecs decoder workflow.\n * Mirrors BaseEncoder but for decoding path.\n */\nimport { AudioDecoderConfig, DecodedVideoFrame, VideoDecoderConfig } from './types';\n\nexport abstract class BaseDecoder<\n TDecoder extends VideoDecoder | AudioDecoder,\n TConfig extends VideoDecoderConfig | AudioDecoderConfig,\n TInput extends EncodedVideoChunk | EncodedAudioChunk,\n TOutput extends VideoFrame | AudioData | DecodedVideoFrame,\n> {\n protected decoder?: TDecoder;\n protected config: TConfig;\n protected controller: TransformStreamDefaultController<TOutput> | null = null;\n\n constructor(config: TConfig) {\n this.config = config;\n }\n\n async initialize(): Promise<void> {\n if (this.decoder?.state === 'configured') {\n return;\n }\n const isSupported = await this.isConfigSupported(this.config);\n if (!isSupported.supported) {\n throw new Error(\n `Codec not supported: ${(this.config as any).codecString || (this.config as any).codec}`\n );\n }\n\n this.decoder = this.createDecoder({\n output: this.handleOutput.bind(this),\n error: this.handleError.bind(this),\n });\n\n await this.configureDecoder(this.config);\n }\n\n async reconfigure(config: Partial<TConfig>): Promise<void> {\n this.config = { ...this.config, ...config } as TConfig;\n\n if (!this.decoder) {\n await this.initialize();\n return;\n }\n\n // Flush before reconfiguring\n if (this.decoder.state === 'configured') {\n await this.decoder.flush();\n }\n\n const isSupported = await this.isConfigSupported(this.config);\n if (!isSupported.supported) {\n throw new Error(\n `New configuration not supported: ${(this.config as any).codecString || (this.config as any).codec}`\n );\n }\n\n await this.configureDecoder(this.config);\n }\n\n async flush(): Promise<void> {\n if (!this.decoder) return;\n await this.decoder.flush();\n }\n\n async reset(): Promise<void> {\n if (!this.decoder) return;\n this.decoder.reset();\n this.onReset();\n }\n\n async close(): Promise<void> {\n if (!this.decoder) return;\n\n if (this.decoder.state === 'configured') {\n await this.decoder.flush();\n }\n\n this.decoder.close();\n this.decoder = undefined;\n }\n\n get isReady(): boolean {\n return this.decoder?.state === 'configured';\n }\n\n get queueSize(): number {\n return (this.decoder as any)?.decodeQueueSize ?? 0;\n }\n\n protected handleOutput(data: TOutput): void {\n if (!this.controller) {\n this.closeIfPossible(data);\n return;\n }\n\n try {\n this.controller.enqueue(data);\n } catch (error) {\n // Stream closed or errored - silently drop frame\n if (error instanceof TypeError) {\n this.closeIfPossible(data);\n return;\n }\n\n throw error;\n }\n }\n\n protected handleError(error: DOMException): void {\n console.error(`${this.getDecoderType()} decoder error:`, error);\n this.controller?.error(error);\n }\n\n private closeIfPossible(data: TOutput): void {\n (data as any)?.close?.();\n (data as DecodedVideoFrame)?.frame?.close?.();\n }\n\n // Abstracts\n protected abstract isConfigSupported(config: TConfig): Promise<{ supported: boolean }>;\n protected abstract createDecoder(init: DecoderInit): TDecoder;\n protected abstract configureDecoder(config: TConfig): Promise<void>;\n protected abstract getDecoderType(): string;\n protected abstract decode(input: TInput): void;\n protected onReset(): void {}\n\n // Backpressure\n protected abstract readonly highWaterMark: number;\n protected abstract readonly decodeQueueThreshold: number;\n\n createStream(): TransformStream<TInput, TOutput> {\n return new TransformStream<TInput, TOutput>(\n {\n start: async (controller) => {\n this.controller = controller;\n if (!this.isReady) {\n await this.initialize();\n }\n },\n transform: async (input) => {\n if (!this.decoder || this.decoder.state !== 'configured') {\n throw new Error('Decoder not configured');\n }\n // backpressure\n if ((this.decoder as any).decodeQueueSize >= this.decodeQueueThreshold) {\n await new Promise<void>((resolve) => {\n const check = () => {\n if (\n !this.decoder ||\n (this.decoder as any).decodeQueueSize < this.decodeQueueThreshold - 1\n ) {\n resolve();\n } else {\n setTimeout(check, 10);\n }\n };\n check();\n });\n }\n this.decode(input);\n },\n flush: async () => {\n await this.flush();\n },\n },\n {\n highWaterMark: this.highWaterMark,\n size: () => 1,\n }\n );\n }\n}\n\ninterface DecoderInit {\n output: (data: any) => void;\n error: (error: DOMException) => void;\n}\n"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"BaseDecoder.js","sources":["../../../src/stages/decode/BaseDecoder.ts"],"sourcesContent":["/**\n * Base decoder class abstracting common WebCodecs decoder workflow.\n * Mirrors BaseEncoder but for decoding path.\n */\nimport { AudioDecoderConfig, DecodedVideoFrame, VideoDecoderConfig } from './types';\n\nexport abstract class BaseDecoder<\n TDecoder extends VideoDecoder | AudioDecoder,\n TConfig extends VideoDecoderConfig | AudioDecoderConfig,\n TInput extends EncodedVideoChunk | EncodedAudioChunk,\n TOutput extends VideoFrame | AudioData | DecodedVideoFrame,\n> {\n protected decoder?: TDecoder;\n protected config: TConfig;\n protected controller: TransformStreamDefaultController<TOutput> | null = null;\n\n constructor(config: TConfig) {\n this.config = config;\n }\n\n async initialize(): Promise<void> {\n if (this.decoder?.state === 'configured') {\n return;\n }\n const isSupported = await this.isConfigSupported(this.config);\n if (!isSupported.supported) {\n throw new Error(\n `Codec not supported: ${(this.config as any).codecString || (this.config as any).codec}`\n );\n }\n\n this.decoder = this.createDecoder({\n output: this.handleOutput.bind(this),\n error: this.handleError.bind(this),\n });\n\n await this.configureDecoder(this.config);\n }\n\n async reconfigure(config: Partial<TConfig>): Promise<void> {\n this.config = { ...this.config, ...config } as TConfig;\n\n if (!this.decoder) {\n await this.initialize();\n return;\n }\n\n // Flush before reconfiguring\n if (this.decoder.state === 'configured') {\n await this.decoder.flush();\n }\n\n const isSupported = await this.isConfigSupported(this.config);\n if (!isSupported.supported) {\n throw new Error(\n `New configuration not supported: ${(this.config as any).codecString || (this.config as any).codec}`\n );\n }\n\n await this.configureDecoder(this.config);\n }\n\n async flush(): Promise<void> {\n if (!this.decoder) return;\n await this.decoder.flush();\n }\n\n async reset(): Promise<void> {\n if (!this.decoder) return;\n this.decoder.reset();\n this.onReset();\n }\n\n async close(): Promise<void> {\n if (!this.decoder) return;\n\n if (this.decoder.state === 'configured') {\n await this.decoder.flush();\n }\n\n this.decoder.close();\n this.decoder = undefined;\n }\n\n get isReady(): boolean {\n return this.decoder?.state === 'configured';\n }\n\n get queueSize(): number {\n return (this.decoder as any)?.decodeQueueSize ?? 0;\n }\n\n protected handleOutput(data: TOutput): void {\n if (!this.controller) {\n this.closeIfPossible(data);\n return;\n }\n\n try {\n this.controller.enqueue(data);\n if (data instanceof VideoFrame) {\n // ugly hack to avoid memory leak\n setTimeout(() => {\n data.close();\n }, 500);\n }\n } catch (error) {\n // Stream closed or errored - silently drop frame\n if (error instanceof TypeError) {\n this.closeIfPossible(data);\n return;\n }\n\n throw error;\n }\n }\n\n protected handleError(error: DOMException): void {\n console.error(`${this.getDecoderType()} decoder error:`, error);\n this.controller?.error(error);\n }\n\n private closeIfPossible(data: TOutput): void {\n (data as any)?.close?.();\n (data as DecodedVideoFrame)?.frame?.close?.();\n }\n\n // Abstracts\n protected abstract isConfigSupported(config: TConfig): Promise<{ supported: boolean }>;\n protected abstract createDecoder(init: DecoderInit): TDecoder;\n protected abstract configureDecoder(config: TConfig): Promise<void>;\n protected abstract getDecoderType(): string;\n protected abstract decode(input: TInput): void;\n protected onReset(): void {}\n\n // Backpressure\n protected abstract readonly highWaterMark: number;\n protected abstract readonly decodeQueueThreshold: number;\n\n createStream(): TransformStream<TInput, TOutput> {\n return new TransformStream<TInput, TOutput>(\n {\n start: async (controller) => {\n this.controller = controller;\n if (!this.isReady) {\n await this.initialize();\n }\n },\n transform: async (input) => {\n if (!this.decoder || this.decoder.state !== 'configured') {\n throw new Error('Decoder not configured');\n }\n // backpressure\n if ((this.decoder as any).decodeQueueSize >= this.decodeQueueThreshold) {\n await new Promise<void>((resolve) => {\n const check = () => {\n if (\n !this.decoder ||\n (this.decoder as any).decodeQueueSize < this.decodeQueueThreshold - 1\n ) {\n resolve();\n } else {\n setTimeout(check, 10);\n }\n };\n check();\n });\n }\n this.decode(input);\n },\n flush: async () => {\n await this.flush();\n },\n },\n {\n highWaterMark: this.highWaterMark,\n size: () => 1,\n }\n );\n }\n}\n\ninterface DecoderInit {\n output: (data: any) => void;\n error: (error: DOMException) => void;\n}\n"],"names":[],"mappings":"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"} |
@@ -14,4 +14,2 @@ import { VideoDecoderConfig } from './types'; | ||
| protected readonly decodeQueueThreshold: number; | ||
| private currentGopSerial; | ||
| private isCurrentFrameKeyframe; | ||
| private bufferedChunks; | ||
@@ -42,4 +40,2 @@ private isProcessingBuffer; | ||
| protected decode(chunk: EncodedVideoChunk): void; | ||
| protected onReset(): void; | ||
| private handleKeyFrame; | ||
| /** | ||
@@ -46,0 +42,0 @@ * Configure the decoder with codec info (can be called after creation) |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"VideoChunkDecoder.d.ts","sourceRoot":"","sources":["../../../src/stages/decode/VideoChunkDecoder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C;;;GAGG;AACH,qBAAa,iBAAkB,SAAQ,WAAW,CAChD,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,EACjB,UAAU,CACX;IACC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,uBAAuB,CAAK;IACpD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,8BAA8B,CAAM;IAE5D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB,SAAS,CAAC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IACzC,SAAS,CAAC,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IAGhD,OAAO,CAAC,gBAAgB,CAAM;IAC9B,OAAO,CAAC,sBAAsB,CAAS;IAGvC,OAAO,CAAC,cAAc,CAA2B;IACjD,OAAO,CAAC,kBAAkB,CAAkB;gBAEhC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC;IAYjE,IAAI,YAAY,IAAI,OAAO,CAE1B;IAED,IAAI,KAAK,IAAI,MAAM,CAElB;IAED;;OAEG;IACG,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,kBAAkB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAS7D,YAAY,IAAI,eAAe,CAAC,iBAAiB,EAAE,UAAU,CAAC;IAyCvE;;OAEG;YACW,YAAY;cAuCP,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;cAkBxC,iBAAiB,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC;IAS9F,SAAS,CAAC,aAAa,CAAC,IAAI,EAAE;QAC5B,MAAM,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;QACpC,KAAK,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;KACtC,GAAG,YAAY;IAIhB,SAAS,CAAC,cAAc,IAAI,MAAM;cAIlB,gBAAgB,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB3E,SAAS,CAAC,MAAM,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI;cAK7B,OAAO,IAAI,IAAI;IAMlC,OAAO,CAAC,cAAc;IAMtB;;OAEG;IACG,SAAS,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAe1D;;OAEG;IACG,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IA6B7B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAUtC"} | ||
| {"version":3,"file":"VideoChunkDecoder.d.ts","sourceRoot":"","sources":["../../../src/stages/decode/VideoChunkDecoder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C;;;GAGG;AACH,qBAAa,iBAAkB,SAAQ,WAAW,CAChD,YAAY,EACZ,kBAAkB,EAClB,iBAAiB,EACjB,UAAU,CACX;IACC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,uBAAuB,CAAK;IACpD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,8BAA8B,CAAM;IAE5D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB,SAAS,CAAC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IACzC,SAAS,CAAC,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IAGhD,OAAO,CAAC,cAAc,CAA2B;IACjD,OAAO,CAAC,kBAAkB,CAAkB;gBAEhC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC;IAYjE,IAAI,YAAY,IAAI,OAAO,CAE1B;IAED,IAAI,KAAK,IAAI,MAAM,CAElB;IAED;;OAEG;IACG,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,kBAAkB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAS7D,YAAY,IAAI,eAAe,CAAC,iBAAiB,EAAE,UAAU,CAAC;IAyCvE;;OAEG;YACW,YAAY;cAkCP,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;cAKxC,iBAAiB,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC;IAS9F,SAAS,CAAC,aAAa,CAAC,IAAI,EAAE;QAC5B,MAAM,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;QACpC,KAAK,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;KACtC,GAAG,YAAY;IAIhB,SAAS,CAAC,cAAc,IAAI,MAAM;cAIlB,gBAAgB,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB3E,SAAS,CAAC,MAAM,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI;IAIhD;;OAEG;IACG,SAAS,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAe1D;;OAEG;IACG,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IA6B7B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAOtC"} |
@@ -8,5 +8,2 @@ import { BaseDecoder } from "./BaseDecoder.js"; | ||
| decodeQueueThreshold; | ||
| // GOP tracking (serial is derived from keyframe timestamp for idempotency) | ||
| currentGopSerial = -1; | ||
| isCurrentFrameKeyframe = false; | ||
| // Buffering support for delayed configuration | ||
@@ -82,5 +79,2 @@ bufferedChunks = []; | ||
| } | ||
| if (chunk.type === "key") { | ||
| this.handleKeyFrame(chunk.timestamp); | ||
| } | ||
| if (this.decoder.decodeQueueSize >= this.decodeQueueThreshold) { | ||
@@ -105,12 +99,5 @@ await new Promise((resolve) => { | ||
| } | ||
| // Override handleOutput to attach GOP metadata | ||
| // Override handleOutput to enqueue decoded frames | ||
| handleOutput(frame) { | ||
| const wrappedFrame = { | ||
| frame, | ||
| gopSerial: this.currentGopSerial, | ||
| isKeyframe: this.isCurrentFrameKeyframe, | ||
| timestamp: frame.timestamp | ||
| }; | ||
| this.isCurrentFrameKeyframe = false; | ||
| super.handleOutput(wrappedFrame); | ||
| super.handleOutput(frame); | ||
| } | ||
@@ -149,12 +136,2 @@ // Implement abstract methods | ||
| } | ||
| // Override reset to clear GOP data | ||
| onReset() { | ||
| this.currentGopSerial = -1; | ||
| this.isCurrentFrameKeyframe = false; | ||
| } | ||
| // GOP management methods | ||
| handleKeyFrame(timestamp) { | ||
| this.currentGopSerial = timestamp; | ||
| this.isCurrentFrameKeyframe = true; | ||
| } | ||
| /** | ||
@@ -193,6 +170,5 @@ * Configure the decoder with codec info (can be called after creation) | ||
| } | ||
| // Override close to clean up GOP data | ||
| // Override close to clean up buffered chunks | ||
| async close() { | ||
| this.bufferedChunks = []; | ||
| this.onReset(); | ||
| await super.close(); | ||
@@ -199,0 +175,0 @@ } |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"VideoChunkDecoder.js","sources":["../../../src/stages/decode/VideoChunkDecoder.ts"],"sourcesContent":["import { VideoDecoderConfig } from './types';\nimport { BaseDecoder } from './BaseDecoder';\n\n/**\n * Video decoder with GOP tracking\n * Tracks keyframe boundaries and attaches GOP metadata to decoded frames\n */\nexport class VideoChunkDecoder extends BaseDecoder<\n VideoDecoder,\n VideoDecoderConfig,\n EncodedVideoChunk,\n VideoFrame\n> {\n private static readonly DEFAULT_HIGH_WATER_MARK = 4;\n private static readonly DEFAULT_DECODE_QUEUE_THRESHOLD = 16;\n\n readonly trackId: string;\n\n protected readonly highWaterMark: number;\n protected readonly decodeQueueThreshold: number;\n\n // GOP tracking (serial is derived from keyframe timestamp for idempotency)\n private currentGopSerial = -1;\n private isCurrentFrameKeyframe = false;\n\n // Buffering support for delayed configuration\n private bufferedChunks: EncodedVideoChunk[] = [];\n private isProcessingBuffer: boolean = false;\n\n constructor(trackId: string, config?: Partial<VideoDecoderConfig>) {\n super((config || {}) as VideoDecoderConfig);\n\n this.trackId = trackId;\n this.highWaterMark =\n config?.backpressure?.highWaterMark ?? VideoChunkDecoder.DEFAULT_HIGH_WATER_MARK;\n this.decodeQueueThreshold =\n config?.backpressure?.decodeQueueThreshold ??\n VideoChunkDecoder.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<VideoDecoderConfig>): Promise<void> {\n if (!this.isReady && config.codec) {\n await this.configure(config as VideoDecoderConfig);\n await this.processBufferedChunks();\n }\n }\n\n // Override createStream to handle GOP tracking and buffering\n // Always create new stream for each clip (ReadableStreams can only be consumed once)\n override createStream(): TransformStream<EncodedVideoChunk, VideoFrame> {\n return new TransformStream<EncodedVideoChunk, VideoFrame>(\n {\n start: async (controller) => {\n this.controller = controller;\n // Don't initialize if no codec config yet\n if (this.config?.codec && this.config?.description && !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: EncodedVideoChunk): Promise<void> {\n if (!this.decoder) {\n throw new Error('Decoder not initialized');\n }\n\n if (this.decoder.state !== 'configured') {\n console.error('[VideoChunkDecoder] Decoder in unexpected state:', this.decoder.state);\n throw new Error(`Decoder not configured, state: ${this.decoder.state}`);\n }\n\n // Track GOP boundaries\n if (chunk.type === 'key') {\n this.handleKeyFrame(chunk.timestamp);\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(`[VideoChunkDecoder] decode error:`, error);\n throw error; // Re-throw to propagate\n }\n }\n\n // Override handleOutput to attach GOP metadata\n protected override handleOutput(frame: VideoFrame): void {\n // Wrap frame with GOP metadata as a plain object\n // The frame itself will be transferred, but metadata travels separately\n const wrappedFrame = {\n frame,\n gopSerial: this.currentGopSerial,\n isKeyframe: this.isCurrentFrameKeyframe,\n timestamp: frame.timestamp,\n };\n\n // Reset keyframe flag for subsequent frames\n this.isCurrentFrameKeyframe = false;\n\n // Call parent to enqueue wrapped frame\n super.handleOutput(wrappedFrame as any);\n }\n\n // Implement abstract methods\n protected async isConfigSupported(config: VideoDecoderConfig): Promise<{ supported: boolean }> {\n const result = await VideoDecoder.isConfigSupported({\n codec: config.codec,\n codedWidth: config.width,\n codedHeight: config.height,\n });\n return { supported: result.supported ?? false };\n }\n\n protected createDecoder(init: {\n output: (frame: VideoFrame) => void;\n error: (error: DOMException) => void;\n }): VideoDecoder {\n return new VideoDecoder(init);\n }\n\n protected getDecoderType(): string {\n return 'Video';\n }\n\n protected async configureDecoder(config: VideoDecoderConfig): Promise<void> {\n if (!this.decoder) return;\n\n const decoderConfig = {\n codec: config.codec,\n codedWidth: config.width,\n codedHeight: config.height,\n hardwareAcceleration: config.hardwareAcceleration || 'no-preference',\n optimizeForLatency: false,\n ...(config.description && { description: config.description }),\n ...(config.displayAspectWidth && { displayAspectWidth: config.displayAspectWidth }),\n ...(config.displayAspectHeight && { displayAspectHeight: config.displayAspectHeight }),\n };\n\n this.decoder.configure(decoderConfig as any);\n // console.log('[VideoChunkDecoder] Decoder configured, state:', this.decoder.state);\n }\n\n protected decode(chunk: EncodedVideoChunk): void {\n this.decoder?.decode(chunk);\n }\n\n // Override reset to clear GOP data\n protected override onReset(): void {\n this.currentGopSerial = -1;\n this.isCurrentFrameKeyframe = false;\n }\n\n // GOP management methods\n private handleKeyFrame(timestamp: number): void {\n // Use timestamp as gopSerial for idempotency (same keyframe = same serial)\n this.currentGopSerial = timestamp;\n this.isCurrentFrameKeyframe = true;\n }\n\n /**\n * Configure the decoder with codec info (can be called after creation)\n */\n async configure(config: VideoDecoderConfig): Promise<void> {\n // console.log('[VideoChunkDecoder] Configuring with:', config);\n\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 // console.log('[VideoChunkDecoder] Processing', this.bufferedChunks.length, 'buffered chunks');\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('[VideoChunkDecoder] 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 GOP data\n override async close(): Promise<void> {\n // Clear buffered chunks\n this.bufferedChunks = [];\n\n // Clean up GOP data first\n this.onReset();\n\n // Call parent close\n await super.close();\n }\n}\n"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"VideoChunkDecoder.js","sources":["../../../src/stages/decode/VideoChunkDecoder.ts"],"sourcesContent":["import { VideoDecoderConfig } from './types';\nimport { BaseDecoder } from './BaseDecoder';\n\n/**\n * Video decoder with GOP tracking\n * Tracks keyframe boundaries and attaches GOP metadata to decoded frames\n */\nexport class VideoChunkDecoder extends BaseDecoder<\n VideoDecoder,\n VideoDecoderConfig,\n EncodedVideoChunk,\n VideoFrame\n> {\n private static readonly DEFAULT_HIGH_WATER_MARK = 4;\n private static readonly DEFAULT_DECODE_QUEUE_THRESHOLD = 16;\n\n readonly trackId: string;\n\n protected readonly highWaterMark: number;\n protected readonly decodeQueueThreshold: number;\n\n // Buffering support for delayed configuration\n private bufferedChunks: EncodedVideoChunk[] = [];\n private isProcessingBuffer: boolean = false;\n\n constructor(trackId: string, config?: Partial<VideoDecoderConfig>) {\n super((config || {}) as VideoDecoderConfig);\n\n this.trackId = trackId;\n this.highWaterMark =\n config?.backpressure?.highWaterMark ?? VideoChunkDecoder.DEFAULT_HIGH_WATER_MARK;\n this.decodeQueueThreshold =\n config?.backpressure?.decodeQueueThreshold ??\n VideoChunkDecoder.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<VideoDecoderConfig>): Promise<void> {\n if (!this.isReady && config.codec) {\n await this.configure(config as VideoDecoderConfig);\n await this.processBufferedChunks();\n }\n }\n\n // Override createStream to handle GOP tracking and buffering\n // Always create new stream for each clip (ReadableStreams can only be consumed once)\n override createStream(): TransformStream<EncodedVideoChunk, VideoFrame> {\n return new TransformStream<EncodedVideoChunk, VideoFrame>(\n {\n start: async (controller) => {\n this.controller = controller;\n // Don't initialize if no codec config yet\n if (this.config?.codec && this.config?.description && !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: EncodedVideoChunk): Promise<void> {\n if (!this.decoder) {\n throw new Error('Decoder not initialized');\n }\n\n if (this.decoder.state !== 'configured') {\n console.error('[VideoChunkDecoder] 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(`[VideoChunkDecoder] decode error:`, error);\n throw error; // Re-throw to propagate\n }\n }\n\n // Override handleOutput to enqueue decoded frames\n protected override handleOutput(frame: VideoFrame): void {\n super.handleOutput(frame);\n }\n\n // Implement abstract methods\n protected async isConfigSupported(config: VideoDecoderConfig): Promise<{ supported: boolean }> {\n const result = await VideoDecoder.isConfigSupported({\n codec: config.codec,\n codedWidth: config.width,\n codedHeight: config.height,\n });\n return { supported: result.supported ?? false };\n }\n\n protected createDecoder(init: {\n output: (frame: VideoFrame) => void;\n error: (error: DOMException) => void;\n }): VideoDecoder {\n return new VideoDecoder(init);\n }\n\n protected getDecoderType(): string {\n return 'Video';\n }\n\n protected async configureDecoder(config: VideoDecoderConfig): Promise<void> {\n if (!this.decoder) return;\n\n const decoderConfig = {\n codec: config.codec,\n codedWidth: config.width,\n codedHeight: config.height,\n hardwareAcceleration: config.hardwareAcceleration || 'no-preference',\n optimizeForLatency: false,\n ...(config.description && { description: config.description }),\n ...(config.displayAspectWidth && { displayAspectWidth: config.displayAspectWidth }),\n ...(config.displayAspectHeight && { displayAspectHeight: config.displayAspectHeight }),\n };\n\n this.decoder.configure(decoderConfig as any);\n // console.log('[VideoChunkDecoder] Decoder configured, state:', this.decoder.state);\n }\n\n protected decode(chunk: EncodedVideoChunk): 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: VideoDecoderConfig): Promise<void> {\n // console.log('[VideoChunkDecoder] Configuring with:', config);\n\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 // console.log('[VideoChunkDecoder] Processing', this.bufferedChunks.length, 'buffered chunks');\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('[VideoChunkDecoder] 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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"} |
| const MICROSECONDS_PER_SECOND = 1e6; | ||
| const DEFAULT_FPS = 30; | ||
| const DEFAULT_FRAME_TOLERANCE_US = 33333; | ||
| function normalizeFps(value) { | ||
@@ -39,21 +38,6 @@ if (!Number.isFinite(value) || value <= 0) { | ||
| } | ||
| function isTimestampWithinFrame(targetTimeUs, frameTimestampUs, frameDurationUs, toleranceUs = DEFAULT_FRAME_TOLERANCE_US) { | ||
| if (!Number.isFinite(frameTimestampUs)) { | ||
| return false; | ||
| } | ||
| const delta = Math.abs(targetTimeUs - frameTimestampUs); | ||
| if (delta <= toleranceUs) { | ||
| return true; | ||
| } | ||
| if (frameDurationUs > 0 && frameTimestampUs <= targetTimeUs) { | ||
| return targetTimeUs < frameTimestampUs + frameDurationUs; | ||
| } | ||
| return false; | ||
| } | ||
| export { | ||
| DEFAULT_FRAME_TOLERANCE_US, | ||
| MICROSECONDS_PER_SECOND, | ||
| frameDurationFromFps, | ||
| frameIndexFromTimestamp, | ||
| isTimestampWithinFrame, | ||
| normalizeFps, | ||
@@ -60,0 +44,0 @@ quantizeTimestampToFrame |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"time-utils.js","sources":["../../src/utils/time-utils.ts"],"sourcesContent":["export type QuantizeStrategy = 'nearest' | 'floor' | 'ceil';\ntype TimeUs = number;\nexport const MICROSECONDS_PER_SECOND = 1_000_000;\n\nconst DEFAULT_FPS = 30;\n\nexport const DEFAULT_FRAME_TOLERANCE_US: TimeUs = 33_333;\n\nexport function normalizeFps(value?: number): number {\n if (!Number.isFinite(value) || (value as number) <= 0) {\n return DEFAULT_FPS;\n }\n return value as number;\n}\n\nexport function frameDurationFromFps(fps?: number): TimeUs {\n const normalized = normalizeFps(fps);\n const duration = MICROSECONDS_PER_SECOND / normalized;\n return Math.max(Math.round(duration), 1);\n}\n\nexport function frameIndexFromTimestamp(\n baseTimestampUs: TimeUs,\n timestampUs: TimeUs,\n fps?: number,\n strategy: QuantizeStrategy = 'nearest'\n): number {\n const frameDurationUs = frameDurationFromFps(fps);\n if (frameDurationUs <= 0) {\n return 0;\n }\n\n const delta = timestampUs - baseTimestampUs;\n const rawIndex = delta / frameDurationUs;\n\n if (!Number.isFinite(rawIndex)) {\n return 0;\n }\n\n switch (strategy) {\n case 'floor':\n return Math.floor(rawIndex);\n case 'ceil':\n return Math.ceil(rawIndex);\n default:\n return Math.round(rawIndex);\n }\n}\n\nexport function quantizeTimestampToFrame(\n timestampUs: TimeUs,\n baseTimestampUs: TimeUs,\n fps?: number,\n strategy: QuantizeStrategy = 'nearest'\n): TimeUs {\n const frameDurationUs = frameDurationFromFps(fps);\n const index = frameIndexFromTimestamp(baseTimestampUs, timestampUs, fps, strategy);\n return baseTimestampUs + index * frameDurationUs;\n}\n\nexport function isTimestampWithinFrame(\n targetTimeUs: TimeUs,\n frameTimestampUs: TimeUs,\n frameDurationUs: TimeUs,\n toleranceUs: TimeUs = DEFAULT_FRAME_TOLERANCE_US\n): boolean {\n if (!Number.isFinite(frameTimestampUs)) {\n return false;\n }\n\n const delta = Math.abs(targetTimeUs - frameTimestampUs);\n if (delta <= toleranceUs) {\n return true;\n }\n\n if (frameDurationUs > 0 && frameTimestampUs <= targetTimeUs) {\n return targetTimeUs < frameTimestampUs + frameDurationUs;\n }\n\n return false;\n}\n"],"names":[],"mappings":"AAEO,MAAM,0BAA0B;AAEvC,MAAM,cAAc;AAEb,MAAM,6BAAqC;AAE3C,SAAS,aAAa,OAAwB;AACnD,MAAI,CAAC,OAAO,SAAS,KAAK,KAAM,SAAoB,GAAG;AACrD,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEO,SAAS,qBAAqB,KAAsB;AACzD,QAAM,aAAa,aAAa,GAAG;AACnC,QAAM,WAAW,0BAA0B;AAC3C,SAAO,KAAK,IAAI,KAAK,MAAM,QAAQ,GAAG,CAAC;AACzC;AAEO,SAAS,wBACd,iBACA,aACA,KACA,WAA6B,WACrB;AACR,QAAM,kBAAkB,qBAAqB,GAAG;AAChD,MAAI,mBAAmB,GAAG;AACxB,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,cAAc;AAC5B,QAAM,WAAW,QAAQ;AAEzB,MAAI,CAAC,OAAO,SAAS,QAAQ,GAAG;AAC9B,WAAO;AAAA,EACT;AAEA,UAAQ,UAAA;AAAA,IACN,KAAK;AACH,aAAO,KAAK,MAAM,QAAQ;AAAA,IAC5B,KAAK;AACH,aAAO,KAAK,KAAK,QAAQ;AAAA,IAC3B;AACE,aAAO,KAAK,MAAM,QAAQ;AAAA,EAAA;AAEhC;AAEO,SAAS,yBACd,aACA,iBACA,KACA,WAA6B,WACrB;AACR,QAAM,kBAAkB,qBAAqB,GAAG;AAChD,QAAM,QAAQ,wBAAwB,iBAAiB,aAAa,KAAK,QAAQ;AACjF,SAAO,kBAAkB,QAAQ;AACnC;AAEO,SAAS,uBACd,cACA,kBACA,iBACA,cAAsB,4BACb;AACT,MAAI,CAAC,OAAO,SAAS,gBAAgB,GAAG;AACtC,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,KAAK,IAAI,eAAe,gBAAgB;AACtD,MAAI,SAAS,aAAa;AACxB,WAAO;AAAA,EACT;AAEA,MAAI,kBAAkB,KAAK,oBAAoB,cAAc;AAC3D,WAAO,eAAe,mBAAmB;AAAA,EAC3C;AAEA,SAAO;AACT;"} | ||
| {"version":3,"file":"time-utils.js","sources":["../../src/utils/time-utils.ts"],"sourcesContent":["export type QuantizeStrategy = 'nearest' | 'floor' | 'ceil';\ntype TimeUs = number;\nexport const MICROSECONDS_PER_SECOND = 1_000_000;\n\nconst DEFAULT_FPS = 30;\n\nexport const DEFAULT_FRAME_TOLERANCE_US: TimeUs = 33_333;\n\nexport function normalizeFps(value?: number): number {\n if (!Number.isFinite(value) || (value as number) <= 0) {\n return DEFAULT_FPS;\n }\n return value as number;\n}\n\nexport function frameDurationFromFps(fps?: number): TimeUs {\n const normalized = normalizeFps(fps);\n const duration = MICROSECONDS_PER_SECOND / normalized;\n return Math.max(Math.round(duration), 1);\n}\n\nexport function frameIndexFromTimestamp(\n baseTimestampUs: TimeUs,\n timestampUs: TimeUs,\n fps?: number,\n strategy: QuantizeStrategy = 'nearest'\n): number {\n const frameDurationUs = frameDurationFromFps(fps);\n if (frameDurationUs <= 0) {\n return 0;\n }\n\n const delta = timestampUs - baseTimestampUs;\n const rawIndex = delta / frameDurationUs;\n\n if (!Number.isFinite(rawIndex)) {\n return 0;\n }\n\n switch (strategy) {\n case 'floor':\n return Math.floor(rawIndex);\n case 'ceil':\n return Math.ceil(rawIndex);\n default:\n return Math.round(rawIndex);\n }\n}\n\nexport function quantizeTimestampToFrame(\n timestampUs: TimeUs,\n baseTimestampUs: TimeUs,\n fps?: number,\n strategy: QuantizeStrategy = 'nearest'\n): TimeUs {\n const frameDurationUs = frameDurationFromFps(fps);\n const index = frameIndexFromTimestamp(baseTimestampUs, timestampUs, fps, strategy);\n return baseTimestampUs + index * frameDurationUs;\n}\n\nexport function isTimestampWithinFrame(\n targetTimeUs: TimeUs,\n frameTimestampUs: TimeUs,\n frameDurationUs: TimeUs,\n toleranceUs: TimeUs = DEFAULT_FRAME_TOLERANCE_US\n): boolean {\n if (!Number.isFinite(frameTimestampUs)) {\n return false;\n }\n\n const delta = Math.abs(targetTimeUs - frameTimestampUs);\n if (delta <= toleranceUs) {\n return true;\n }\n\n if (frameDurationUs > 0 && frameTimestampUs <= targetTimeUs) {\n return targetTimeUs < frameTimestampUs + frameDurationUs;\n }\n\n return false;\n}\n"],"names":[],"mappings":"AAEO,MAAM,0BAA0B;AAEvC,MAAM,cAAc;AAIb,SAAS,aAAa,OAAwB;AACnD,MAAI,CAAC,OAAO,SAAS,KAAK,KAAM,SAAoB,GAAG;AACrD,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEO,SAAS,qBAAqB,KAAsB;AACzD,QAAM,aAAa,aAAa,GAAG;AACnC,QAAM,WAAW,0BAA0B;AAC3C,SAAO,KAAK,IAAI,KAAK,MAAM,QAAQ,GAAG,CAAC;AACzC;AAEO,SAAS,wBACd,iBACA,aACA,KACA,WAA6B,WACrB;AACR,QAAM,kBAAkB,qBAAqB,GAAG;AAChD,MAAI,mBAAmB,GAAG;AACxB,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,cAAc;AAC5B,QAAM,WAAW,QAAQ;AAEzB,MAAI,CAAC,OAAO,SAAS,QAAQ,GAAG;AAC9B,WAAO;AAAA,EACT;AAEA,UAAQ,UAAA;AAAA,IACN,KAAK;AACH,aAAO,KAAK,MAAM,QAAQ;AAAA,IAC5B,KAAK;AACH,aAAO,KAAK,KAAK,QAAQ;AAAA,IAC3B;AACE,aAAO,KAAK,MAAM,QAAQ;AAAA,EAAA;AAEhC;AAEO,SAAS,yBACd,aACA,iBACA,KACA,WAA6B,WACrB;AACR,QAAM,kBAAkB,qBAAqB,GAAG;AAChD,QAAM,QAAQ,wBAAwB,iBAAiB,aAAa,KAAK,QAAQ;AACjF,SAAO,kBAAkB,QAAQ;AACnC;"} |
| { | ||
| "audio-compose.worker": "stages/compose/audio-compose.worker.rW63uN6z.js", | ||
| "video-compose.worker": "stages/compose/video-compose.worker.B7xEVmN3.js", | ||
| "audio-decode.worker": "stages/decode/audio-decode.worker.Czb1P66a.js", | ||
| "video-decode.worker": "stages/decode/video-decode.worker.DLX8FRVc.js", | ||
| "video-compose.worker": "stages/compose/video-compose.worker.M5uomNVr.js", | ||
| "audio-decode.worker": "stages/decode/audio-decode.worker.3SD5i_Oe.js", | ||
| "video-decode.worker": "stages/decode/video-decode.worker.BAB6216v.js", | ||
| "audio-demux.worker": "stages/demux/audio-demux.worker.BTFPcY7P.js", | ||
@@ -7,0 +7,0 @@ "video-demux.worker": "stages/demux/video-demux.worker.D_WeHPkt.js", |
+1
-1
| { | ||
| "name": "@meframe/core", | ||
| "version": "0.0.25", | ||
| "version": "0.0.26", | ||
| "description": "Next generation media processing framework based on WebCodecs", | ||
@@ -5,0 +5,0 @@ "type": "module", |
| import { TimeUs } from '../../model/types'; | ||
| import { GOP } from '../types'; | ||
| export declare function computeGopRange(startUs: TimeUs, endUs: TimeUs, intervalUs: number): { | ||
| startIndex: number; | ||
| endIndex: number; | ||
| }; | ||
| export declare function findGopIndex(timeUs: TimeUs, intervalUs: number): number; | ||
| export declare function findFrameIndex(gop: GOP, timeUs: TimeUs, toleranceUs?: TimeUs): number; | ||
| //# sourceMappingURL=gop-utils.d.ts.map |
| {"version":3,"file":"gop-utils.d.ts","sourceRoot":"","sources":["../../../src/cache/l1/gop-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAMpC,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,GACjB;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAQ1C;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAGvE;AAED,wBAAgB,cAAc,CAC5B,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,MAAM,EACd,WAAW,GAAE,MAAmC,GAC/C,MAAM,CA2DR"} |
| import { DEFAULT_FRAME_TOLERANCE_US, isTimestampWithinFrame } from "../../utils/time-utils.js"; | ||
| const DEFAULT_GOP_INTERVAL_US = 2e6; | ||
| function findGopIndex(timeUs, intervalUs) { | ||
| const interval = intervalUs > 0 ? intervalUs : DEFAULT_GOP_INTERVAL_US; | ||
| return Math.floor(timeUs / interval); | ||
| } | ||
| function findFrameIndex(gop, timeUs, toleranceUs = DEFAULT_FRAME_TOLERANCE_US) { | ||
| if (gop.frames.length === 0) { | ||
| return -1; | ||
| } | ||
| let left = 0; | ||
| let right = gop.frames.length - 1; | ||
| let candidate = -1; | ||
| while (left <= right) { | ||
| const mid = Math.floor((left + right) / 2); | ||
| const frame = gop.frames[mid]; | ||
| if (!frame) { | ||
| break; | ||
| } | ||
| const frameTime = getFrameTimestamp(frame, gop, mid); | ||
| if (frameTime === timeUs) { | ||
| return mid; | ||
| } | ||
| if (frameTime < timeUs) { | ||
| candidate = mid; | ||
| left = mid + 1; | ||
| } else { | ||
| right = mid - 1; | ||
| } | ||
| } | ||
| const matches = (index) => { | ||
| if (index < 0 || index >= gop.frames.length) { | ||
| return false; | ||
| } | ||
| const frame = gop.frames[index]; | ||
| if (!frame) { | ||
| return false; | ||
| } | ||
| const frameTime = getFrameTimestamp(frame, gop, index); | ||
| const frameDuration = getFrameDuration(frame, gop); | ||
| return isTimestampWithinFrame(timeUs, frameTime, frameDuration, toleranceUs); | ||
| }; | ||
| if (candidate !== -1 && matches(candidate)) { | ||
| return candidate; | ||
| } | ||
| const nextIndex = candidate === -1 ? 0 : Math.min(candidate + 1, gop.frames.length - 1); | ||
| if (matches(nextIndex)) { | ||
| return nextIndex; | ||
| } | ||
| if (candidate > 0 && matches(candidate - 1)) { | ||
| return candidate - 1; | ||
| } | ||
| return -1; | ||
| } | ||
| function getFrameTimestamp(frame, gop, index) { | ||
| if (typeof frame.timestampUs === "number") { | ||
| return frame.timestampUs; | ||
| } | ||
| if (gop.frames.length > 1 && gop.durationUs > 0) { | ||
| const approximateInterval = Math.round(gop.durationUs / gop.frames.length); | ||
| return gop.startUs + index * approximateInterval; | ||
| } | ||
| return gop.startUs; | ||
| } | ||
| function getFrameDuration(frame, gop) { | ||
| if (typeof frame.durationUs === "number" && frame.durationUs > 0) { | ||
| return frame.durationUs; | ||
| } | ||
| if (gop.frames.length > 1 && gop.durationUs > 0) { | ||
| return Math.round(gop.durationUs / gop.frames.length); | ||
| } | ||
| return gop.durationUs; | ||
| } | ||
| export { | ||
| findFrameIndex, | ||
| findGopIndex | ||
| }; | ||
| //# sourceMappingURL=gop-utils.js.map |
| {"version":3,"file":"gop-utils.js","sources":["../../../src/cache/l1/gop-utils.ts"],"sourcesContent":["import type { TimeUs } from '../../model/types';\nimport type { GOP } from '../types';\nimport { RcFrame } from '../../model';\nimport { DEFAULT_FRAME_TOLERANCE_US, isTimestampWithinFrame } from '../../utils/time-utils';\n\nconst DEFAULT_GOP_INTERVAL_US = 2_000_000;\n\nexport function computeGopRange(\n startUs: TimeUs,\n endUs: TimeUs,\n intervalUs: number\n): { startIndex: number; endIndex: number } {\n const startIndex = findGopIndex(startUs, intervalUs);\n const adjustedEnd = endUs > startUs ? endUs - 1 : endUs;\n const endIndex = findGopIndex(adjustedEnd, intervalUs);\n return {\n startIndex,\n endIndex: Math.max(startIndex, endIndex),\n };\n}\n\nexport function findGopIndex(timeUs: TimeUs, intervalUs: number): number {\n const interval = intervalUs > 0 ? intervalUs : DEFAULT_GOP_INTERVAL_US;\n return Math.floor(timeUs / interval);\n}\n\nexport function findFrameIndex(\n gop: GOP,\n timeUs: TimeUs,\n toleranceUs: TimeUs = DEFAULT_FRAME_TOLERANCE_US\n): number {\n if (gop.frames.length === 0) {\n return -1;\n }\n\n let left = 0;\n let right = gop.frames.length - 1;\n let candidate = -1;\n\n while (left <= right) {\n const mid = Math.floor((left + right) / 2);\n const frame = gop.frames[mid];\n if (!frame) {\n break;\n }\n\n const frameTime = getFrameTimestamp(frame, gop, mid);\n\n if (frameTime === timeUs) {\n return mid;\n }\n\n if (frameTime < timeUs) {\n candidate = mid;\n left = mid + 1;\n } else {\n right = mid - 1;\n }\n }\n\n const matches = (index: number): boolean => {\n if (index < 0 || index >= gop.frames.length) {\n return false;\n }\n\n const frame = gop.frames[index];\n if (!frame) {\n return false;\n }\n\n const frameTime = getFrameTimestamp(frame, gop, index);\n const frameDuration = getFrameDuration(frame, gop);\n return isTimestampWithinFrame(timeUs, frameTime, frameDuration, toleranceUs);\n };\n\n if (candidate !== -1 && matches(candidate)) {\n return candidate;\n }\n\n const nextIndex = candidate === -1 ? 0 : Math.min(candidate + 1, gop.frames.length - 1);\n if (matches(nextIndex)) {\n return nextIndex;\n }\n\n if (candidate > 0 && matches(candidate - 1)) {\n return candidate - 1;\n }\n\n return -1;\n}\n\nfunction getFrameTimestamp(frame: RcFrame, gop: GOP, index: number): TimeUs {\n if (typeof frame.timestampUs === 'number') {\n return frame.timestampUs;\n }\n\n if (gop.frames.length > 1 && gop.durationUs > 0) {\n const approximateInterval = Math.round(gop.durationUs / gop.frames.length);\n return gop.startUs + index * approximateInterval;\n }\n\n return gop.startUs;\n}\n\nfunction getFrameDuration(frame: RcFrame, gop: GOP): TimeUs {\n if (typeof frame.durationUs === 'number' && frame.durationUs > 0) {\n return frame.durationUs;\n }\n\n if (gop.frames.length > 1 && gop.durationUs > 0) {\n return Math.round(gop.durationUs / gop.frames.length);\n }\n\n return gop.durationUs;\n}\n"],"names":[],"mappings":";AAKA,MAAM,0BAA0B;AAgBzB,SAAS,aAAa,QAAgB,YAA4B;AACvE,QAAM,WAAW,aAAa,IAAI,aAAa;AAC/C,SAAO,KAAK,MAAM,SAAS,QAAQ;AACrC;AAEO,SAAS,eACd,KACA,QACA,cAAsB,4BACd;AACR,MAAI,IAAI,OAAO,WAAW,GAAG;AAC3B,WAAO;AAAA,EACT;AAEA,MAAI,OAAO;AACX,MAAI,QAAQ,IAAI,OAAO,SAAS;AAChC,MAAI,YAAY;AAEhB,SAAO,QAAQ,OAAO;AACpB,UAAM,MAAM,KAAK,OAAO,OAAO,SAAS,CAAC;AACzC,UAAM,QAAQ,IAAI,OAAO,GAAG;AAC5B,QAAI,CAAC,OAAO;AACV;AAAA,IACF;AAEA,UAAM,YAAY,kBAAkB,OAAO,KAAK,GAAG;AAEnD,QAAI,cAAc,QAAQ;AACxB,aAAO;AAAA,IACT;AAEA,QAAI,YAAY,QAAQ;AACtB,kBAAY;AACZ,aAAO,MAAM;AAAA,IACf,OAAO;AACL,cAAQ,MAAM;AAAA,IAChB;AAAA,EACF;AAEA,QAAM,UAAU,CAAC,UAA2B;AAC1C,QAAI,QAAQ,KAAK,SAAS,IAAI,OAAO,QAAQ;AAC3C,aAAO;AAAA,IACT;AAEA,UAAM,QAAQ,IAAI,OAAO,KAAK;AAC9B,QAAI,CAAC,OAAO;AACV,aAAO;AAAA,IACT;AAEA,UAAM,YAAY,kBAAkB,OAAO,KAAK,KAAK;AACrD,UAAM,gBAAgB,iBAAiB,OAAO,GAAG;AACjD,WAAO,uBAAuB,QAAQ,WAAW,eAAe,WAAW;AAAA,EAC7E;AAEA,MAAI,cAAc,MAAM,QAAQ,SAAS,GAAG;AAC1C,WAAO;AAAA,EACT;AAEA,QAAM,YAAY,cAAc,KAAK,IAAI,KAAK,IAAI,YAAY,GAAG,IAAI,OAAO,SAAS,CAAC;AACtF,MAAI,QAAQ,SAAS,GAAG;AACtB,WAAO;AAAA,EACT;AAEA,MAAI,YAAY,KAAK,QAAQ,YAAY,CAAC,GAAG;AAC3C,WAAO,YAAY;AAAA,EACrB;AAEA,SAAO;AACT;AAEA,SAAS,kBAAkB,OAAgB,KAAU,OAAuB;AAC1E,MAAI,OAAO,MAAM,gBAAgB,UAAU;AACzC,WAAO,MAAM;AAAA,EACf;AAEA,MAAI,IAAI,OAAO,SAAS,KAAK,IAAI,aAAa,GAAG;AAC/C,UAAM,sBAAsB,KAAK,MAAM,IAAI,aAAa,IAAI,OAAO,MAAM;AACzE,WAAO,IAAI,UAAU,QAAQ;AAAA,EAC/B;AAEA,SAAO,IAAI;AACb;AAEA,SAAS,iBAAiB,OAAgB,KAAkB;AAC1D,MAAI,OAAO,MAAM,eAAe,YAAY,MAAM,aAAa,GAAG;AAChE,WAAO,MAAM;AAAA,EACf;AAEA,MAAI,IAAI,OAAO,SAAS,KAAK,IAAI,aAAa,GAAG;AAC/C,WAAO,KAAK,MAAM,IAAI,aAAa,IAAI,OAAO,MAAM;AAAA,EACtD;AAEA,SAAO,IAAI;AACb;"} |
| class BaseDecoder { | ||
| decoder; | ||
| config; | ||
| controller = null; | ||
| constructor(config) { | ||
| this.config = config; | ||
| } | ||
| async initialize() { | ||
| if (this.decoder?.state === "configured") { | ||
| return; | ||
| } | ||
| const isSupported = await this.isConfigSupported(this.config); | ||
| if (!isSupported.supported) { | ||
| throw new Error( | ||
| `Codec not supported: ${this.config.codecString || this.config.codec}` | ||
| ); | ||
| } | ||
| this.decoder = this.createDecoder({ | ||
| output: this.handleOutput.bind(this), | ||
| error: this.handleError.bind(this) | ||
| }); | ||
| await this.configureDecoder(this.config); | ||
| } | ||
| async reconfigure(config) { | ||
| this.config = { ...this.config, ...config }; | ||
| if (!this.decoder) { | ||
| await this.initialize(); | ||
| return; | ||
| } | ||
| if (this.decoder.state === "configured") { | ||
| await this.decoder.flush(); | ||
| } | ||
| const isSupported = await this.isConfigSupported(this.config); | ||
| if (!isSupported.supported) { | ||
| throw new Error( | ||
| `New configuration not supported: ${this.config.codecString || this.config.codec}` | ||
| ); | ||
| } | ||
| await this.configureDecoder(this.config); | ||
| } | ||
| async flush() { | ||
| if (!this.decoder) return; | ||
| await this.decoder.flush(); | ||
| } | ||
| async reset() { | ||
| if (!this.decoder) return; | ||
| this.decoder.reset(); | ||
| this.onReset(); | ||
| } | ||
| async close() { | ||
| if (!this.decoder) return; | ||
| if (this.decoder.state === "configured") { | ||
| await this.decoder.flush(); | ||
| } | ||
| this.decoder.close(); | ||
| this.decoder = void 0; | ||
| } | ||
| get isReady() { | ||
| return this.decoder?.state === "configured"; | ||
| } | ||
| get queueSize() { | ||
| return this.decoder?.decodeQueueSize ?? 0; | ||
| } | ||
| handleOutput(data) { | ||
| if (!this.controller) { | ||
| this.closeIfPossible(data); | ||
| return; | ||
| } | ||
| try { | ||
| this.controller.enqueue(data); | ||
| } catch (error) { | ||
| if (error instanceof TypeError) { | ||
| this.closeIfPossible(data); | ||
| return; | ||
| } | ||
| throw error; | ||
| } | ||
| } | ||
| handleError(error) { | ||
| console.error(`${this.getDecoderType()} decoder error:`, error); | ||
| this.controller?.error(error); | ||
| } | ||
| closeIfPossible(data) { | ||
| data?.close?.(); | ||
| data?.frame?.close?.(); | ||
| } | ||
| onReset() { | ||
| } | ||
| createStream() { | ||
| return new TransformStream( | ||
| { | ||
| start: async (controller) => { | ||
| this.controller = controller; | ||
| if (!this.isReady) { | ||
| await this.initialize(); | ||
| } | ||
| }, | ||
| transform: async (input) => { | ||
| if (!this.decoder || this.decoder.state !== "configured") { | ||
| throw new Error("Decoder not configured"); | ||
| } | ||
| if (this.decoder.decodeQueueSize >= this.decodeQueueThreshold) { | ||
| await new Promise((resolve) => { | ||
| const check = () => { | ||
| if (!this.decoder || this.decoder.decodeQueueSize < this.decodeQueueThreshold - 1) { | ||
| resolve(); | ||
| } else { | ||
| setTimeout(check, 10); | ||
| } | ||
| }; | ||
| check(); | ||
| }); | ||
| } | ||
| this.decode(input); | ||
| }, | ||
| flush: async () => { | ||
| await this.flush(); | ||
| } | ||
| }, | ||
| { | ||
| highWaterMark: this.highWaterMark, | ||
| size: () => 1 | ||
| } | ||
| ); | ||
| } | ||
| } | ||
| export { | ||
| BaseDecoder as B | ||
| }; | ||
| //# sourceMappingURL=BaseDecoder.Bk26nCBk.js.map |
| {"version":3,"file":"BaseDecoder.Bk26nCBk.js","sources":["../../src/stages/decode/BaseDecoder.ts"],"sourcesContent":["/**\n * Base decoder class abstracting common WebCodecs decoder workflow.\n * Mirrors BaseEncoder but for decoding path.\n */\nimport { AudioDecoderConfig, DecodedVideoFrame, VideoDecoderConfig } from './types';\n\nexport abstract class BaseDecoder<\n TDecoder extends VideoDecoder | AudioDecoder,\n TConfig extends VideoDecoderConfig | AudioDecoderConfig,\n TInput extends EncodedVideoChunk | EncodedAudioChunk,\n TOutput extends VideoFrame | AudioData | DecodedVideoFrame,\n> {\n protected decoder?: TDecoder;\n protected config: TConfig;\n protected controller: TransformStreamDefaultController<TOutput> | null = null;\n\n constructor(config: TConfig) {\n this.config = config;\n }\n\n async initialize(): Promise<void> {\n if (this.decoder?.state === 'configured') {\n return;\n }\n const isSupported = await this.isConfigSupported(this.config);\n if (!isSupported.supported) {\n throw new Error(\n `Codec not supported: ${(this.config as any).codecString || (this.config as any).codec}`\n );\n }\n\n this.decoder = this.createDecoder({\n output: this.handleOutput.bind(this),\n error: this.handleError.bind(this),\n });\n\n await this.configureDecoder(this.config);\n }\n\n async reconfigure(config: Partial<TConfig>): Promise<void> {\n this.config = { ...this.config, ...config } as TConfig;\n\n if (!this.decoder) {\n await this.initialize();\n return;\n }\n\n // Flush before reconfiguring\n if (this.decoder.state === 'configured') {\n await this.decoder.flush();\n }\n\n const isSupported = await this.isConfigSupported(this.config);\n if (!isSupported.supported) {\n throw new Error(\n `New configuration not supported: ${(this.config as any).codecString || (this.config as any).codec}`\n );\n }\n\n await this.configureDecoder(this.config);\n }\n\n async flush(): Promise<void> {\n if (!this.decoder) return;\n await this.decoder.flush();\n }\n\n async reset(): Promise<void> {\n if (!this.decoder) return;\n this.decoder.reset();\n this.onReset();\n }\n\n async close(): Promise<void> {\n if (!this.decoder) return;\n\n if (this.decoder.state === 'configured') {\n await this.decoder.flush();\n }\n\n this.decoder.close();\n this.decoder = undefined;\n }\n\n get isReady(): boolean {\n return this.decoder?.state === 'configured';\n }\n\n get queueSize(): number {\n return (this.decoder as any)?.decodeQueueSize ?? 0;\n }\n\n protected handleOutput(data: TOutput): void {\n if (!this.controller) {\n this.closeIfPossible(data);\n return;\n }\n\n try {\n this.controller.enqueue(data);\n } catch (error) {\n // Stream closed or errored - silently drop frame\n if (error instanceof TypeError) {\n this.closeIfPossible(data);\n return;\n }\n\n throw error;\n }\n }\n\n protected handleError(error: DOMException): void {\n console.error(`${this.getDecoderType()} decoder error:`, error);\n this.controller?.error(error);\n }\n\n private closeIfPossible(data: TOutput): void {\n (data as any)?.close?.();\n (data as DecodedVideoFrame)?.frame?.close?.();\n }\n\n // Abstracts\n protected abstract isConfigSupported(config: TConfig): Promise<{ supported: boolean }>;\n protected abstract createDecoder(init: DecoderInit): TDecoder;\n protected abstract configureDecoder(config: TConfig): Promise<void>;\n protected abstract getDecoderType(): string;\n protected abstract decode(input: TInput): void;\n protected onReset(): void {}\n\n // Backpressure\n protected abstract readonly highWaterMark: number;\n protected abstract readonly decodeQueueThreshold: number;\n\n createStream(): TransformStream<TInput, TOutput> {\n return new TransformStream<TInput, TOutput>(\n {\n start: async (controller) => {\n this.controller = controller;\n if (!this.isReady) {\n await this.initialize();\n }\n },\n transform: async (input) => {\n if (!this.decoder || this.decoder.state !== 'configured') {\n throw new Error('Decoder not configured');\n }\n // backpressure\n if ((this.decoder as any).decodeQueueSize >= this.decodeQueueThreshold) {\n await new Promise<void>((resolve) => {\n const check = () => {\n if (\n !this.decoder ||\n (this.decoder as any).decodeQueueSize < this.decodeQueueThreshold - 1\n ) {\n resolve();\n } else {\n setTimeout(check, 10);\n }\n };\n check();\n });\n }\n this.decode(input);\n },\n flush: async () => {\n await this.flush();\n },\n },\n {\n highWaterMark: this.highWaterMark,\n size: () => 1,\n }\n );\n }\n}\n\ninterface DecoderInit {\n output: (data: any) => void;\n error: (error: DOMException) => void;\n}\n"],"names":[],"mappings":"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"} |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
| import { W as WorkerChannel, a as WorkerMessageType, b as WorkerState } from "../../WorkerChannel.CE5euh3R.js"; | ||
| import { B as BaseDecoder } from "../../BaseDecoder.Bk26nCBk.js"; | ||
| class AudioChunkDecoder extends BaseDecoder { | ||
| // Default values | ||
| static DEFAULT_HIGH_WATER_MARK = 20; | ||
| static DEFAULT_DECODE_QUEUE_THRESHOLD = 16; | ||
| // Exposed properties | ||
| trackId; | ||
| // Backpressure configuration | ||
| highWaterMark; | ||
| decodeQueueThreshold; | ||
| // Buffering support for delayed configuration | ||
| bufferedChunks = []; | ||
| isProcessingBuffer = false; | ||
| constructor(trackId, config) { | ||
| super(config); | ||
| this.trackId = trackId; | ||
| this.highWaterMark = config?.backpressure?.highWaterMark ?? AudioChunkDecoder.DEFAULT_HIGH_WATER_MARK; | ||
| this.decodeQueueThreshold = AudioChunkDecoder.DEFAULT_DECODE_QUEUE_THRESHOLD; | ||
| } | ||
| // Computed properties | ||
| get isConfigured() { | ||
| return this.isReady; | ||
| } | ||
| get state() { | ||
| return this.decoder?.state || "unconfigured"; | ||
| } | ||
| /** | ||
| * Update configuration - can be called before or after initialization | ||
| */ | ||
| async updateConfig(config) { | ||
| if (!this.isReady && config.codec) { | ||
| await this.configure(config); | ||
| await this.processBufferedChunks(); | ||
| return; | ||
| } | ||
| } | ||
| // Override createStream to handle buffering | ||
| createStream() { | ||
| return new TransformStream( | ||
| { | ||
| start: async (controller) => { | ||
| this.controller = controller; | ||
| if (this.config?.codec && !this.isReady) { | ||
| await this.initialize(); | ||
| } | ||
| }, | ||
| transform: async (chunk) => { | ||
| if (!this.isReady) { | ||
| this.bufferedChunks.push(chunk); | ||
| return; | ||
| } | ||
| if (this.isProcessingBuffer) { | ||
| this.bufferedChunks.push(chunk); | ||
| return; | ||
| } | ||
| await this.processChunk(chunk); | ||
| }, | ||
| flush: async () => { | ||
| if (this.isReady) { | ||
| await this.flush(); | ||
| } | ||
| } | ||
| }, | ||
| { | ||
| highWaterMark: this.highWaterMark, | ||
| size: () => 1 | ||
| } | ||
| ); | ||
| } | ||
| /** | ||
| * Process a single chunk (extracted from transform for reuse) | ||
| */ | ||
| async processChunk(chunk) { | ||
| if (!this.decoder) { | ||
| throw new Error("Decoder not initialized"); | ||
| } | ||
| if (this.decoder.state !== "configured") { | ||
| console.error("[AudioChunkDecoder] Decoder in unexpected state:", this.decoder.state); | ||
| throw new Error(`Decoder not configured, state: ${this.decoder.state}`); | ||
| } | ||
| if (this.decoder.decodeQueueSize >= this.decodeQueueThreshold) { | ||
| await new Promise((resolve) => { | ||
| const check = () => { | ||
| if (!this.decoder || this.decoder.decodeQueueSize < this.decodeQueueThreshold - 1) { | ||
| resolve(); | ||
| } else { | ||
| setTimeout(check, 20); | ||
| } | ||
| }; | ||
| check(); | ||
| }); | ||
| } | ||
| try { | ||
| this.decode(chunk); | ||
| } catch (error) { | ||
| console.error(`[AudioChunkDecoder] decode error:`, error); | ||
| throw error; | ||
| } | ||
| } | ||
| // Implement abstract methods | ||
| async isConfigSupported(config) { | ||
| const result = await AudioDecoder.isConfigSupported({ | ||
| codec: config.codec, | ||
| sampleRate: config.sampleRate, | ||
| numberOfChannels: config.numberOfChannels | ||
| }); | ||
| return { supported: result.supported ?? false }; | ||
| } | ||
| createDecoder(init) { | ||
| return new AudioDecoder(init); | ||
| } | ||
| getDecoderType() { | ||
| return "Audio"; | ||
| } | ||
| async configureDecoder(config) { | ||
| if (!this.decoder) return; | ||
| await this.decoder.configure({ | ||
| codec: config.codec, | ||
| sampleRate: config.sampleRate, | ||
| numberOfChannels: config.numberOfChannels, | ||
| ...config.description && { description: config.description } | ||
| }); | ||
| } | ||
| decode(chunk) { | ||
| this.decoder?.decode(chunk); | ||
| } | ||
| /** | ||
| * Configure the decoder with codec info (can be called after creation) | ||
| */ | ||
| async configure(config) { | ||
| if (this.isReady) { | ||
| await this.reconfigure(config); | ||
| return; | ||
| } | ||
| this.config = config; | ||
| await this.initialize(); | ||
| } | ||
| /** | ||
| * Process any buffered chunks after configuration | ||
| */ | ||
| async processBufferedChunks() { | ||
| if (!this.isReady || this.bufferedChunks.length === 0) { | ||
| return; | ||
| } | ||
| this.isProcessingBuffer = true; | ||
| const chunks = [...this.bufferedChunks]; | ||
| this.bufferedChunks = []; | ||
| for (const chunk of chunks) { | ||
| try { | ||
| await this.processChunk(chunk); | ||
| } catch (error) { | ||
| console.error("[AudioChunkDecoder] Error processing buffered chunk:", error); | ||
| } | ||
| } | ||
| this.isProcessingBuffer = false; | ||
| if (this.bufferedChunks.length > 0) { | ||
| await this.processBufferedChunks(); | ||
| } | ||
| } | ||
| // Override close to clean up buffered chunks | ||
| async close() { | ||
| this.bufferedChunks = []; | ||
| await super.close(); | ||
| } | ||
| } | ||
| const normalizeDescription = (desc) => { | ||
| if (!desc) return void 0; | ||
| if (desc instanceof ArrayBuffer) return desc; | ||
| const view = desc; | ||
| return view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength); | ||
| }; | ||
| class AudioDecodeWorker { | ||
| channel; | ||
| decoder = null; | ||
| clipId = ""; | ||
| trackId = ""; | ||
| defaultConfig = {}; | ||
| trackMetadata = null; | ||
| upstreamPort = null; | ||
| constructor() { | ||
| this.channel = new WorkerChannel(self, { | ||
| name: "AudioDecodeWorker", | ||
| timeout: 3e4 | ||
| }); | ||
| this.setupHandlers(); | ||
| } | ||
| setupHandlers() { | ||
| this.channel.registerHandler("configure", this.handleConfigure.bind(this)); | ||
| this.channel.registerHandler("connect", this.handleConnect.bind(this)); | ||
| this.channel.registerHandler("flush", this.handleFlush.bind(this)); | ||
| this.channel.registerHandler("reset", this.handleReset.bind(this)); | ||
| this.channel.registerHandler("get_stats", this.handleGetStats.bind(this)); | ||
| this.channel.registerHandler(WorkerMessageType.Dispose, this.handleDispose.bind(this)); | ||
| } | ||
| async handleConnect(payload) { | ||
| const { port, direction, sessionId, trackId } = payload; | ||
| if (direction === "upstream") { | ||
| this.upstreamPort = port; | ||
| this.clipId = sessionId || "default"; | ||
| this.trackId = trackId || sessionId || "default"; | ||
| const channel = new WorkerChannel(port, { | ||
| name: "Demux-AudioDecode", | ||
| timeout: 3e4 | ||
| }); | ||
| channel.receiveStream((stream, metadata) => { | ||
| this.handleReceiveStream(stream, { | ||
| ...metadata, | ||
| clipStartUs: payload.clipStartUs, | ||
| clipDurationUs: payload.clipDurationUs | ||
| }); | ||
| }); | ||
| channel.registerHandler("configure", this.handleConfigure.bind(this)); | ||
| } | ||
| return { success: true }; | ||
| } | ||
| async handleConfigure(payload) { | ||
| const { sessionId, streamType, codec, sampleRate, numberOfChannels, description, config } = payload; | ||
| if (sessionId && streamType === "audio") { | ||
| Object.assign(this.defaultConfig, { | ||
| codec, | ||
| sampleRate, | ||
| numberOfChannels, | ||
| description: normalizeDescription(description) | ||
| }); | ||
| if (this.decoder) { | ||
| await this.decoder.updateConfig({ | ||
| codec, | ||
| sampleRate, | ||
| numberOfChannels, | ||
| description: normalizeDescription(description) | ||
| }); | ||
| } | ||
| return { success: true }; | ||
| } | ||
| if (config?.audio) { | ||
| Object.assign(this.defaultConfig, config.audio); | ||
| if (this.decoder) { | ||
| await this.decoder.updateConfig(config.audio); | ||
| } | ||
| } | ||
| this.channel.state = WorkerState.Ready; | ||
| return { success: true }; | ||
| } | ||
| async handleReceiveStream(stream, metadata) { | ||
| const sessionId = metadata?.sessionId || this.clipId; | ||
| const trackId = metadata?.trackId || this.trackId; | ||
| if (!this.decoder) { | ||
| const decoderConfig = { | ||
| ...this.defaultConfig, | ||
| codec: metadata?.codec ?? this.defaultConfig.codec, | ||
| sampleRate: metadata?.sampleRate ?? this.defaultConfig.sampleRate, | ||
| numberOfChannels: metadata?.numberOfChannels ?? this.defaultConfig.numberOfChannels, | ||
| description: normalizeDescription(metadata?.description) ?? this.defaultConfig.description | ||
| }; | ||
| this.decoder = new AudioChunkDecoder(trackId, decoderConfig); | ||
| } | ||
| this.trackMetadata = { | ||
| clipId: this.clipId, | ||
| config: this.extractTrackConfig(metadata?.runtimeConfig), | ||
| sampleRate: metadata?.sampleRate, | ||
| numberOfChannels: metadata?.numberOfChannels, | ||
| type: metadata?.trackType ?? "other" | ||
| }; | ||
| const transform = this.decoder.createStream(); | ||
| this.channel.sendStream(transform.readable, { | ||
| streamType: "audio", | ||
| sessionId, | ||
| trackId, | ||
| clipStartUs: metadata?.clipStartUs ?? 0, | ||
| clipDurationUs: metadata?.clipDurationUs ?? 0, | ||
| trackMetadata: this.trackMetadata | ||
| }); | ||
| stream.pipeTo(transform.writable).catch((error) => console.error("[AudioDecodeWorker] Audio stream pipe error:", error)); | ||
| } | ||
| extractTrackConfig(config) { | ||
| return { | ||
| startTimeUs: config?.startTimeUs ?? 0, | ||
| durationUs: config?.durationUs, | ||
| volume: config?.volume ?? 1, | ||
| fadeIn: config?.fadeIn, | ||
| fadeOut: config?.fadeOut, | ||
| effects: config?.effects ?? [], | ||
| duckingTag: config?.duckingTag | ||
| }; | ||
| } | ||
| async handleFlush() { | ||
| if (this.decoder) { | ||
| await this.decoder.flush(); | ||
| } | ||
| return { success: true }; | ||
| } | ||
| async handleReset() { | ||
| if (this.decoder) { | ||
| await this.decoder.reset(); | ||
| } | ||
| this.channel.notify("reset_complete", { | ||
| type: "audio" | ||
| }); | ||
| return { success: true }; | ||
| } | ||
| async handleGetStats() { | ||
| if (!this.decoder) { | ||
| return {}; | ||
| } | ||
| return { | ||
| audio: { | ||
| clipId: this.clipId, | ||
| trackId: this.trackId, | ||
| configured: this.decoder.isConfigured, | ||
| queueSize: this.decoder.queueSize, | ||
| state: this.decoder.state | ||
| } | ||
| }; | ||
| } | ||
| async handleDispose() { | ||
| if (this.decoder) { | ||
| await this.decoder.close(); | ||
| this.decoder = null; | ||
| } | ||
| this.upstreamPort?.close(); | ||
| this.upstreamPort = null; | ||
| this.trackMetadata = null; | ||
| this.channel.state = WorkerState.Disposed; | ||
| return { success: true }; | ||
| } | ||
| } | ||
| const worker = new AudioDecodeWorker(); | ||
| self.addEventListener("beforeunload", () => { | ||
| worker["handleDispose"](); | ||
| }); | ||
| const audioDecode_worker = null; | ||
| export { | ||
| AudioDecodeWorker, | ||
| audioDecode_worker as default | ||
| }; | ||
| //# sourceMappingURL=audio-decode.worker.Czb1P66a.js.map |
| {"version":3,"file":"audio-decode.worker.Czb1P66a.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 // 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","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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"} |
| import { W as WorkerChannel, a as WorkerMessageType, b as WorkerState } from "../../WorkerChannel.CE5euh3R.js"; | ||
| import { B as BaseDecoder } from "../../BaseDecoder.Bk26nCBk.js"; | ||
| class VideoChunkDecoder extends BaseDecoder { | ||
| static DEFAULT_HIGH_WATER_MARK = 4; | ||
| static DEFAULT_DECODE_QUEUE_THRESHOLD = 16; | ||
| trackId; | ||
| highWaterMark; | ||
| decodeQueueThreshold; | ||
| // GOP tracking (serial is derived from keyframe timestamp for idempotency) | ||
| currentGopSerial = -1; | ||
| isCurrentFrameKeyframe = false; | ||
| // Buffering support for delayed configuration | ||
| bufferedChunks = []; | ||
| isProcessingBuffer = false; | ||
| constructor(trackId, config) { | ||
| super(config || {}); | ||
| this.trackId = trackId; | ||
| this.highWaterMark = config?.backpressure?.highWaterMark ?? VideoChunkDecoder.DEFAULT_HIGH_WATER_MARK; | ||
| this.decodeQueueThreshold = config?.backpressure?.decodeQueueThreshold ?? VideoChunkDecoder.DEFAULT_DECODE_QUEUE_THRESHOLD; | ||
| } | ||
| // Computed properties | ||
| get isConfigured() { | ||
| return this.isReady; | ||
| } | ||
| get state() { | ||
| return this.decoder?.state || "unconfigured"; | ||
| } | ||
| /** | ||
| * Update configuration - can be called before or after initialization | ||
| */ | ||
| async updateConfig(config) { | ||
| if (!this.isReady && config.codec) { | ||
| await this.configure(config); | ||
| await this.processBufferedChunks(); | ||
| } | ||
| } | ||
| // Override createStream to handle GOP tracking and buffering | ||
| // Always create new stream for each clip (ReadableStreams can only be consumed once) | ||
| createStream() { | ||
| return new TransformStream( | ||
| { | ||
| start: async (controller) => { | ||
| this.controller = controller; | ||
| if (this.config?.codec && this.config?.description && !this.isReady) { | ||
| await this.initialize(); | ||
| } | ||
| }, | ||
| transform: async (chunk) => { | ||
| if (!this.isReady) { | ||
| this.bufferedChunks.push(chunk); | ||
| return; | ||
| } | ||
| if (this.isProcessingBuffer) { | ||
| this.bufferedChunks.push(chunk); | ||
| return; | ||
| } | ||
| await this.processChunk(chunk); | ||
| }, | ||
| flush: async () => { | ||
| if (this.isReady) { | ||
| await this.flush(); | ||
| } | ||
| } | ||
| }, | ||
| { | ||
| highWaterMark: this.highWaterMark, | ||
| size: () => 1 | ||
| } | ||
| ); | ||
| } | ||
| /** | ||
| * Process a single chunk (extracted from transform for reuse) | ||
| */ | ||
| async processChunk(chunk) { | ||
| if (!this.decoder) { | ||
| throw new Error("Decoder not initialized"); | ||
| } | ||
| if (this.decoder.state !== "configured") { | ||
| console.error("[VideoChunkDecoder] Decoder in unexpected state:", this.decoder.state); | ||
| throw new Error(`Decoder not configured, state: ${this.decoder.state}`); | ||
| } | ||
| if (chunk.type === "key") { | ||
| this.handleKeyFrame(chunk.timestamp); | ||
| } | ||
| if (this.decoder.decodeQueueSize >= this.decodeQueueThreshold) { | ||
| await new Promise((resolve) => { | ||
| const check = () => { | ||
| if (!this.decoder || this.decoder.decodeQueueSize < this.decodeQueueThreshold - 1) { | ||
| resolve(); | ||
| } else { | ||
| setTimeout(check, 20); | ||
| } | ||
| }; | ||
| check(); | ||
| }); | ||
| } | ||
| try { | ||
| this.decode(chunk); | ||
| } catch (error) { | ||
| console.error(`[VideoChunkDecoder] decode error:`, error); | ||
| throw error; | ||
| } | ||
| } | ||
| // Override handleOutput to attach GOP metadata | ||
| handleOutput(frame) { | ||
| const wrappedFrame = { | ||
| frame, | ||
| gopSerial: this.currentGopSerial, | ||
| isKeyframe: this.isCurrentFrameKeyframe, | ||
| timestamp: frame.timestamp | ||
| }; | ||
| this.isCurrentFrameKeyframe = false; | ||
| super.handleOutput(wrappedFrame); | ||
| } | ||
| // Implement abstract methods | ||
| async isConfigSupported(config) { | ||
| const result = await VideoDecoder.isConfigSupported({ | ||
| codec: config.codec, | ||
| codedWidth: config.width, | ||
| codedHeight: config.height | ||
| }); | ||
| return { supported: result.supported ?? false }; | ||
| } | ||
| createDecoder(init) { | ||
| return new VideoDecoder(init); | ||
| } | ||
| getDecoderType() { | ||
| return "Video"; | ||
| } | ||
| async configureDecoder(config) { | ||
| if (!this.decoder) return; | ||
| const decoderConfig = { | ||
| codec: config.codec, | ||
| codedWidth: config.width, | ||
| codedHeight: config.height, | ||
| hardwareAcceleration: config.hardwareAcceleration || "no-preference", | ||
| optimizeForLatency: false, | ||
| ...config.description && { description: config.description }, | ||
| ...config.displayAspectWidth && { displayAspectWidth: config.displayAspectWidth }, | ||
| ...config.displayAspectHeight && { displayAspectHeight: config.displayAspectHeight } | ||
| }; | ||
| this.decoder.configure(decoderConfig); | ||
| } | ||
| decode(chunk) { | ||
| this.decoder?.decode(chunk); | ||
| } | ||
| // Override reset to clear GOP data | ||
| onReset() { | ||
| this.currentGopSerial = -1; | ||
| this.isCurrentFrameKeyframe = false; | ||
| } | ||
| // GOP management methods | ||
| handleKeyFrame(timestamp) { | ||
| this.currentGopSerial = timestamp; | ||
| this.isCurrentFrameKeyframe = true; | ||
| } | ||
| /** | ||
| * Configure the decoder with codec info (can be called after creation) | ||
| */ | ||
| async configure(config) { | ||
| if (this.isReady) { | ||
| await this.reconfigure(config); | ||
| return; | ||
| } | ||
| this.config = config; | ||
| await this.initialize(); | ||
| } | ||
| /** | ||
| * Process any buffered chunks after configuration | ||
| */ | ||
| async processBufferedChunks() { | ||
| if (!this.isReady || this.bufferedChunks.length === 0) { | ||
| return; | ||
| } | ||
| this.isProcessingBuffer = true; | ||
| const chunks = [...this.bufferedChunks]; | ||
| this.bufferedChunks = []; | ||
| for (const chunk of chunks) { | ||
| try { | ||
| await this.processChunk(chunk); | ||
| } catch (error) { | ||
| console.error("[VideoChunkDecoder] Error processing buffered chunk:", error); | ||
| } | ||
| } | ||
| this.isProcessingBuffer = false; | ||
| if (this.bufferedChunks.length > 0) { | ||
| await this.processBufferedChunks(); | ||
| } | ||
| } | ||
| // Override close to clean up GOP data | ||
| async close() { | ||
| this.bufferedChunks = []; | ||
| this.onReset(); | ||
| await super.close(); | ||
| } | ||
| } | ||
| const normalizeDescription = (desc) => { | ||
| if (!desc) return void 0; | ||
| if (desc instanceof ArrayBuffer) return desc; | ||
| const view = desc; | ||
| return view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength); | ||
| }; | ||
| class VideoDecodeWorker { | ||
| channel; | ||
| decoder = null; | ||
| clipId = ""; | ||
| defaultConfig = {}; | ||
| upstreamPort = null; | ||
| downstreamPort = null; | ||
| constructor() { | ||
| this.channel = new WorkerChannel(self, { | ||
| name: "VideoDecodeWorker", | ||
| timeout: 3e4 | ||
| }); | ||
| this.setupHandlers(); | ||
| } | ||
| setupHandlers() { | ||
| this.channel.registerHandler("configure", this.handleConfigure.bind(this)); | ||
| this.channel.registerHandler("connect", this.handleConnect.bind(this)); | ||
| this.channel.registerHandler("flush", this.handleFlush.bind(this)); | ||
| this.channel.registerHandler("reset", this.handleReset.bind(this)); | ||
| this.channel.registerHandler("get_stats", this.handleGetStats.bind(this)); | ||
| this.channel.registerHandler(WorkerMessageType.Dispose, this.handleDispose.bind(this)); | ||
| } | ||
| async handleConnect(payload) { | ||
| const { port, direction, sessionId } = payload; | ||
| if (direction === "upstream") { | ||
| this.upstreamPort = port; | ||
| this.clipId = sessionId || "default"; | ||
| const channel = new WorkerChannel(port, { | ||
| name: "Demux-VideoDecode", | ||
| timeout: 3e4 | ||
| }); | ||
| channel.receiveStream((stream, metadata) => { | ||
| this.handleReceiveStream(stream, { | ||
| ...metadata, | ||
| clipStartUs: payload.clipStartUs, | ||
| clipDurationUs: payload.clipDurationUs | ||
| }); | ||
| }); | ||
| channel.registerHandler("configure", this.handleConfigure.bind(this)); | ||
| } | ||
| if (direction === "downstream") { | ||
| this.downstreamPort = port; | ||
| } | ||
| return { success: true }; | ||
| } | ||
| async handleConfigure(payload) { | ||
| const { sessionId, streamType, codec, width, height, description, config } = payload; | ||
| if (sessionId && streamType === "video") { | ||
| if (this.decoder) { | ||
| await this.decoder.updateConfig({ | ||
| codec, | ||
| width, | ||
| height, | ||
| description: normalizeDescription(description) | ||
| }); | ||
| } | ||
| return { success: true }; | ||
| } | ||
| if (config?.video) { | ||
| Object.assign(this.defaultConfig, config.video); | ||
| if (this.decoder) { | ||
| await this.decoder.updateConfig(config.video); | ||
| } | ||
| } | ||
| this.channel.state = WorkerState.Ready; | ||
| return { success: true }; | ||
| } | ||
| async handleReceiveStream(stream, metadata) { | ||
| const sessionId = metadata?.sessionId || this.clipId; | ||
| if (!this.decoder) { | ||
| this.decoder = new VideoChunkDecoder(sessionId, { | ||
| ...this.defaultConfig, | ||
| codec: metadata?.codec, | ||
| width: metadata?.width, | ||
| height: metadata?.height, | ||
| description: normalizeDescription(metadata?.description) | ||
| }); | ||
| } | ||
| const transform = this.decoder.createStream(); | ||
| if (this.downstreamPort) { | ||
| const channel = new WorkerChannel(this.downstreamPort, { | ||
| name: "VideoDecode-Compose", | ||
| timeout: 3e4 | ||
| }); | ||
| channel.sendStream(transform.readable, { | ||
| streamType: "video", | ||
| sessionId | ||
| }); | ||
| stream.pipeTo(transform.writable).catch( | ||
| (error) => console.error("[VideoDecodeWorker] Video stream pipe error:", sessionId, error) | ||
| ); | ||
| } | ||
| } | ||
| async handleFlush() { | ||
| if (this.decoder) { | ||
| await this.decoder.flush(); | ||
| } | ||
| return { success: true }; | ||
| } | ||
| async handleReset() { | ||
| if (this.decoder) { | ||
| await this.decoder.reset(); | ||
| } | ||
| this.channel.notify("reset_complete", { | ||
| type: "video" | ||
| }); | ||
| return { success: true }; | ||
| } | ||
| async handleGetStats() { | ||
| if (!this.decoder) { | ||
| return {}; | ||
| } | ||
| return { | ||
| video: { | ||
| clipId: this.clipId, | ||
| configured: this.decoder.isConfigured, | ||
| queueSize: this.decoder.queueSize, | ||
| state: this.decoder.state | ||
| } | ||
| }; | ||
| } | ||
| async handleDispose() { | ||
| if (this.decoder) { | ||
| await this.decoder.close(); | ||
| this.decoder = null; | ||
| } | ||
| this.upstreamPort?.close(); | ||
| this.upstreamPort = null; | ||
| this.downstreamPort?.close(); | ||
| this.downstreamPort = null; | ||
| this.channel.state = WorkerState.Disposed; | ||
| return { success: true }; | ||
| } | ||
| } | ||
| const worker = new VideoDecodeWorker(); | ||
| self.addEventListener("beforeunload", () => { | ||
| worker["handleDispose"](); | ||
| }); | ||
| const videoDecode_worker = null; | ||
| export { | ||
| VideoDecodeWorker, | ||
| videoDecode_worker as default | ||
| }; | ||
| //# sourceMappingURL=video-decode.worker.DLX8FRVc.js.map |
| {"version":3,"file":"video-decode.worker.DLX8FRVc.js","sources":["../../../../src/stages/decode/VideoChunkDecoder.ts","../../../../src/stages/decode/video-decode.worker.ts"],"sourcesContent":["import { VideoDecoderConfig } from './types';\nimport { BaseDecoder } from './BaseDecoder';\n\n/**\n * Video decoder with GOP tracking\n * Tracks keyframe boundaries and attaches GOP metadata to decoded frames\n */\nexport class VideoChunkDecoder extends BaseDecoder<\n VideoDecoder,\n VideoDecoderConfig,\n EncodedVideoChunk,\n VideoFrame\n> {\n private static readonly DEFAULT_HIGH_WATER_MARK = 4;\n private static readonly DEFAULT_DECODE_QUEUE_THRESHOLD = 16;\n\n readonly trackId: string;\n\n protected readonly highWaterMark: number;\n protected readonly decodeQueueThreshold: number;\n\n // GOP tracking (serial is derived from keyframe timestamp for idempotency)\n private currentGopSerial = -1;\n private isCurrentFrameKeyframe = false;\n\n // Buffering support for delayed configuration\n private bufferedChunks: EncodedVideoChunk[] = [];\n private isProcessingBuffer: boolean = false;\n\n constructor(trackId: string, config?: Partial<VideoDecoderConfig>) {\n super((config || {}) as VideoDecoderConfig);\n\n this.trackId = trackId;\n this.highWaterMark =\n config?.backpressure?.highWaterMark ?? VideoChunkDecoder.DEFAULT_HIGH_WATER_MARK;\n this.decodeQueueThreshold =\n config?.backpressure?.decodeQueueThreshold ??\n VideoChunkDecoder.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<VideoDecoderConfig>): Promise<void> {\n if (!this.isReady && config.codec) {\n await this.configure(config as VideoDecoderConfig);\n await this.processBufferedChunks();\n }\n }\n\n // Override createStream to handle GOP tracking and buffering\n // Always create new stream for each clip (ReadableStreams can only be consumed once)\n override createStream(): TransformStream<EncodedVideoChunk, VideoFrame> {\n return new TransformStream<EncodedVideoChunk, VideoFrame>(\n {\n start: async (controller) => {\n this.controller = controller;\n // Don't initialize if no codec config yet\n if (this.config?.codec && this.config?.description && !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: EncodedVideoChunk): Promise<void> {\n if (!this.decoder) {\n throw new Error('Decoder not initialized');\n }\n\n if (this.decoder.state !== 'configured') {\n console.error('[VideoChunkDecoder] Decoder in unexpected state:', this.decoder.state);\n throw new Error(`Decoder not configured, state: ${this.decoder.state}`);\n }\n\n // Track GOP boundaries\n if (chunk.type === 'key') {\n this.handleKeyFrame(chunk.timestamp);\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(`[VideoChunkDecoder] decode error:`, error);\n throw error; // Re-throw to propagate\n }\n }\n\n // Override handleOutput to attach GOP metadata\n protected override handleOutput(frame: VideoFrame): void {\n // Wrap frame with GOP metadata as a plain object\n // The frame itself will be transferred, but metadata travels separately\n const wrappedFrame = {\n frame,\n gopSerial: this.currentGopSerial,\n isKeyframe: this.isCurrentFrameKeyframe,\n timestamp: frame.timestamp,\n };\n\n // Reset keyframe flag for subsequent frames\n this.isCurrentFrameKeyframe = false;\n\n // Call parent to enqueue wrapped frame\n super.handleOutput(wrappedFrame as any);\n }\n\n // Implement abstract methods\n protected async isConfigSupported(config: VideoDecoderConfig): Promise<{ supported: boolean }> {\n const result = await VideoDecoder.isConfigSupported({\n codec: config.codec,\n codedWidth: config.width,\n codedHeight: config.height,\n });\n return { supported: result.supported ?? false };\n }\n\n protected createDecoder(init: {\n output: (frame: VideoFrame) => void;\n error: (error: DOMException) => void;\n }): VideoDecoder {\n return new VideoDecoder(init);\n }\n\n protected getDecoderType(): string {\n return 'Video';\n }\n\n protected async configureDecoder(config: VideoDecoderConfig): Promise<void> {\n if (!this.decoder) return;\n\n const decoderConfig = {\n codec: config.codec,\n codedWidth: config.width,\n codedHeight: config.height,\n hardwareAcceleration: config.hardwareAcceleration || 'no-preference',\n optimizeForLatency: false,\n ...(config.description && { description: config.description }),\n ...(config.displayAspectWidth && { displayAspectWidth: config.displayAspectWidth }),\n ...(config.displayAspectHeight && { displayAspectHeight: config.displayAspectHeight }),\n };\n\n this.decoder.configure(decoderConfig as any);\n // console.log('[VideoChunkDecoder] Decoder configured, state:', this.decoder.state);\n }\n\n protected decode(chunk: EncodedVideoChunk): void {\n this.decoder?.decode(chunk);\n }\n\n // Override reset to clear GOP data\n protected override onReset(): void {\n this.currentGopSerial = -1;\n this.isCurrentFrameKeyframe = false;\n }\n\n // GOP management methods\n private handleKeyFrame(timestamp: number): void {\n // Use timestamp as gopSerial for idempotency (same keyframe = same serial)\n this.currentGopSerial = timestamp;\n this.isCurrentFrameKeyframe = true;\n }\n\n /**\n * Configure the decoder with codec info (can be called after creation)\n */\n async configure(config: VideoDecoderConfig): Promise<void> {\n // console.log('[VideoChunkDecoder] Configuring with:', config);\n\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 // console.log('[VideoChunkDecoder] Processing', this.bufferedChunks.length, 'buffered chunks');\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('[VideoChunkDecoder] 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 GOP data\n override async close(): Promise<void> {\n // Clear buffered chunks\n this.bufferedChunks = [];\n\n // Clean up GOP data first\n this.onReset();\n\n // Call parent close\n await super.close();\n }\n}\n","import { WorkerChannel } from '../../worker/WorkerChannel';\nimport { WorkerMessageType, WorkerState } from '../../worker/types';\nimport { VideoChunkDecoder } from './VideoChunkDecoder';\nimport { VideoDecoderConfig } from './types';\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 * VideoDecodeWorker (Clip Local) - Decodes video for a single clip\n * Receives encoded video chunks from VideoDemuxWorker and outputs decoded frames\n *\n * Pipeline: VideoDemuxWorker → VideoDecodeWorker → VideoComposeWorker\n *\n * Features:\n * - Single clip, single VideoDecoder instance (no routing)\n * - GOP-based decoding with metadata\n * - Stream-based processing with backpressure\n * - Lifecycle tied to clip pipeline\n */\nexport class VideoDecodeWorker {\n private channel: WorkerChannel;\n private decoder: VideoChunkDecoder | null = null;\n private clipId: string = '';\n\n private defaultConfig: Partial<VideoDecoderConfig> = {};\n\n private upstreamPort: MessagePort | null = null;\n private downstreamPort: MessagePort | null = null;\n\n constructor() {\n this.channel = new WorkerChannel(self as any, {\n name: 'VideoDecodeWorker',\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?: 'video';\n sessionId?: string;\n clipStartUs?: number;\n clipDurationUs?: number;\n }): Promise<{ success: boolean }> {\n const { port, direction, sessionId } = payload;\n\n if (direction === 'upstream') {\n this.upstreamPort = port;\n this.clipId = sessionId || 'default';\n\n const channel = new WorkerChannel(port, {\n name: 'Demux-VideoDecode',\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 if (direction === 'downstream') {\n this.downstreamPort = port;\n }\n\n return { success: true };\n }\n\n private async handleConfigure(payload: {\n config?: { video?: Partial<VideoDecoderConfig> };\n sessionId?: string;\n streamType?: 'video';\n codec?: string;\n width?: number;\n height?: number;\n description?: ArrayBuffer | Uint8Array;\n }): Promise<{ success: boolean }> {\n const { sessionId, streamType, codec, width, height, description, config } = payload;\n\n if (sessionId && streamType === 'video') {\n if (this.decoder) {\n await this.decoder.updateConfig({\n codec,\n width,\n height,\n description: normalizeDescription(description),\n });\n }\n return { success: true };\n }\n\n if (config?.video) {\n Object.assign(this.defaultConfig, config.video);\n\n if (this.decoder) {\n await this.decoder.updateConfig(config.video);\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\n if (!this.decoder) {\n this.decoder = new VideoChunkDecoder(sessionId, {\n ...this.defaultConfig,\n codec: metadata?.codec,\n width: metadata?.width,\n height: metadata?.height,\n description: normalizeDescription(metadata?.description),\n });\n }\n\n const transform = this.decoder.createStream();\n\n if (this.downstreamPort) {\n const channel = new WorkerChannel(this.downstreamPort, {\n name: 'VideoDecode-Compose',\n timeout: 30000,\n });\n\n channel.sendStream(transform.readable as ReadableStream, {\n streamType: 'video',\n sessionId,\n });\n\n stream\n .pipeTo(transform.writable)\n .catch((error) =>\n console.error('[VideoDecodeWorker] Video stream pipe error:', sessionId, error)\n );\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: 'video',\n });\n\n return { success: true };\n }\n\n private async handleGetStats(): Promise<{ video?: any }> {\n if (!this.decoder) {\n return {};\n }\n\n return {\n video: {\n clipId: this.clipId,\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.downstreamPort?.close();\n this.downstreamPort = null;\n\n this.channel.state = WorkerState.Disposed;\n\n return { success: true };\n }\n}\n\nconst worker = new VideoDecodeWorker();\n\nself.addEventListener('beforeunload', () => {\n worker['handleDispose']();\n});\n\nexport default null;\n"],"names":[],"mappings":"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"} |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
3510569
-1.04%367
-1.08%37233
-1.17%