@meframe/core 0.3.3 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/event/events.d.ts +4 -2
- package/dist/event/events.d.ts.map +1 -1
- package/dist/event/events.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/node_modules/.pnpm/mp4-muxer@5.2.2/node_modules/mp4-muxer/build/mp4-muxer.js.map +1 -0
- package/dist/{medeo-fe/node_modules → node_modules}/.pnpm/mp4box@0.5.4/node_modules/mp4box/dist/mp4box.all.js +2 -2
- package/dist/node_modules/.pnpm/mp4box@0.5.4/node_modules/mp4box/dist/mp4box.all.js.map +1 -0
- package/dist/orchestrator/ExportScheduler.d.ts +2 -3
- package/dist/orchestrator/ExportScheduler.d.ts.map +1 -1
- package/dist/orchestrator/ExportScheduler.js +2 -3
- package/dist/orchestrator/ExportScheduler.js.map +1 -1
- package/dist/orchestrator/Orchestrator.d.ts +2 -1
- package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
- package/dist/orchestrator/Orchestrator.js +26 -16
- package/dist/orchestrator/Orchestrator.js.map +1 -1
- package/dist/orchestrator/VideoClipSession.d.ts +1 -1
- package/dist/orchestrator/VideoClipSession.d.ts.map +1 -1
- package/dist/orchestrator/VideoClipSession.js +2 -2
- package/dist/orchestrator/VideoClipSession.js.map +1 -1
- package/dist/orchestrator/{OnDemandVideoSession.d.ts → VideoWindowDecodeSession.d.ts} +4 -4
- package/dist/orchestrator/VideoWindowDecodeSession.d.ts.map +1 -0
- package/dist/orchestrator/{OnDemandVideoSession.js → VideoWindowDecodeSession.js} +10 -10
- package/dist/orchestrator/VideoWindowDecodeSession.js.map +1 -0
- package/dist/stages/decode/index.d.ts +0 -1
- package/dist/stages/decode/index.d.ts.map +1 -1
- package/dist/{utils/video-decoder-helpers.d.ts → stages/decode/video-decoder.d.ts} +1 -1
- package/dist/stages/decode/video-decoder.d.ts.map +1 -0
- package/dist/{utils/video-decoder-helpers.js → stages/decode/video-decoder.js} +2 -2
- package/dist/stages/decode/video-decoder.js.map +1 -0
- package/dist/stages/mux/MP4Muxer.js +1 -1
- package/dist/utils/mp4box.js +1 -1
- package/dist/worker/WorkerPool.d.ts.map +1 -1
- package/dist/worker/WorkerPool.js +1 -5
- package/dist/worker/WorkerPool.js.map +1 -1
- package/dist/worker/types.d.ts +1 -1
- package/dist/worker/types.d.ts.map +1 -1
- package/dist/worker/types.js.map +1 -1
- package/dist/worker/worker-event-whitelist.d.ts.map +1 -1
- package/dist/worker/worker-manifest.d.ts +2 -2
- package/dist/worker/worker-manifest.js.map +1 -1
- package/dist/workers/WorkerChannel.DQK8rAab.js.map +1 -1
- package/dist/workers/worker-manifest.json +0 -4
- package/package.json +1 -1
- package/dist/medeo-fe/node_modules/.pnpm/mp4-muxer@5.2.2/node_modules/mp4-muxer/build/mp4-muxer.js.map +0 -1
- package/dist/medeo-fe/node_modules/.pnpm/mp4box@0.5.4/node_modules/mp4box/dist/mp4box.all.js.map +0 -1
- package/dist/orchestrator/OnDemandVideoSession.d.ts.map +0 -1
- package/dist/orchestrator/OnDemandVideoSession.js.map +0 -1
- package/dist/stages/decode/VideoChunkDecoder.d.ts +0 -49
- package/dist/stages/decode/VideoChunkDecoder.d.ts.map +0 -1
- package/dist/utils/video-decoder-helpers.d.ts.map +0 -1
- package/dist/utils/video-decoder-helpers.js.map +0 -1
- package/dist/workers/BaseDecoder.CB5XmTpS.js +0 -137
- package/dist/workers/BaseDecoder.CB5XmTpS.js.map +0 -1
- package/dist/workers/MP4Demuxer.DfWiwyjB.js +0 -7396
- package/dist/workers/MP4Demuxer.DfWiwyjB.js.map +0 -1
- package/dist/workers/stages/decode/audio-decode.worker.-DGlQrJD.js +0 -320
- package/dist/workers/stages/decode/audio-decode.worker.-DGlQrJD.js.map +0 -1
- package/dist/workers/stages/decode/video-decode.worker.BnWVUkng.js +0 -334
- package/dist/workers/stages/decode/video-decode.worker.BnWVUkng.js.map +0 -1
- package/dist/workers/stages/demux/audio-demux.worker.D-_LoVqW.js +0 -502
- package/dist/workers/stages/demux/audio-demux.worker.D-_LoVqW.js.map +0 -1
- package/dist/workers/stages/demux/video-demux.worker.BWDrLGni.js +0 -210
- package/dist/workers/stages/demux/video-demux.worker.BWDrLGni.js.map +0 -1
- /package/dist/{medeo-fe/node_modules → node_modules}/.pnpm/mp4-muxer@5.2.2/node_modules/mp4-muxer/build/mp4-muxer.js +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"video-decode.worker.BnWVUkng.js","sources":["../../../../src/utils/platform-utils.ts","../../../../src/stages/decode/VideoChunkDecoder.ts","../../../../src/stages/decode/video-decode.worker.ts"],"sourcesContent":["/**\n * Platform detection utilities with caching for performance\n */\n\nexport interface PlatformInfo {\n isWindows: boolean;\n isMacOS: boolean;\n isLinux: boolean;\n platform: string;\n userAgent: string;\n}\n\n// Cache platform detection result (only detect once)\nlet cachedPlatformInfo: PlatformInfo | null = null;\n\n/**\n * Detect current platform (cached after first call)\n */\nexport function detectPlatform(): PlatformInfo {\n if (cachedPlatformInfo) {\n return cachedPlatformInfo;\n }\n\n const platform = typeof navigator !== 'undefined' ? navigator.platform : '';\n const userAgent = typeof navigator !== 'undefined' ? navigator.userAgent : '';\n\n cachedPlatformInfo = {\n isWindows: /Win/i.test(platform) || /Win/i.test(userAgent),\n isMacOS: /Mac/i.test(platform),\n isLinux: /Linux/i.test(platform),\n platform,\n userAgent,\n };\n\n return cachedPlatformInfo;\n}\n\n/**\n * Check if current platform is Windows (cached)\n */\nexport function isWindows(): boolean {\n return detectPlatform().isWindows;\n}\n\n/**\n * Check if current platform is macOS (cached)\n */\nexport function isMacOS(): boolean {\n return detectPlatform().isMacOS;\n}\n\n/**\n * Check if current platform is Linux (cached)\n */\nexport function isLinux(): boolean {\n return detectPlatform().isLinux;\n}\n\n/**\n * Get platform-recommended hardware acceleration setting for video decoding\n *\n * Background:\n * Windows hardware video decoders (especially with DXVA2/D3D11) may hang indefinitely\n * when calling VideoDecoder.flush() in certain scenarios (frequent seeks, large GOPs).\n * This appears to be a driver/platform-specific issue affecting Chromium's WebCodecs.\n *\n * Related discussions:\n * - https://github.com/w3c/webcodecs/issues\n * - Observed in production on Windows 10/11 with various GPU vendors\n *\n * Workaround:\n * Use software decoding on Windows to avoid flush() hangs, with ~4x slower decode\n * but reliable operation. Other platforms use hardware acceleration by default.\n *\n * @returns Platform-recommended hardware acceleration setting\n */\nexport function getRecommendedHardwareAcceleration(): HardwareAcceleration {\n // Windows: prefer software to avoid flush hang in hardware decoders\n if (isWindows()) {\n return 'prefer-software';\n }\n\n // Other platforms: no preference (let browser choose)\n return 'no-preference';\n}\n\nexport interface BrowserCompatibility {\n webCodecsAvailable: boolean;\n audioWebCodecsAvailable: boolean;\n opfsAvailable: boolean;\n missingFeatures: string[];\n browserInfo: {\n name: string;\n version: string;\n recommended: string;\n };\n}\n\nlet cachedCompatibility: BrowserCompatibility | null = null;\n\n/**\n * Check browser compatibility for WebCodecs and OPFS (sync, < 1ms, cached)\n * Only checks API existence, no file operations\n */\nexport function checkBrowserCompatibility(): BrowserCompatibility {\n if (cachedCompatibility) return cachedCompatibility;\n\n const missingFeatures: string[] = [];\n let webCodecsAvailable = true;\n let audioWebCodecsAvailable = true;\n let opfsAvailable = false;\n\n // Check WebCodecs APIs (sync, < 1ms)\n if (typeof VideoDecoder === 'undefined') {\n missingFeatures.push('VideoDecoder');\n webCodecsAvailable = false;\n }\n if (typeof VideoEncoder === 'undefined') {\n missingFeatures.push('VideoEncoder');\n webCodecsAvailable = false;\n }\n if (typeof VideoFrame === 'undefined') {\n missingFeatures.push('VideoFrame');\n webCodecsAvailable = false;\n }\n if (typeof EncodedVideoChunk === 'undefined') {\n missingFeatures.push('EncodedVideoChunk');\n webCodecsAvailable = false;\n }\n\n // Check Audio WebCodecs APIs (sync, < 1ms)\n if (typeof AudioDecoder === 'undefined') {\n missingFeatures.push('AudioDecoder');\n audioWebCodecsAvailable = false;\n }\n if (typeof AudioEncoder === 'undefined') {\n missingFeatures.push('AudioEncoder');\n audioWebCodecsAvailable = false;\n }\n if (typeof AudioData === 'undefined') {\n missingFeatures.push('AudioData');\n audioWebCodecsAvailable = false;\n }\n if (typeof EncodedAudioChunk === 'undefined') {\n missingFeatures.push('EncodedAudioChunk');\n audioWebCodecsAvailable = false;\n }\n\n // Detect browser (sync, < 1ms)\n const userAgent = typeof navigator !== 'undefined' ? navigator.userAgent : '';\n let browserName = 'Unknown';\n let browserVersion = 'Unknown';\n let recommended = 'Chrome 94+ / Edge 94+ / Safari 26+';\n\n if (userAgent.includes('Edg/')) {\n browserName = 'Edge';\n const match = userAgent.match(/Edg\\/(\\d+)/);\n browserVersion = match?.[1] ?? 'Unknown';\n recommended = 'Edge 94+';\n } else if (userAgent.includes('Chrome/')) {\n browserName = 'Chrome';\n const match = userAgent.match(/Chrome\\/(\\d+)/);\n browserVersion = match?.[1] ?? 'Unknown';\n recommended = 'Chrome 94+';\n } else if (userAgent.includes('Safari/') && !userAgent.includes('Chrome')) {\n browserName = 'Safari';\n const match = userAgent.match(/Version\\/(\\d+)/);\n browserVersion = match?.[1] ?? 'Unknown';\n recommended = 'Safari 26+';\n } else if (userAgent.includes('Firefox/')) {\n browserName = 'Firefox';\n const match = userAgent.match(/Firefox\\/(\\d+)/);\n browserVersion = match?.[1] ?? 'Unknown';\n recommended = 'Not supported';\n }\n\n // Check OPFS API (sync, no file operations)\n if (webCodecsAvailable) {\n if (\n typeof navigator !== 'undefined' &&\n navigator.storage &&\n typeof navigator.storage.getDirectory === 'function'\n ) {\n // Assume support if the API exists (no file operations performed here).\n opfsAvailable = true;\n } else {\n missingFeatures.push('OPFS');\n }\n }\n\n cachedCompatibility = {\n webCodecsAvailable,\n audioWebCodecsAvailable,\n opfsAvailable,\n missingFeatures,\n browserInfo: { name: browserName, version: browserVersion, recommended },\n };\n\n return cachedCompatibility;\n}\n","import { VideoDecoderConfig } from './types';\nimport { BaseDecoder } from './BaseDecoder';\nimport { getRecommendedHardwareAcceleration } from '../../utils/platform-utils';\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 this.decode(chunk);\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 hardwareAcceleration =\n config.hardwareAcceleration ?? getRecommendedHardwareAcceleration();\n\n const decoderConfig = {\n codec: config.codec,\n codedWidth: config.width,\n codedHeight: config.height,\n hardwareAcceleration,\n optimizeForLatency: true,\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\n // Log when using software decoding for debugging\n if (hardwareAcceleration === 'prefer-software') {\n console.info('[VideoChunkDecoder] Using software decoding for platform compatibility');\n }\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","/**\n * @deprecated VideoDecodeWorker is deprecated and will be removed in a future version.\n *\n * Reason: Export pipeline now uses OnDemandVideoSession (main thread) for decoding.\n * This eliminates the need for EncodedVideoChunk serialization and provides better\n * code reuse with preview's decoding logic.\n *\n * Replacement: OnDemandVideoSession (packages/core/src/orchestrator/OnDemandVideoSession.ts)\n *\n * New pipeline: IndexedVideoSource → OnDemandVideoSession (decode) → ComposeWorker → EncodeWorker\n * All in main thread before ComposeWorker, avoiding worker communication overhead.\n *\n * This file is kept for backward compatibility only and may be removed in the future.\n */\n\nimport { 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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"}
|
|
@@ -1,502 +0,0 @@
|
|
|
1
|
-
import { W as WorkerChannel, a as WorkerMessageType, b as WorkerState } from "../../WorkerChannel.DQK8rAab.js";
|
|
2
|
-
import { M as MP4Demuxer } from "../../MP4Demuxer.DfWiwyjB.js";
|
|
3
|
-
const BITRATE_TABLE = {
|
|
4
|
-
"1-3": [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0],
|
|
5
|
-
"1-2": [0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0],
|
|
6
|
-
"1-1": [0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0],
|
|
7
|
-
"2-3": [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0],
|
|
8
|
-
"2-2": [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0],
|
|
9
|
-
"2-1": [0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0]
|
|
10
|
-
};
|
|
11
|
-
const SAMPLE_RATE_TABLE = {
|
|
12
|
-
1: [44100, 48e3, 32e3],
|
|
13
|
-
2: [22050, 24e3, 16e3],
|
|
14
|
-
2.5: [11025, 12e3, 8e3]
|
|
15
|
-
};
|
|
16
|
-
class MP3FrameParser {
|
|
17
|
-
buffer = new Uint8Array(0);
|
|
18
|
-
config = null;
|
|
19
|
-
timestampUs = 0;
|
|
20
|
-
push(chunk) {
|
|
21
|
-
this.appendToBuffer(chunk);
|
|
22
|
-
this.skipId3Headers();
|
|
23
|
-
const frames = [];
|
|
24
|
-
let configEmitted;
|
|
25
|
-
let offset = 0;
|
|
26
|
-
while (offset <= this.buffer.length - 4) {
|
|
27
|
-
const header = this.parseHeader(this.buffer, offset);
|
|
28
|
-
if (!header) {
|
|
29
|
-
offset += 1;
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
if (offset + header.frameSize > this.buffer.length) {
|
|
33
|
-
break;
|
|
34
|
-
}
|
|
35
|
-
const frameBytes = this.buffer.slice(offset, offset + header.frameSize);
|
|
36
|
-
const durationUs = Math.round(header.samplesPerFrame * 1e6 / header.sampleRate);
|
|
37
|
-
if (!this.config) {
|
|
38
|
-
this.config = {
|
|
39
|
-
codec: "mp3",
|
|
40
|
-
sampleRate: header.sampleRate,
|
|
41
|
-
channels: header.channels,
|
|
42
|
-
bitrateKbps: header.bitrateKbps,
|
|
43
|
-
samplesPerFrame: header.samplesPerFrame
|
|
44
|
-
};
|
|
45
|
-
configEmitted = this.config;
|
|
46
|
-
}
|
|
47
|
-
frames.push({
|
|
48
|
-
data: frameBytes,
|
|
49
|
-
timestampUs: this.timestampUs,
|
|
50
|
-
durationUs
|
|
51
|
-
});
|
|
52
|
-
this.timestampUs += durationUs;
|
|
53
|
-
offset += header.frameSize;
|
|
54
|
-
}
|
|
55
|
-
if (offset > 0) {
|
|
56
|
-
this.buffer = this.buffer.slice(offset);
|
|
57
|
-
}
|
|
58
|
-
return { frames, config: configEmitted };
|
|
59
|
-
}
|
|
60
|
-
flush() {
|
|
61
|
-
const { frames } = this.push(new Uint8Array(0));
|
|
62
|
-
const remainder = this.buffer;
|
|
63
|
-
this.buffer = new Uint8Array(0);
|
|
64
|
-
if (remainder.length) {
|
|
65
|
-
console.warn("[MP3FrameParser] Remaining unparsed bytes:", remainder.length);
|
|
66
|
-
}
|
|
67
|
-
return frames;
|
|
68
|
-
}
|
|
69
|
-
appendToBuffer(chunk) {
|
|
70
|
-
if (chunk.length === 0) {
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
const combined = new Uint8Array(this.buffer.length + chunk.length);
|
|
74
|
-
combined.set(this.buffer, 0);
|
|
75
|
-
combined.set(chunk, this.buffer.length);
|
|
76
|
-
this.buffer = combined;
|
|
77
|
-
}
|
|
78
|
-
skipId3Headers() {
|
|
79
|
-
let offset = 0;
|
|
80
|
-
while (this.buffer.length - offset >= 10) {
|
|
81
|
-
if (this.buffer[offset] === 73 && this.buffer[offset + 1] === 68 && this.buffer[offset + 2] === 51) {
|
|
82
|
-
const size = this.readSynchsafeInteger(this.buffer.subarray(offset + 6, offset + 10));
|
|
83
|
-
const total = size + 10;
|
|
84
|
-
if (offset + total <= this.buffer.length) {
|
|
85
|
-
offset += total;
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
break;
|
|
90
|
-
}
|
|
91
|
-
if (offset > 0) {
|
|
92
|
-
this.buffer = this.buffer.slice(offset);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
parseHeader(buffer, offset) {
|
|
96
|
-
if (offset + 3 >= buffer.length) {
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
if (buffer[offset] !== 255 || ((buffer[offset + 1] ?? 0) & 224) !== 224) {
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
const versionBits = (buffer[offset + 1] ?? 0) >> 3 & 3;
|
|
103
|
-
const layerBits = (buffer[offset + 1] ?? 0) >> 1 & 3;
|
|
104
|
-
const bitrateIndex = (buffer[offset + 2] ?? 0) >> 4 & 15;
|
|
105
|
-
const sampleRateIndex = (buffer[offset + 2] ?? 0) >> 2 & 3;
|
|
106
|
-
const paddingBit = (buffer[offset + 2] ?? 0) >> 1 & 1;
|
|
107
|
-
const channelMode = (buffer[offset + 3] ?? 0) >> 6 & 3;
|
|
108
|
-
const version = this.getVersion(versionBits);
|
|
109
|
-
const layer = this.getLayer(layerBits);
|
|
110
|
-
if (!version || !layer) {
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
const sampleRate = SAMPLE_RATE_TABLE[version]?.[sampleRateIndex] ?? null;
|
|
114
|
-
if (!sampleRate) {
|
|
115
|
-
return null;
|
|
116
|
-
}
|
|
117
|
-
const bitrateKey = `${version === 1 ? 1 : 2}-${layer}`;
|
|
118
|
-
const bitrateKbps = BITRATE_TABLE[bitrateKey]?.[bitrateIndex] ?? null;
|
|
119
|
-
if (bitrateKbps === null) {
|
|
120
|
-
return null;
|
|
121
|
-
}
|
|
122
|
-
const samplesPerFrame = this.getSamplesPerFrame(version, layer);
|
|
123
|
-
const frameSize = this.calculateFrameSize(layer, bitrateKbps, sampleRate, paddingBit);
|
|
124
|
-
if (!frameSize || frameSize < 24) {
|
|
125
|
-
return null;
|
|
126
|
-
}
|
|
127
|
-
const channels = channelMode === 3 ? 1 : 2;
|
|
128
|
-
return {
|
|
129
|
-
frameSize,
|
|
130
|
-
sampleRate,
|
|
131
|
-
channels,
|
|
132
|
-
bitrateKbps: bitrateKbps || null,
|
|
133
|
-
samplesPerFrame
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
getVersion(bits) {
|
|
137
|
-
switch (bits) {
|
|
138
|
-
case 3:
|
|
139
|
-
return 1;
|
|
140
|
-
case 2:
|
|
141
|
-
return 2;
|
|
142
|
-
case 0:
|
|
143
|
-
return 2.5;
|
|
144
|
-
default:
|
|
145
|
-
return null;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
getLayer(bits) {
|
|
149
|
-
switch (bits) {
|
|
150
|
-
case 3:
|
|
151
|
-
return 1;
|
|
152
|
-
case 2:
|
|
153
|
-
return 2;
|
|
154
|
-
case 1:
|
|
155
|
-
return 3;
|
|
156
|
-
default:
|
|
157
|
-
return null;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
getSamplesPerFrame(version, layer) {
|
|
161
|
-
if (layer === 1) {
|
|
162
|
-
return 384;
|
|
163
|
-
}
|
|
164
|
-
if (layer === 2) {
|
|
165
|
-
return 1152;
|
|
166
|
-
}
|
|
167
|
-
return version === 1 ? 1152 : 576;
|
|
168
|
-
}
|
|
169
|
-
calculateFrameSize(layer, bitrateKbps, sampleRate, padding) {
|
|
170
|
-
if (bitrateKbps <= 0) {
|
|
171
|
-
return 0;
|
|
172
|
-
}
|
|
173
|
-
if (layer === 1) {
|
|
174
|
-
return (12 * bitrateKbps * 1e3 / sampleRate + padding) * 4;
|
|
175
|
-
}
|
|
176
|
-
return Math.floor(144 * bitrateKbps * 1e3 / sampleRate + padding);
|
|
177
|
-
}
|
|
178
|
-
readSynchsafeInteger(bytes) {
|
|
179
|
-
if (bytes.length !== 4) {
|
|
180
|
-
return 0;
|
|
181
|
-
}
|
|
182
|
-
return ((bytes[0] ?? 0) & 127) << 21 | ((bytes[1] ?? 0) & 127) << 14 | ((bytes[2] ?? 0) & 127) << 7 | (bytes[3] ?? 0) & 127;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
class AudioDemuxWorker {
|
|
186
|
-
channel;
|
|
187
|
-
demuxer = null;
|
|
188
|
-
audioStream = null;
|
|
189
|
-
mp3Parser = null;
|
|
190
|
-
currentFormat = null;
|
|
191
|
-
// Connection to decoder worker
|
|
192
|
-
decoderPort = null;
|
|
193
|
-
constructor() {
|
|
194
|
-
this.channel = new WorkerChannel(self, {
|
|
195
|
-
name: "AudioDemuxWorker",
|
|
196
|
-
timeout: 3e4
|
|
197
|
-
});
|
|
198
|
-
this.setupHandlers();
|
|
199
|
-
}
|
|
200
|
-
setupHandlers() {
|
|
201
|
-
this.channel.registerHandler("configure", this.handleConfigure.bind(this));
|
|
202
|
-
this.channel.registerHandler("connect", this.handleConnect.bind(this));
|
|
203
|
-
this.channel.registerHandler("get_stats", this.handleGetStats.bind(this));
|
|
204
|
-
this.channel.registerHandler(WorkerMessageType.Dispose, this.handleDispose.bind(this));
|
|
205
|
-
this.channel.receiveStream(this.handleReceiveStream.bind(this));
|
|
206
|
-
}
|
|
207
|
-
/**
|
|
208
|
-
* Unified connect handler used by stream pipeline
|
|
209
|
-
*/
|
|
210
|
-
async handleConnect(payload) {
|
|
211
|
-
this.decoderPort = payload.port;
|
|
212
|
-
return { success: true };
|
|
213
|
-
}
|
|
214
|
-
/**
|
|
215
|
-
* Configure demuxer with format settings
|
|
216
|
-
* @param payload.config - Demuxer configuration
|
|
217
|
-
* @param payload.initial - If true, initialize worker state; otherwise just update config
|
|
218
|
-
*/
|
|
219
|
-
async handleConfigure(payload) {
|
|
220
|
-
const { config, initial = false } = payload;
|
|
221
|
-
try {
|
|
222
|
-
if (initial) {
|
|
223
|
-
this.channel.state = WorkerState.Ready;
|
|
224
|
-
const format = config.container || this.detectFormat(config);
|
|
225
|
-
if (format === "mp3") {
|
|
226
|
-
this.mp3Parser = new MP3FrameParser();
|
|
227
|
-
this.demuxer?.destroy();
|
|
228
|
-
this.demuxer = null;
|
|
229
|
-
this.audioStream = null;
|
|
230
|
-
this.currentFormat = "mp3";
|
|
231
|
-
const mp3Track = { id: 1, type: "audio", codec: "mp3" };
|
|
232
|
-
this.channel.notify("configured", {
|
|
233
|
-
tracks: [mp3Track],
|
|
234
|
-
format,
|
|
235
|
-
codec: "mp3"
|
|
236
|
-
});
|
|
237
|
-
return { success: true, tracks: [mp3Track] };
|
|
238
|
-
} else if (format === "mp4" || format === "m4a") {
|
|
239
|
-
if (this.demuxer) {
|
|
240
|
-
this.demuxer.destroy();
|
|
241
|
-
}
|
|
242
|
-
this.demuxer = new MP4Demuxer({
|
|
243
|
-
...config,
|
|
244
|
-
skipVideo: true
|
|
245
|
-
// Audio only
|
|
246
|
-
});
|
|
247
|
-
this.audioStream = this.demuxer.createAudioStream();
|
|
248
|
-
this.mp3Parser = null;
|
|
249
|
-
this.currentFormat = "mp4";
|
|
250
|
-
} else {
|
|
251
|
-
throw {
|
|
252
|
-
code: "UNSUPPORTED_FORMAT",
|
|
253
|
-
message: `Unsupported audio format: ${format}`
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
if (!this.audioStream) {
|
|
257
|
-
throw {
|
|
258
|
-
code: "NO_AUDIO_TRACK",
|
|
259
|
-
message: "No audio track found in container"
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
const tracks = Array.from(this.demuxer.tracks.values());
|
|
263
|
-
this.channel.notify("configured", {
|
|
264
|
-
tracks,
|
|
265
|
-
format,
|
|
266
|
-
codec: tracks[0]?.codec
|
|
267
|
-
});
|
|
268
|
-
return { success: true, tracks };
|
|
269
|
-
} else {
|
|
270
|
-
if (!this.demuxer) {
|
|
271
|
-
throw {
|
|
272
|
-
code: "NOT_INITIALIZED",
|
|
273
|
-
message: "Demuxer not initialized. Call configure with initial=true first"
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
return { success: true };
|
|
277
|
-
}
|
|
278
|
-
} catch (error) {
|
|
279
|
-
throw {
|
|
280
|
-
code: error.code || "CONFIG_ERROR",
|
|
281
|
-
message: error.message
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
/**
|
|
286
|
-
* Detect audio format from configuration
|
|
287
|
-
*/
|
|
288
|
-
detectFormat(config) {
|
|
289
|
-
if (config.codec?.includes("mp3")) return "mp3";
|
|
290
|
-
if (config.codec?.includes("aac")) return "mp4";
|
|
291
|
-
if (config.codec?.includes("opus")) return "webm";
|
|
292
|
-
return "mp3";
|
|
293
|
-
}
|
|
294
|
-
/**
|
|
295
|
-
* Handle input stream from ResourceLoader (main thread)
|
|
296
|
-
*/
|
|
297
|
-
async handleReceiveStream(stream, metadata) {
|
|
298
|
-
if (this.currentFormat === "mp3" && !this.mp3Parser) {
|
|
299
|
-
this.mp3Parser = new MP3FrameParser();
|
|
300
|
-
}
|
|
301
|
-
if (this.currentFormat === "mp4" && (!this.demuxer || !this.audioStream)) {
|
|
302
|
-
await this.handleConfigure({
|
|
303
|
-
config: { container: "mp4", highWaterMark: 10 },
|
|
304
|
-
initial: true
|
|
305
|
-
}).catch((error) => {
|
|
306
|
-
console.error("[AudioDemuxWorker] Configure error:", metadata?.sessionId, error);
|
|
307
|
-
return;
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
if (!this.decoderPort) {
|
|
311
|
-
console.error("[AudioDemuxWorker] Decoder not connected");
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
const decoderChannel = new WorkerChannel(this.decoderPort, {
|
|
315
|
-
name: "AudioDemux-Decoder",
|
|
316
|
-
timeout: 3e4
|
|
317
|
-
});
|
|
318
|
-
if (this.currentFormat === "mp3") {
|
|
319
|
-
await this.pipeMp3Stream(stream, decoderChannel, metadata).catch((error) => {
|
|
320
|
-
console.error("[AudioDemuxWorker] MP3 stream error:", metadata?.sessionId, error);
|
|
321
|
-
});
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
if (!this.demuxer || !this.audioStream) {
|
|
325
|
-
console.error("[AudioDemuxWorker] Audio demuxer not initialized");
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
const transformed = stream.pipeThrough(this.audioStream);
|
|
329
|
-
const trackInfo = Array.from(this.demuxer.tracks.values())[0];
|
|
330
|
-
await decoderChannel.sendStream(transformed, {
|
|
331
|
-
streamType: "audio",
|
|
332
|
-
sessionId: metadata?.sessionId ?? "default",
|
|
333
|
-
trackId: metadata?.trackId ?? "main",
|
|
334
|
-
codec: trackInfo?.codec ?? "mp4a.40.2",
|
|
335
|
-
sampleRate: trackInfo?.sampleRate ?? 44100,
|
|
336
|
-
numberOfChannels: trackInfo?.numberOfChannels ?? 2,
|
|
337
|
-
description: trackInfo?.description
|
|
338
|
-
}).catch((error) => {
|
|
339
|
-
console.error("[AudioDemuxWorker] Send stream error:", metadata?.sessionId, error);
|
|
340
|
-
});
|
|
341
|
-
this.channel.notify("demux_complete", {
|
|
342
|
-
tracksProcessed: this.demuxer?.tracks.size || 0
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
async pipeMp3Stream(stream, decoderChannel, metadata) {
|
|
346
|
-
if (!this.mp3Parser) {
|
|
347
|
-
this.mp3Parser = new MP3FrameParser();
|
|
348
|
-
}
|
|
349
|
-
const reader = stream.getReader();
|
|
350
|
-
let currentConfig = null;
|
|
351
|
-
let configured = false;
|
|
352
|
-
const bufferedFrames = [];
|
|
353
|
-
let writer = null;
|
|
354
|
-
const ensureWriter = async () => {
|
|
355
|
-
if (!writer) {
|
|
356
|
-
const cfg = currentConfig;
|
|
357
|
-
if (!cfg) {
|
|
358
|
-
throw new Error("MP3 config missing while creating writer");
|
|
359
|
-
}
|
|
360
|
-
const transform = new TransformStream();
|
|
361
|
-
writer = transform.writable.getWriter();
|
|
362
|
-
await decoderChannel.sendStream(transform.readable, {
|
|
363
|
-
streamType: "audio",
|
|
364
|
-
sessionId: metadata?.sessionId ?? "default",
|
|
365
|
-
trackId: metadata?.trackId ?? "main",
|
|
366
|
-
codec: "mp3",
|
|
367
|
-
sampleRate: cfg.sampleRate,
|
|
368
|
-
numberOfChannels: cfg.channels,
|
|
369
|
-
description: void 0
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
|
-
return writer;
|
|
373
|
-
};
|
|
374
|
-
try {
|
|
375
|
-
while (true) {
|
|
376
|
-
const { done, value } = await reader.read();
|
|
377
|
-
if (done) {
|
|
378
|
-
break;
|
|
379
|
-
}
|
|
380
|
-
if (!value) {
|
|
381
|
-
continue;
|
|
382
|
-
}
|
|
383
|
-
const { frames, config } = this.mp3Parser.push(value);
|
|
384
|
-
if (config) {
|
|
385
|
-
currentConfig = config;
|
|
386
|
-
}
|
|
387
|
-
if (config && !configured) {
|
|
388
|
-
await decoderChannel.send("configure", {
|
|
389
|
-
streamType: "audio",
|
|
390
|
-
clipId: metadata?.clipId ?? "default",
|
|
391
|
-
codec: "mp3",
|
|
392
|
-
sampleRate: config.sampleRate,
|
|
393
|
-
numberOfChannels: config.channels,
|
|
394
|
-
description: void 0
|
|
395
|
-
});
|
|
396
|
-
this.channel.notify("demux_configured", {
|
|
397
|
-
clipId: metadata?.clipId ?? "default",
|
|
398
|
-
codec: "mp3",
|
|
399
|
-
sampleRate: config.sampleRate,
|
|
400
|
-
numberOfChannels: config.channels
|
|
401
|
-
});
|
|
402
|
-
configured = true;
|
|
403
|
-
}
|
|
404
|
-
if (config) {
|
|
405
|
-
this.currentFormat = "mp3";
|
|
406
|
-
}
|
|
407
|
-
if (!configured) {
|
|
408
|
-
bufferedFrames.push(...frames);
|
|
409
|
-
continue;
|
|
410
|
-
}
|
|
411
|
-
const targetWriter = await ensureWriter();
|
|
412
|
-
const readyFrames = bufferedFrames.splice(0, bufferedFrames.length);
|
|
413
|
-
readyFrames.push(...frames);
|
|
414
|
-
for (const frame of readyFrames) {
|
|
415
|
-
const chunk = new EncodedAudioChunk({
|
|
416
|
-
type: "key",
|
|
417
|
-
timestamp: frame.timestampUs,
|
|
418
|
-
duration: frame.durationUs,
|
|
419
|
-
data: frame.data.buffer.slice(
|
|
420
|
-
frame.data.byteOffset,
|
|
421
|
-
frame.data.byteOffset + frame.data.byteLength
|
|
422
|
-
)
|
|
423
|
-
});
|
|
424
|
-
await targetWriter.write(chunk);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
const remaining = this.mp3Parser.flush();
|
|
428
|
-
if (remaining.length) {
|
|
429
|
-
if (!configured) {
|
|
430
|
-
throw new Error("MP3 stream ended before configuration");
|
|
431
|
-
}
|
|
432
|
-
const targetWriter = await ensureWriter();
|
|
433
|
-
for (const frame of remaining) {
|
|
434
|
-
const chunk = new EncodedAudioChunk({
|
|
435
|
-
type: "key",
|
|
436
|
-
timestamp: frame.timestampUs,
|
|
437
|
-
duration: frame.durationUs,
|
|
438
|
-
data: frame.data.buffer.slice(
|
|
439
|
-
frame.data.byteOffset,
|
|
440
|
-
frame.data.byteOffset + frame.data.byteLength
|
|
441
|
-
)
|
|
442
|
-
});
|
|
443
|
-
await targetWriter.write(chunk);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
if (writer) {
|
|
447
|
-
await writer.close();
|
|
448
|
-
}
|
|
449
|
-
this.channel.notify("demux_complete", {
|
|
450
|
-
tracksProcessed: 1
|
|
451
|
-
});
|
|
452
|
-
} finally {
|
|
453
|
-
reader.releaseLock();
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
/**
|
|
457
|
-
* Get demuxer statistics
|
|
458
|
-
*/
|
|
459
|
-
async handleGetStats() {
|
|
460
|
-
if (this.currentFormat === "mp3") {
|
|
461
|
-
return {
|
|
462
|
-
tracksInfo: this.mp3Parser ? [{ id: 1, type: "audio", codec: "mp3" }] : [],
|
|
463
|
-
format: "mp3",
|
|
464
|
-
state: this.channel.state
|
|
465
|
-
};
|
|
466
|
-
}
|
|
467
|
-
if (this.demuxer) {
|
|
468
|
-
return {
|
|
469
|
-
tracksInfo: Array.from(this.demuxer.tracks.values()),
|
|
470
|
-
format: "mp4",
|
|
471
|
-
state: this.channel.state
|
|
472
|
-
};
|
|
473
|
-
}
|
|
474
|
-
return { state: this.channel.state };
|
|
475
|
-
}
|
|
476
|
-
/**
|
|
477
|
-
* Dispose worker and cleanup resources
|
|
478
|
-
*/
|
|
479
|
-
async handleDispose() {
|
|
480
|
-
if (this.demuxer) {
|
|
481
|
-
this.demuxer.destroy();
|
|
482
|
-
}
|
|
483
|
-
this.demuxer = null;
|
|
484
|
-
this.audioStream = null;
|
|
485
|
-
this.mp3Parser = null;
|
|
486
|
-
this.currentFormat = null;
|
|
487
|
-
this.decoderPort?.close();
|
|
488
|
-
this.decoderPort = null;
|
|
489
|
-
this.channel.state = WorkerState.Disposed;
|
|
490
|
-
return { success: true };
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
const worker = new AudioDemuxWorker();
|
|
494
|
-
self.addEventListener("beforeunload", () => {
|
|
495
|
-
worker["handleDispose"]();
|
|
496
|
-
});
|
|
497
|
-
const audioDemux_worker = null;
|
|
498
|
-
export {
|
|
499
|
-
AudioDemuxWorker,
|
|
500
|
-
audioDemux_worker as default
|
|
501
|
-
};
|
|
502
|
-
//# sourceMappingURL=audio-demux.worker.D-_LoVqW.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"audio-demux.worker.D-_LoVqW.js","sources":["../../../../src/stages/demux/MP3FrameParser.ts","../../../../src/stages/demux/audio-demux.worker.ts"],"sourcesContent":["const BITRATE_TABLE: Record<string, number[]> = {\n '1-3': [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0],\n '1-2': [0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0],\n '1-1': [0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0],\n '2-3': [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0],\n '2-2': [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0],\n '2-1': [0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0],\n};\n\nconst SAMPLE_RATE_TABLE: Record<number, number[]> = {\n 1: [44100, 48000, 32000],\n 2: [22050, 24000, 16000],\n 2.5: [11025, 12000, 8000],\n};\n\nexport interface MP3Config {\n codec: 'mp3';\n sampleRate: number;\n channels: number;\n bitrateKbps: number | null;\n samplesPerFrame: number;\n}\n\nexport interface MP3Frame {\n data: Uint8Array;\n timestampUs: number;\n durationUs: number;\n}\n\ninterface ParseResult {\n frames: MP3Frame[];\n config?: MP3Config;\n}\n\nexport class MP3FrameParser {\n private buffer = new Uint8Array(0);\n private config: MP3Config | null = null;\n private timestampUs = 0;\n\n push(chunk: Uint8Array): ParseResult {\n this.appendToBuffer(chunk);\n this.skipId3Headers();\n\n const frames: MP3Frame[] = [];\n let configEmitted: MP3Config | undefined;\n\n let offset = 0;\n while (offset <= this.buffer.length - 4) {\n const header = this.parseHeader(this.buffer, offset);\n if (!header) {\n offset += 1;\n continue;\n }\n\n if (offset + header.frameSize > this.buffer.length) {\n break;\n }\n\n const frameBytes = this.buffer.slice(offset, offset + header.frameSize);\n const durationUs = Math.round((header.samplesPerFrame * 1_000_000) / header.sampleRate);\n\n if (!this.config) {\n this.config = {\n codec: 'mp3',\n sampleRate: header.sampleRate,\n channels: header.channels,\n bitrateKbps: header.bitrateKbps,\n samplesPerFrame: header.samplesPerFrame,\n };\n configEmitted = this.config;\n }\n\n frames.push({\n data: frameBytes,\n timestampUs: this.timestampUs,\n durationUs,\n });\n\n this.timestampUs += durationUs;\n offset += header.frameSize;\n }\n\n if (offset > 0) {\n this.buffer = this.buffer.slice(offset);\n }\n\n return { frames, config: configEmitted };\n }\n\n flush(): MP3Frame[] {\n const { frames } = this.push(new Uint8Array(0));\n const remainder = this.buffer;\n this.buffer = new Uint8Array(0);\n if (remainder.length) {\n console.warn('[MP3FrameParser] Remaining unparsed bytes:', remainder.length);\n }\n return frames;\n }\n\n private appendToBuffer(chunk: Uint8Array): void {\n if (chunk.length === 0) {\n return;\n }\n const combined = new Uint8Array(this.buffer.length + chunk.length);\n combined.set(this.buffer, 0);\n combined.set(chunk, this.buffer.length);\n this.buffer = combined;\n }\n\n private skipId3Headers(): void {\n let offset = 0;\n while (this.buffer.length - offset >= 10) {\n if (\n this.buffer[offset] === 0x49 &&\n this.buffer[offset + 1] === 0x44 &&\n this.buffer[offset + 2] === 0x33\n ) {\n const size = this.readSynchsafeInteger(this.buffer.subarray(offset + 6, offset + 10));\n const total = size + 10;\n if (offset + total <= this.buffer.length) {\n offset += total;\n continue;\n }\n }\n break;\n }\n if (offset > 0) {\n this.buffer = this.buffer.slice(offset);\n }\n }\n\n private parseHeader(\n buffer: Uint8Array,\n offset: number\n ): {\n frameSize: number;\n sampleRate: number;\n channels: number;\n bitrateKbps: number | null;\n samplesPerFrame: number;\n } | null {\n if (offset + 3 >= buffer.length) {\n return null;\n }\n if (buffer[offset] !== 0xff || ((buffer[offset + 1] ?? 0) & 0xe0) !== 0xe0) {\n return null;\n }\n\n const versionBits = ((buffer[offset + 1] ?? 0) >> 3) & 0x03;\n const layerBits = ((buffer[offset + 1] ?? 0) >> 1) & 0x03;\n const bitrateIndex = ((buffer[offset + 2] ?? 0) >> 4) & 0x0f;\n const sampleRateIndex = ((buffer[offset + 2] ?? 0) >> 2) & 0x03;\n const paddingBit = ((buffer[offset + 2] ?? 0) >> 1) & 0x01;\n const channelMode = ((buffer[offset + 3] ?? 0) >> 6) & 0x03;\n\n const version = this.getVersion(versionBits);\n const layer = this.getLayer(layerBits);\n if (!version || !layer) {\n return null;\n }\n\n const sampleRate = SAMPLE_RATE_TABLE[version]?.[sampleRateIndex] ?? null;\n if (!sampleRate) {\n return null;\n }\n\n const bitrateKey = `${version === 1 ? 1 : 2}-${layer}`;\n const bitrateKbps = BITRATE_TABLE[bitrateKey]?.[bitrateIndex] ?? null;\n if (bitrateKbps === null) {\n return null;\n }\n\n const samplesPerFrame = this.getSamplesPerFrame(version, layer);\n const frameSize = this.calculateFrameSize(layer, bitrateKbps, sampleRate, paddingBit);\n if (!frameSize || frameSize < 24) {\n return null;\n }\n\n const channels = channelMode === 3 ? 1 : 2;\n\n return {\n frameSize,\n sampleRate,\n channels,\n bitrateKbps: bitrateKbps || null,\n samplesPerFrame,\n };\n }\n\n private getVersion(bits: number): 1 | 2 | 2.5 | null {\n switch (bits) {\n case 0b11:\n return 1;\n case 0b10:\n return 2;\n case 0b00:\n return 2.5;\n default:\n return null;\n }\n }\n\n private getLayer(bits: number): 1 | 2 | 3 | null {\n switch (bits) {\n case 0b11:\n return 1;\n case 0b10:\n return 2;\n case 0b01:\n return 3;\n default:\n return null;\n }\n }\n\n private getSamplesPerFrame(version: 1 | 2 | 2.5, layer: 1 | 2 | 3): number {\n if (layer === 1) {\n return 384;\n }\n if (layer === 2) {\n return 1152;\n }\n return version === 1 ? 1152 : 576;\n }\n\n private calculateFrameSize(\n layer: 1 | 2 | 3,\n bitrateKbps: number,\n sampleRate: number,\n padding: number\n ): number {\n if (bitrateKbps <= 0) {\n return 0;\n }\n\n if (layer === 1) {\n return ((12 * bitrateKbps * 1000) / sampleRate + padding) * 4;\n }\n\n return Math.floor((144 * bitrateKbps * 1000) / sampleRate + padding);\n }\n\n private readSynchsafeInteger(bytes: Uint8Array): number {\n if (bytes.length !== 4) {\n return 0;\n }\n return (\n (((bytes[0] ?? 0) & 0x7f) << 21) |\n (((bytes[1] ?? 0) & 0x7f) << 14) |\n (((bytes[2] ?? 0) & 0x7f) << 7) |\n ((bytes[3] ?? 0) & 0x7f)\n );\n }\n}\n","import { WorkerChannel } from '../../worker/WorkerChannel';\nimport { WorkerMessageType, WorkerState } from '../../worker/types';\nimport { MP4Demuxer } from './MP4Demuxer';\nimport { MP3FrameParser, type MP3Config, type MP3Frame } from './MP3FrameParser';\nimport type { DemuxConfig } from './types';\n\n/**\n * AudioDemuxWorker - First stage for audio processing\n * Extracts audio tracks from various container formats (MP3, MP4/M4A, etc.)\n *\n * Pipeline: ResourceLoader (Main Thread) → AudioDemuxWorker → DecodeWorker\n *\n * Features:\n * - Multi-format support (MP3, AAC in MP4/M4A)\n * - Stream-based processing with backpressure\n * - Direct streaming to DecodeWorker\n */\nexport class AudioDemuxWorker {\n private channel: WorkerChannel;\n private demuxer: MP4Demuxer | null = null;\n private audioStream: TransformStream<Uint8Array, EncodedAudioChunk> | null = null;\n private mp3Parser: MP3FrameParser | null = null;\n private currentFormat: 'mp3' | 'mp4' | null = null;\n\n // Connection to decoder worker\n private decoderPort: MessagePort | null = null;\n\n constructor() {\n // Initialize WorkerChannel\n this.channel = new WorkerChannel(self as any, {\n name: 'AudioDemuxWorker',\n timeout: 30000,\n });\n\n this.setupHandlers();\n }\n\n private setupHandlers(): void {\n // Register message handlers\n this.channel.registerHandler('configure', this.handleConfigure.bind(this));\n // Unified stream connect (feature-flagged)\n this.channel.registerHandler('connect' as any, this.handleConnect.bind(this));\n // Unified stream connect only\n this.channel.registerHandler('get_stats', this.handleGetStats.bind(this));\n this.channel.registerHandler(WorkerMessageType.Dispose, this.handleDispose.bind(this));\n\n // Setup stream receiver from ResourceLoader (main thread)\n this.channel.receiveStream(this.handleReceiveStream.bind(this));\n }\n\n /**\n * Unified connect handler used by stream pipeline\n */\n private async handleConnect(payload: {\n direction: 'upstream';\n port: MessagePort;\n streamType: 'video' | 'audio' | 'frame' | 'chunk';\n }): Promise<{ success: boolean }> {\n this.decoderPort = payload.port;\n // Demux connects upstream to decoder\n return { success: true };\n }\n\n /**\n * Configure demuxer with format settings\n * @param payload.config - Demuxer configuration\n * @param payload.initial - If true, initialize worker state; otherwise just update config\n */\n private async handleConfigure(payload: {\n config: DemuxConfig;\n initial?: boolean;\n }): Promise<{ success: boolean; tracks?: any[] }> {\n const { config, initial = false } = payload;\n\n try {\n if (initial) {\n // Initial setup - set worker state to ready\n this.channel.state = WorkerState.Ready;\n\n // Detect format and create appropriate demuxer\n const format = config.container || this.detectFormat(config);\n\n if (format === 'mp3') {\n this.mp3Parser = new MP3FrameParser();\n this.demuxer?.destroy();\n this.demuxer = null;\n this.audioStream = null;\n this.currentFormat = 'mp3';\n\n const mp3Track = { id: 1, type: 'audio', codec: 'mp3' };\n this.channel.notify('configured', {\n tracks: [mp3Track],\n format,\n codec: 'mp3',\n });\n\n return { success: true, tracks: [mp3Track] };\n } else if (format === 'mp4' || format === 'm4a') {\n if (this.demuxer) {\n this.demuxer.destroy();\n }\n\n this.demuxer = new MP4Demuxer({\n ...config,\n skipVideo: true, // Audio only\n });\n this.audioStream = this.demuxer.createAudioStream();\n this.mp3Parser = null;\n this.currentFormat = 'mp4';\n } else {\n throw {\n code: 'UNSUPPORTED_FORMAT',\n message: `Unsupported audio format: ${format}`,\n };\n }\n\n if (!this.audioStream) {\n throw {\n code: 'NO_AUDIO_TRACK',\n message: 'No audio track found in container',\n };\n }\n\n const tracks = Array.from(this.demuxer.tracks.values());\n\n // Notify configuration complete\n this.channel.notify('configured', {\n tracks,\n format,\n codec: tracks[0]?.codec,\n });\n\n return { success: true, tracks };\n } else {\n // Update configuration only (e.g., backpressure settings)\n if (!this.demuxer) {\n throw {\n code: 'NOT_INITIALIZED',\n message: 'Demuxer not initialized. Call configure with initial=true first',\n };\n }\n\n // Demuxers don't support runtime config updates\n // Would need initial=true for changes\n\n return { success: true };\n }\n } catch (error: any) {\n throw {\n code: error.code || 'CONFIG_ERROR',\n message: error.message,\n };\n }\n }\n\n /**\n * Detect audio format from configuration\n */\n private detectFormat(config: DemuxConfig): string {\n // Simple format detection based on codec or file extension\n if (config.codec?.includes('mp3')) return 'mp3';\n if (config.codec?.includes('aac')) return 'mp4';\n if (config.codec?.includes('opus')) return 'webm';\n\n return 'mp3';\n }\n\n /**\n * Handle input stream from ResourceLoader (main thread)\n */\n private async handleReceiveStream(\n stream: ReadableStream<Uint8Array>,\n metadata?: Record<string, any>\n ): Promise<void> {\n // Initialize demuxer if not configured\n if (this.currentFormat === 'mp3' && !this.mp3Parser) {\n this.mp3Parser = new MP3FrameParser();\n }\n\n if (this.currentFormat === 'mp4' && (!this.demuxer || !this.audioStream)) {\n await this.handleConfigure({\n config: { container: 'mp4', highWaterMark: 10 },\n initial: true,\n }).catch((error) => {\n console.error('[AudioDemuxWorker] Configure error:', metadata?.sessionId, error);\n return;\n });\n }\n\n if (!this.decoderPort) {\n console.error('[AudioDemuxWorker] Decoder not connected');\n return;\n }\n\n // Setup channel to decoder\n const decoderChannel = new WorkerChannel(this.decoderPort, {\n name: 'AudioDemux-Decoder',\n timeout: 30000,\n });\n\n if (this.currentFormat === 'mp3') {\n await this.pipeMp3Stream(stream, decoderChannel, metadata).catch((error) => {\n console.error('[AudioDemuxWorker] MP3 stream error:', metadata?.sessionId, error);\n });\n return;\n }\n\n if (!this.demuxer || !this.audioStream) {\n console.error('[AudioDemuxWorker] Audio demuxer not initialized');\n return;\n }\n\n const transformed = stream.pipeThrough(this.audioStream);\n const trackInfo = Array.from(this.demuxer.tracks.values())[0];\n\n await decoderChannel\n .sendStream(transformed, {\n streamType: 'audio',\n sessionId: metadata?.sessionId ?? 'default',\n trackId: metadata?.trackId ?? 'main',\n codec: trackInfo?.codec ?? 'mp4a.40.2',\n sampleRate: trackInfo?.sampleRate ?? 44_100,\n numberOfChannels: trackInfo?.numberOfChannels ?? 2,\n description: trackInfo?.description,\n })\n .catch((error) => {\n console.error('[AudioDemuxWorker] Send stream error:', metadata?.sessionId, error);\n });\n\n this.channel.notify('demux_complete', {\n tracksProcessed: this.demuxer?.tracks.size || 0,\n });\n }\n\n private async pipeMp3Stream(\n stream: ReadableStream<Uint8Array>,\n decoderChannel: WorkerChannel,\n metadata?: Record<string, any>\n ): Promise<void> {\n if (!this.mp3Parser) {\n this.mp3Parser = new MP3FrameParser();\n }\n\n const reader = stream.getReader();\n let currentConfig: MP3Config | null = null;\n let configured = false;\n const bufferedFrames: MP3Frame[] = [];\n let writer: WritableStreamDefaultWriter<EncodedAudioChunk> | null = null;\n\n const ensureWriter = async (): Promise<WritableStreamDefaultWriter<EncodedAudioChunk>> => {\n if (!writer) {\n const cfg = currentConfig;\n if (!cfg) {\n throw new Error('MP3 config missing while creating writer');\n }\n const transform = new TransformStream<EncodedAudioChunk, EncodedAudioChunk>();\n writer = transform.writable.getWriter();\n await decoderChannel.sendStream(transform.readable, {\n streamType: 'audio',\n sessionId: metadata?.sessionId ?? 'default',\n trackId: metadata?.trackId ?? 'main',\n codec: 'mp3',\n sampleRate: cfg.sampleRate,\n numberOfChannels: cfg.channels,\n description: undefined,\n });\n }\n return writer!;\n };\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n\n if (!value) {\n continue;\n }\n\n const { frames, config } = this.mp3Parser.push(value);\n\n if (config) {\n currentConfig = config;\n }\n\n if (config && !configured) {\n await decoderChannel.send('configure' as any, {\n streamType: 'audio',\n clipId: metadata?.clipId ?? 'default',\n codec: 'mp3',\n sampleRate: config.sampleRate,\n numberOfChannels: config.channels,\n description: undefined,\n });\n this.channel.notify('demux_configured', {\n clipId: metadata?.clipId ?? 'default',\n codec: 'mp3',\n sampleRate: config.sampleRate,\n numberOfChannels: config.channels,\n });\n configured = true;\n }\n\n if (config) {\n this.currentFormat = 'mp3';\n }\n\n if (!configured) {\n bufferedFrames.push(...frames);\n continue;\n }\n\n const targetWriter = await ensureWriter();\n const readyFrames = bufferedFrames.splice(0, bufferedFrames.length);\n readyFrames.push(...frames);\n for (const frame of readyFrames) {\n const chunk = new EncodedAudioChunk({\n type: 'key',\n timestamp: frame.timestampUs,\n duration: frame.durationUs,\n data: frame.data.buffer.slice(\n frame.data.byteOffset,\n frame.data.byteOffset + frame.data.byteLength\n ),\n });\n await targetWriter.write(chunk);\n }\n }\n\n const remaining = this.mp3Parser.flush();\n if (remaining.length) {\n if (!configured) {\n throw new Error('MP3 stream ended before configuration');\n }\n const targetWriter = await ensureWriter();\n for (const frame of remaining) {\n const chunk = new EncodedAudioChunk({\n type: 'key',\n timestamp: frame.timestampUs,\n duration: frame.durationUs,\n data: frame.data.buffer.slice(\n frame.data.byteOffset,\n frame.data.byteOffset + frame.data.byteLength\n ),\n });\n await targetWriter.write(chunk);\n }\n }\n\n if (writer) {\n await (writer as WritableStreamDefaultWriter<EncodedAudioChunk>).close();\n }\n\n this.channel.notify('demux_complete', {\n tracksProcessed: 1,\n });\n } finally {\n reader.releaseLock();\n }\n }\n\n /**\n * Get demuxer statistics\n */\n private async handleGetStats(): Promise<{\n queueSize?: number;\n tracksInfo?: any[];\n format?: string;\n state?: WorkerState;\n }> {\n if (this.currentFormat === 'mp3') {\n return {\n tracksInfo: this.mp3Parser ? [{ id: 1, type: 'audio', codec: 'mp3' }] : [],\n format: 'mp3',\n state: this.channel.state,\n };\n }\n\n if (this.demuxer) {\n return {\n tracksInfo: Array.from(this.demuxer.tracks.values()),\n format: 'mp4',\n state: this.channel.state,\n };\n }\n\n return { state: this.channel.state };\n }\n\n /**\n * Dispose worker and cleanup resources\n */\n private async handleDispose(): Promise<{ success: boolean }> {\n if (this.demuxer) {\n this.demuxer.destroy();\n }\n this.demuxer = null;\n this.audioStream = null;\n this.mp3Parser = null;\n this.currentFormat = null;\n\n this.decoderPort?.close();\n this.decoderPort = null;\n\n this.channel.state = WorkerState.Disposed;\n\n return { success: true };\n }\n}\n\n// Initialize worker\nconst worker = new AudioDemuxWorker();\n\n// Handle worker termination\nself.addEventListener('beforeunload', () => {\n worker['handleDispose']();\n});\n\nexport default null; // Required for TypeScript worker compilation\n"],"names":[],"mappings":"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"}
|