@meframe/core 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Meframe.d.ts.map +1 -1
- package/dist/Meframe.js +7 -0
- package/dist/Meframe.js.map +1 -1
- package/dist/controllers/PlaybackController.d.ts.map +1 -1
- package/dist/controllers/PlaybackController.js +1 -1
- package/dist/controllers/PlaybackController.js.map +1 -1
- package/dist/orchestrator/AudioExportSession.d.ts +28 -0
- package/dist/orchestrator/AudioExportSession.d.ts.map +1 -0
- package/dist/orchestrator/AudioExportSession.js +95 -0
- package/dist/orchestrator/AudioExportSession.js.map +1 -0
- package/dist/orchestrator/AudioPreviewSession.d.ts +61 -0
- package/dist/orchestrator/AudioPreviewSession.d.ts.map +1 -0
- package/dist/orchestrator/AudioPreviewSession.js +340 -0
- package/dist/orchestrator/AudioPreviewSession.js.map +1 -0
- package/dist/orchestrator/AudioWindowPreparer.d.ts +62 -0
- package/dist/orchestrator/AudioWindowPreparer.d.ts.map +1 -0
- package/dist/orchestrator/AudioWindowPreparer.js +259 -0
- package/dist/orchestrator/AudioWindowPreparer.js.map +1 -0
- package/dist/orchestrator/ExportScheduler.d.ts +2 -2
- package/dist/orchestrator/ExportScheduler.js.map +1 -1
- package/dist/orchestrator/Orchestrator.d.ts +8 -2
- package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
- package/dist/orchestrator/Orchestrator.js +22 -16
- package/dist/orchestrator/Orchestrator.js.map +1 -1
- package/dist/stages/mux/MP4Muxer.js.map +1 -1
- package/dist/stages/mux/MuxManager.d.ts +1 -4
- package/dist/stages/mux/MuxManager.d.ts.map +1 -1
- package/dist/stages/mux/MuxManager.js +1 -1
- package/dist/stages/mux/MuxManager.js.map +1 -1
- package/package.json +1 -1
- package/dist/orchestrator/GlobalAudioSession.d.ts +0 -139
- package/dist/orchestrator/GlobalAudioSession.d.ts.map +0 -1
- package/dist/orchestrator/GlobalAudioSession.js +0 -683
- package/dist/orchestrator/GlobalAudioSession.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"GlobalAudioSession.js","sources":["../../src/orchestrator/GlobalAudioSession.ts"],"sourcesContent":["import type { TimeUs } from '../model/types';\nimport { OfflineAudioMixer } from '../stages/compose/OfflineAudioMixer';\nimport type { CompositionModel } from '../model';\nimport type { WorkerPool } from '../worker/WorkerPool';\nimport type { ResourceLoader } from '../stages/load/ResourceLoader';\nimport type { EventBus } from '../event/EventBus';\nimport type { EventPayloadMap } from '../event/events';\nimport { MeframeEvent } from '../event/events';\nimport type { CacheManager } from '../cache/CacheManager';\nimport { AudioMixBlockCache } from '../cache/AudioMixBlockCache';\nimport { AudioChunkEncoder } from '../stages/encode/AudioChunkEncoder';\nimport { AudioChunkDecoder } from '../stages/decode/AudioChunkDecoder';\nimport { isAudioClip, hasResourceId } from '../model/types';\nimport type { RequestMode } from './types';\n\ninterface AudioDataMessage {\n sessionId: string;\n audioData: AudioData;\n clipStartUs: TimeUs;\n clipDurationUs: TimeUs;\n}\n\ninterface AudioSessionDeps {\n cacheManager: CacheManager;\n workerPool: WorkerPool;\n resourceLoader: ResourceLoader;\n eventBus: EventBus<EventPayloadMap>;\n buildWorkerConfigs: () => any;\n}\n\nexport class GlobalAudioSession {\n private mixer: OfflineAudioMixer;\n private activeClips = new Set<string>();\n private deps: AudioSessionDeps;\n private model: CompositionModel | null = null;\n private audioContext: AudioContext | null = null;\n private volume = 1.0;\n private playbackRate = 1.0;\n private isPlaying = false;\n\n // Preview strategy (unified):\n // - Always schedule audio in fixed 60s \"mix blocks\"\n // - Cache 2~3 mixed AudioBuffer blocks (LRU) to accelerate seek\n // - Schedule ahead using AudioContext clock to avoid underrun\n private readonly PREVIEW_BLOCK_DURATION_US: TimeUs = 60 * 1_000_000; // 60s\n private readonly PREVIEW_BLOCK_CACHE_SIZE = 3;\n private readonly PREVIEW_SCHEDULE_AHEAD_SEC = 12.0; // keep enough scheduled audio to hide mixing latency\n private readonly PREVIEW_BUFFER_GUARD_US: TimeUs = 2_000_000; // if next block isn't ready near boundary -> buffering\n private readonly PREVIEW_BLOCK_FADE_SEC = 0.01; // 10ms fade-in/out to avoid click at boundaries\n\n private previewBlockCache = new AudioMixBlockCache(this.PREVIEW_BLOCK_CACHE_SIZE);\n\n private previewScheduleTask: Promise<void> | null = null;\n private previewScheduleToken = 0;\n private previewMixToken = 0;\n private previewNextBlockIndex = 0;\n private previewNextScheduleTime = 0; // AudioContext time\n private previewFirstBlockOffsetUs: TimeUs = 0; // seek offset within the first block\n private previewLastTimelineUs: TimeUs = 0;\n private previewLastStallWarnAt = 0; // AudioContext time (sec)\n\n private previewScheduledSources = new Set<{ source: AudioBufferSourceNode; gain: GainNode }>();\n private previewMixQueue: Promise<unknown> = Promise.resolve();\n\n constructor(deps: AudioSessionDeps) {\n this.deps = deps;\n this.mixer = new OfflineAudioMixer(deps.cacheManager, () => this.model);\n }\n\n private enqueuePreviewMix<T>(work: () => Promise<T>, token: number): Promise<T | null> {\n const run = () => work();\n const next = this.previewMixQueue.then(\n () => {\n // If a seek/reset happened since this task was enqueued, drop it.\n if (this.previewMixToken !== token) {\n return null;\n }\n return run();\n },\n () => {\n if (this.previewMixToken !== token) {\n return null;\n }\n return run();\n }\n );\n // Keep queue alive even if a task fails.\n this.previewMixQueue = next.then(\n () => undefined,\n () => undefined\n );\n return next as Promise<T | null>;\n }\n\n setModel(model: CompositionModel): void {\n this.model = model;\n }\n\n onAudioData(message: AudioDataMessage): void {\n const { sessionId, audioData, clipStartUs, clipDurationUs } = message;\n const globalTimeUs = clipStartUs + (audioData.timestamp ?? 0);\n this.deps.cacheManager.putClipAudioData(sessionId, audioData, clipDurationUs, globalTimeUs);\n }\n\n async ensureAudioForTime(timeUs: TimeUs, options?: { mode?: RequestMode }): Promise<void> {\n const model = this.model;\n if (!model) return;\n\n const mode = options?.mode ?? 'blocking';\n\n // Preview contract:\n // - blocking: ensure the current 60s mixed block is ready (may be slow -> should be wrapped by buffering UI)\n // - probe: best-effort preheat current and next block without blocking\n const blockIndex = Math.floor(Math.max(0, timeUs) / this.PREVIEW_BLOCK_DURATION_US);\n\n if (mode === 'probe') {\n // Default: only preheat the current block.\n // Preheating next block too early makes scrubbing heavier than needed.\n void this.getOrCreateMixedBlock(blockIndex);\n\n // If we're close enough to boundary (within scheduling lookahead), also preheat next block.\n const blockEndUs = (blockIndex + 1) * this.PREVIEW_BLOCK_DURATION_US;\n const remainingToBoundaryUs = blockEndUs - Math.max(0, timeUs);\n const lookaheadUs = Math.floor(this.PREVIEW_SCHEDULE_AHEAD_SEC * 1_000_000);\n if (remainingToBoundaryUs > 0 && remainingToBoundaryUs <= lookaheadUs) {\n void this.getOrCreateMixedBlock(blockIndex + 1);\n }\n return;\n }\n\n await this.getOrCreateMixedBlock(blockIndex);\n\n // If we're very close to the block boundary, also ensure the next block.\n // This supports the buffering flow: when we enter buffering near boundary,\n // EnsureAudio(blocking) will actually make the next block ready.\n const blockEndUs = (blockIndex + 1) * this.PREVIEW_BLOCK_DURATION_US;\n const remainingToBoundaryUs = blockEndUs - Math.max(0, timeUs);\n if (remainingToBoundaryUs > 0 && remainingToBoundaryUs <= this.PREVIEW_BUFFER_GUARD_US) {\n await this.getOrCreateMixedBlock(blockIndex + 1);\n }\n }\n\n isPreviewMixBlockCached(timeUs: TimeUs): boolean {\n const blockIndex = Math.floor(Math.max(0, timeUs) / this.PREVIEW_BLOCK_DURATION_US);\n return this.previewBlockCache.get(blockIndex) !== null;\n }\n\n shouldEnterBufferingForUpcomingPreviewAudio(timeUs: TimeUs): boolean {\n const model = this.model;\n if (!model) return false;\n\n const clampedUs = Math.max(0, timeUs);\n const blockIndex = Math.floor(clampedUs / this.PREVIEW_BLOCK_DURATION_US);\n const nextBlockStartUs = (blockIndex + 1) * this.PREVIEW_BLOCK_DURATION_US;\n if (nextBlockStartUs >= model.durationUs) return false;\n\n const remainingToBoundaryUs = nextBlockStartUs - clampedUs;\n if (remainingToBoundaryUs > this.PREVIEW_BUFFER_GUARD_US) return false;\n\n // Probe next block readiness by checking cache at next block start time.\n return !this.isPreviewMixBlockCached(nextBlockStartUs);\n }\n\n /**\n * Fast readiness probe for preview playback.\n *\n * This is intentionally synchronous and lightweight:\n * - Only checks resource-level readiness (download + MP4 index parsing).\n * - If any relevant resource isn't ready yet, return false.\n * - Does NOT require audio samples / PCM window coverage (probe is resource-level only).\n *\n * Note: This probe does NOT gate on PCM coverage to avoid frequent buffering oscillation.\n * PCM is prepared incrementally by scheduleAudio() / ensureAudioForTimeRange().\n */\n isAudioResourceWindowReady(startUs: TimeUs, endUs: TimeUs): boolean {\n const model = this.model;\n if (!model) return true;\n\n const activeClips = model.getActiveClips(startUs, endUs);\n\n for (const clip of activeClips) {\n if (clip.trackKind !== 'audio' && clip.trackKind !== 'video') continue;\n if (!hasResourceId(clip)) continue;\n\n const resource = model.getResource(clip.resourceId);\n if (resource?.state !== 'ready') {\n return false;\n }\n }\n\n return true;\n }\n\n async activateAllAudioClips(): Promise<void> {\n const model = this.model;\n if (!model) {\n return;\n }\n\n const audioTracks = model.tracks.filter((track) => track.kind === 'audio');\n if (audioTracks.length === 0) return;\n\n // Find maximum clip count across all audio tracks\n const maxClipCount = Math.max(...audioTracks.map((track) => track.clips.length));\n\n // Horizontal loading: activate clip[0] from all tracks, then clip[1], etc.\n for (let clipIndex = 0; clipIndex < maxClipCount; clipIndex++) {\n for (const track of audioTracks) {\n const clip = track.clips[clipIndex];\n if (!clip || this.activeClips.has(clip.id)) continue;\n\n if (!isAudioClip(clip)) {\n throw new Error(`Clip ${clip.id} in audio track is not an audio clip`);\n }\n\n // Preview: Use main-thread parsing → AudioSampleCache → on-demand decode\n // Check if we have cached samples (already parsed in ResourceLoader)\n if (this.deps.cacheManager.audioSampleCache.has(clip.resourceId)) {\n // Already parsed, mark as active\n this.activeClips.add(clip.id);\n this.deps.eventBus.emit(MeframeEvent.ClipActivated, { clipId: clip.id });\n continue;\n }\n\n // Ensure resource is loaded (will be parsed and cached in main thread)\n await this.deps.resourceLoader.load(clip.resourceId, {\n isPreload: false,\n clipId: clip.id,\n trackId: track.id,\n });\n\n // Mark as active\n this.activeClips.add(clip.id);\n this.deps.eventBus.emit(MeframeEvent.ClipActivated, { clipId: clip.id });\n }\n }\n }\n\n async deactivateClip(clipId: string): Promise<void> {\n if (!this.activeClips.has(clipId)) {\n return;\n }\n\n this.activeClips.delete(clipId);\n this.deps.cacheManager.clearClipAudioData(clipId);\n }\n\n async startPlayback(timeUs: TimeUs, audioContext: AudioContext): Promise<void> {\n this.audioContext = audioContext;\n\n // Resume AudioContext if suspended (required by modern browsers)\n if (audioContext.state === 'suspended') {\n await audioContext.resume();\n }\n\n // Ensure audio is decoded and ready (blocking mode for startup).\n // We do this before mixing to reduce \"missing PCM\" risk.\n await this.ensureAudioForTime(timeUs, { mode: 'blocking' });\n\n this.isPlaying = true;\n\n // Unified block scheduling: align to block index and schedule immediately.\n this.startPreviewBlockScheduling(timeUs, audioContext);\n // Schedule first block in blocking mode to avoid initial silence.\n await this.scheduleNextPreviewBlock(audioContext, this.previewScheduleToken);\n // Then keep scheduling in background.\n void this.scheduleAudio(timeUs, audioContext);\n }\n\n stopPlayback(): void {\n this.isPlaying = false;\n this.stopAllPreviewSources();\n this.cancelPreviewBlockScheduling();\n this.previewMixToken += 1;\n }\n\n updateTime(_timeUs: TimeUs): void {\n // Kept for compatibility\n }\n\n /**\n * Schedule audio chunks ahead of playback cursor\n * Uses OfflineAudioMixer for proper mixing, then plays the result\n */\n async scheduleAudio(currentTimelineUs: TimeUs, audioContext: AudioContext): Promise<void> {\n if (!this.isPlaying || !this.model || !this.audioContext) {\n return;\n }\n\n this.previewLastTimelineUs = currentTimelineUs;\n\n // Keep scheduling in the background to avoid blocking the render loop.\n if (this.previewScheduleTask) return;\n\n // Initialize if needed (e.g. after model switch without explicit startPlayback).\n if (this.previewNextScheduleTime === 0) {\n this.startPreviewBlockScheduling(currentTimelineUs, audioContext);\n }\n\n const token = this.previewScheduleToken;\n this.previewScheduleTask = this.runPreviewBlockSchedulingLoop(audioContext, token).finally(\n () => {\n if (this.previewScheduleToken === token) {\n this.previewScheduleTask = null;\n }\n }\n );\n }\n\n /**\n * Reset playback states (called on seek)\n */\n resetPlaybackStates(): void {\n this.stopAllPreviewSources();\n this.cancelPreviewBlockScheduling();\n this.previewMixToken += 1;\n }\n\n setVolume(volume: number): void {\n this.volume = volume;\n const audioContext = this.audioContext;\n if (!audioContext) return;\n\n // Apply volume to already scheduled nodes (small ramp to avoid click).\n const t = audioContext.currentTime;\n for (const { gain } of this.previewScheduledSources) {\n try {\n gain.gain.cancelScheduledValues(t);\n gain.gain.setValueAtTime(gain.gain.value, t);\n gain.gain.linearRampToValueAtTime(volume, t + 0.01);\n } catch {\n // ignore\n }\n }\n }\n\n setPlaybackRate(rate: number): void {\n this.playbackRate = rate;\n // Playback rate change requires restarting scheduling to keep sync with the timeline clock.\n this.resetPlaybackStates();\n }\n\n private startPreviewBlockScheduling(startTimelineUs: TimeUs, audioContext: AudioContext): void {\n this.cancelPreviewBlockScheduling();\n this.stopAllPreviewSources();\n\n const clampedUs = Math.max(0, startTimelineUs);\n const blockIndex = Math.floor(clampedUs / this.PREVIEW_BLOCK_DURATION_US);\n const blockStartUs = blockIndex * this.PREVIEW_BLOCK_DURATION_US;\n\n this.previewFirstBlockOffsetUs = clampedUs - blockStartUs;\n this.previewNextBlockIndex = blockIndex;\n this.previewNextScheduleTime = audioContext.currentTime + 0.02;\n this.previewLastTimelineUs = startTimelineUs;\n }\n\n private cancelPreviewBlockScheduling(): void {\n this.previewScheduleToken += 1;\n this.previewScheduleTask = null;\n this.previewNextBlockIndex = 0;\n this.previewNextScheduleTime = 0;\n this.previewFirstBlockOffsetUs = 0;\n this.previewLastTimelineUs = 0;\n }\n\n private realignPreviewSchedulingToTimeline(audioContext: AudioContext): void {\n const model = this.model;\n if (!model) return;\n\n const clampedUs = Math.max(0, this.previewLastTimelineUs);\n const blockIndex = Math.floor(clampedUs / this.PREVIEW_BLOCK_DURATION_US);\n const blockStartUs = blockIndex * this.PREVIEW_BLOCK_DURATION_US;\n if (blockStartUs >= model.durationUs) return;\n\n this.stopAllPreviewSources();\n this.previewFirstBlockOffsetUs = clampedUs - blockStartUs;\n this.previewNextBlockIndex = blockIndex;\n this.previewNextScheduleTime = audioContext.currentTime + 0.02;\n }\n\n private async runPreviewBlockSchedulingLoop(\n audioContext: AudioContext,\n token: number\n ): Promise<void> {\n while (this.isPlaying && this.previewScheduleToken === token) {\n const model = this.model;\n if (!model) return;\n\n // End-of-timeline: nothing more to schedule. Mark scheduling as \"far enough\" so future\n // scheduleAudio() calls won't spin.\n const nextBlockStartUs = this.previewNextBlockIndex * this.PREVIEW_BLOCK_DURATION_US;\n if (nextBlockStartUs >= model.durationUs) {\n this.previewNextScheduleTime = audioContext.currentTime + this.PREVIEW_SCHEDULE_AHEAD_SEC;\n return;\n }\n\n const scheduleAheadTime = audioContext.currentTime + this.PREVIEW_SCHEDULE_AHEAD_SEC;\n if (this.previewNextScheduleTime >= scheduleAheadTime) return;\n\n const prevBlockIndex = this.previewNextBlockIndex;\n const prevScheduleTime = this.previewNextScheduleTime;\n\n await this.scheduleNextPreviewBlock(audioContext, token);\n\n // If scheduling made no progress, bail out to avoid a tight loop that can freeze UI.\n // The next RAF tick will call scheduleAudio() again after some time has advanced.\n if (\n this.previewScheduleToken === token &&\n this.previewNextBlockIndex === prevBlockIndex &&\n this.previewNextScheduleTime === prevScheduleTime\n ) {\n // Throttled diagnostic: log at most once per second to avoid console spam.\n const now = audioContext.currentTime;\n if (now - this.previewLastStallWarnAt >= 1) {\n this.previewLastStallWarnAt = now;\n console.warn(\n '[GlobalAudioSession][preview] scheduling stalled; stop loop to avoid spin',\n {\n prevBlockIndex,\n prevScheduleTime,\n now,\n }\n );\n }\n return;\n }\n }\n }\n\n private async getOrCreateMixedBlock(blockIndex: number): Promise<AudioBuffer | null> {\n const model = this.model;\n if (!model) return null;\n\n const token = this.previewMixToken;\n return await this.previewBlockCache.getOrCreate(blockIndex, async () => {\n return await this.enqueuePreviewMix(async () => {\n const startUs = blockIndex * this.PREVIEW_BLOCK_DURATION_US;\n const endUs = Math.min(startUs + this.PREVIEW_BLOCK_DURATION_US, model.durationUs);\n await this.ensureAudioForTimeRange(startUs, endUs, {\n mode: 'blocking',\n loadResource: true,\n });\n const mixed = await this.mixer.mix(startUs, endUs);\n\n // Preview uses mixed AudioBuffer blocks as the primary cache. PCM in AudioL1Cache is a transient\n // intermediate and can be cleared after each block to keep memory bounded (same idea as export).\n this.deps.cacheManager.clearAudioCache();\n\n return mixed;\n }, token);\n });\n }\n\n private async scheduleNextPreviewBlock(audioContext: AudioContext, token: number): Promise<void> {\n const model = this.model;\n if (!this.isPlaying || !model) return;\n if (this.previewScheduleToken !== token) return;\n\n // If we're behind the audio clock, resync to avoid attempting to schedule in the past.\n if (this.previewNextScheduleTime < audioContext.currentTime + 0.01) {\n // Important: when we fall behind (e.g. mixing slower than expected), we must re-align\n // blockIndex + offset to current timeline; otherwise we'd restart the block from its beginning\n // and cause A/V desync.\n this.realignPreviewSchedulingToTimeline(audioContext);\n }\n\n const blockIndex = this.previewNextBlockIndex;\n const blockStartUs = blockIndex * this.PREVIEW_BLOCK_DURATION_US;\n if (blockStartUs >= model.durationUs) {\n // End-of-timeline: advance state so scheduling loop can finish without stalling.\n this.previewFirstBlockOffsetUs = 0;\n this.previewNextBlockIndex = blockIndex + 1;\n this.previewNextScheduleTime = audioContext.currentTime + this.PREVIEW_SCHEDULE_AHEAD_SEC;\n return;\n }\n\n const buffer = await this.getOrCreateMixedBlock(blockIndex);\n if (!buffer) {\n // Could be cancelled (seek/reset) or an empty-range guard; advance to avoid stalling.\n this.previewFirstBlockOffsetUs = 0;\n this.previewNextBlockIndex = blockIndex + 1;\n this.previewNextScheduleTime = audioContext.currentTime + 0.02;\n return;\n }\n if (this.previewScheduleToken !== token) return;\n\n const rate = this.playbackRate || 1.0;\n const offsetUs = this.previewFirstBlockOffsetUs;\n let startTime = this.previewNextScheduleTime;\n let offsetSec = offsetUs > 0 ? offsetUs / 1_000_000 : 0;\n\n // Mixing can be slow; after await, startTime might already be in the past.\n // If so, resync startTime to \"now\" and advance offset accordingly to preserve A/V sync.\n const now = audioContext.currentTime;\n if (startTime < now + 0.01) {\n const targetStartTime = now + 0.02;\n const skippedSec = Math.max(0, (targetStartTime - startTime) * rate);\n startTime = targetStartTime;\n offsetSec += skippedSec;\n }\n\n // If the computed offset already exceeds this block's buffer duration, it means the timeline\n // has moved past this block while we were preparing. Skipping the block is required to:\n // - avoid scheduling a zero-length source\n // - avoid a tight scheduling loop (remainingSec <= 0 would return without advancing state)\n if (offsetSec >= buffer.duration) {\n this.previewFirstBlockOffsetUs = 0;\n this.previewNextBlockIndex = blockIndex + 1;\n this.previewNextScheduleTime = startTime;\n return;\n }\n\n const remainingSec = Math.max(0, buffer.duration - offsetSec);\n if (remainingSec <= 0) return;\n\n const source = audioContext.createBufferSource();\n source.buffer = buffer;\n source.playbackRate.value = rate;\n\n const gainNode = audioContext.createGain();\n const volume = this.volume;\n\n // Fade in/out to avoid click at block boundaries (no overlap in timeline; preserves A/V timing).\n const fadeSec = Math.min(this.PREVIEW_BLOCK_FADE_SEC, remainingSec / 2);\n gainNode.gain.setValueAtTime(0, startTime);\n gainNode.gain.linearRampToValueAtTime(volume, startTime + fadeSec);\n gainNode.gain.setValueAtTime(volume, startTime + Math.max(fadeSec, remainingSec - fadeSec));\n gainNode.gain.linearRampToValueAtTime(0, startTime + remainingSec);\n\n source.connect(gainNode);\n gainNode.connect(audioContext.destination);\n source.start(startTime, offsetSec);\n\n const entry = { source, gain: gainNode };\n this.previewScheduledSources.add(entry);\n\n source.onended = () => {\n try {\n source.disconnect();\n gainNode.disconnect();\n } catch {\n // ignore\n }\n this.previewScheduledSources.delete(entry);\n };\n\n // Advance to next block\n this.previewFirstBlockOffsetUs = 0;\n this.previewNextBlockIndex = blockIndex + 1;\n this.previewNextScheduleTime = startTime + remainingSec / rate;\n }\n\n private stopAllPreviewSources(): void {\n for (const { source, gain } of this.previewScheduledSources) {\n // Best-effort cleanup:\n // AudioNode.stop/disconnect can throw depending on node state (not started yet, already stopped, etc).\n // We intentionally isolate each call so one failure doesn't prevent other nodes from being stopped.\n try {\n source.disconnect();\n } catch {\n // ignore\n }\n try {\n // stop(0) for immediate stop; may throw if not started yet\n source.stop(0);\n } catch {\n // ignore\n }\n try {\n gain.disconnect();\n } catch {\n // ignore\n }\n }\n this.previewScheduledSources.clear();\n }\n\n reset(): void {\n this.stopAllPreviewSources();\n this.deps.cacheManager.clearAudioCache();\n this.activeClips.clear();\n this.previewBlockCache.clear();\n this.cancelPreviewBlockScheduling();\n this.previewMixToken += 1;\n }\n\n /**\n * Mix and encode audio for a specific segment (used by ExportScheduler)\n */\n async mixAndEncodeSegment(\n startUs: TimeUs,\n endUs: TimeUs,\n onChunk: (chunk: EncodedAudioChunk, metadata?: EncodedAudioChunkMetadata) => void\n ): Promise<void> {\n // Wait for audio clips in this time range to be ready (on-demand wait)\n await this.ensureAudioForSegment(startUs, endUs);\n\n const mixedBuffer = await this.mixer.mix(startUs, endUs);\n const audioData = this.audioBufferToAudioData(mixedBuffer, startUs);\n\n if (!audioData) return;\n\n if (!this.exportEncoder) {\n this.exportEncoder = new AudioChunkEncoder();\n await this.exportEncoder.initialize();\n this.exportEncoderStream = this.exportEncoder.createStream();\n this.exportEncoderWriter = this.exportEncoderStream.writable.getWriter();\n\n // Start reader immediately (but don't await - it's a long-running loop)\n void this.startExportEncoderReader(this.exportEncoderStream.readable, onChunk);\n\n // Wait a bit to ensure reader is ready before first write\n await new Promise((resolve) => setTimeout(resolve, 10));\n }\n\n await this.exportEncoderWriter?.write(audioData);\n }\n\n /**\n * Ensure audio clips in time range are decoded (for export)\n * Decodes from AudioSampleCache (replaces Worker pipeline)\n */\n private async ensureAudioForSegment(startUs: TimeUs, endUs: TimeUs): Promise<void> {\n // Export mode: don't load resources (they should already be loaded), only decode cached samples\n // Use strictMode=true to ensure 99% coverage for high-quality export\n await this.ensureAudioForTimeRange(startUs, endUs, {\n mode: 'blocking',\n loadResource: false,\n strictMode: true,\n });\n }\n\n private exportEncoder: AudioChunkEncoder | null = null;\n private exportEncoderStream: TransformStream<\n AudioData,\n { chunk: EncodedAudioChunk; metadata: any }\n > | null = null;\n private exportEncoderWriter: WritableStreamDefaultWriter<AudioData> | null = null;\n\n private async startExportEncoderReader(\n stream: ReadableStream<{ chunk: EncodedAudioChunk; metadata: any }>,\n onChunk: (chunk: EncodedAudioChunk, metadata?: EncodedAudioChunkMetadata) => void\n ) {\n const reader = stream.getReader();\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n if (value) {\n onChunk(value.chunk, value.metadata);\n }\n }\n } catch (e) {\n console.error('Export encoder reader error', e);\n }\n }\n\n async finalizeExportAudio(): Promise<void> {\n if (this.exportEncoderWriter) {\n await this.exportEncoderWriter.close();\n this.exportEncoderWriter = null;\n }\n this.exportEncoder = null;\n this.exportEncoderStream = null;\n }\n\n // Preview source scheduling is managed by stopAllPreviewSources()/previewScheduledSources.\n\n /**\n * Core method to ensure audio for all clips in a time range\n * Unified implementation used by ensureAudioForTime, scheduleAudio, and export\n */\n private async ensureAudioForTimeRange(\n startUs: TimeUs,\n endUs: TimeUs,\n options: { mode?: RequestMode; loadResource?: boolean; strictMode?: boolean }\n ): Promise<void> {\n const model = this.model;\n if (!model) return;\n\n const { mode = 'blocking', loadResource = true, strictMode = false } = options;\n\n // Find all clips that overlap with [startUs, endUs]\n const activeClips = model.getActiveClips(startUs, endUs);\n\n const ensurePromises = activeClips.map(async (clip) => {\n // Only process audio and video clips\n if (clip.trackKind !== 'audio' && clip.trackKind !== 'video') return;\n if (!hasResourceId(clip)) return;\n\n // Skip clips without audio track (performance optimization)\n // If AudioSampleCache doesn't have this resource, and resource is ready,\n // it means the resource has no audio track (e.g., video-only or image)\n const resource = model.getResource(clip.resourceId);\n if (\n resource?.state === 'ready' &&\n !this.deps.cacheManager.audioSampleCache.has(clip.resourceId)\n ) {\n // Resource is ready but has no audio samples - skip\n return;\n }\n\n // Ensure AudioSampleCache has data\n if (!this.deps.cacheManager.audioSampleCache.has(clip.resourceId)) {\n if (!loadResource) {\n // Export mode: skip clips without cached samples\n return;\n }\n\n const resource = model.getResource(clip.resourceId);\n if (resource?.state !== 'ready') {\n // Resource not yet loaded - wait for it\n await this.deps.resourceLoader.load(clip.resourceId, {\n isPreload: false,\n clipId: clip.id,\n trackId: clip.trackId,\n });\n }\n }\n\n // Calculate clip-relative time range\n const clipRelativeStartUs = Math.max(0, startUs - clip.startUs);\n const clipRelativeEndUs = Math.min(clip.durationUs, endUs - clip.startUs);\n\n // Convert to resource time (aligned with video architecture)\n // This ensures correct filtering of audio samples and cache queries\n const trimStartUs = clip.trimStartUs ?? 0;\n const resourceStartUs = clipRelativeStartUs + trimStartUs;\n const resourceEndUs = clipRelativeEndUs + trimStartUs;\n\n // Ensure audio window using resource time coordinates\n await this.ensureAudioWindow(clip.id, resourceStartUs, resourceEndUs, strictMode);\n });\n\n if (mode === 'probe') {\n void Promise.all(ensurePromises);\n return;\n }\n await Promise.all(ensurePromises);\n }\n\n /**\n * Ensure audio window for a clip (aligned with video architecture)\n *\n * Note: Unlike video getFrame(), this method doesn't need a 'preheat' parameter\n * Why: Audio cache check is window-level (range query) via hasWindowPCM()\n * It verifies the entire window has ≥95% data (preview) or ≥99% (export)\n * This naturally prevents premature return during preheating\n */\n async ensureAudioWindow(\n clipId: string,\n startUs: TimeUs,\n endUs: TimeUs,\n strictMode: boolean = false\n ): Promise<void> {\n // Check L1 cache - window-level verification (not point-level like video)\n if (this.deps.cacheManager.hasWindowPCM(clipId, startUs, endUs, strictMode)) {\n return; // Entire window has sufficient data\n }\n\n await this.decodeAudioWindow(clipId, startUs, endUs);\n }\n\n /**\n * Decode audio window for a clip (aligned with video architecture)\n * Incremental decoding strategy with smart fallback:\n * - High coverage (≥80%): Skip decoding\n * - Low coverage (<30%): Full decode (avoid fragmentation)\n * - Medium coverage (30%-80%): Incremental decode\n */\n private async decodeAudioWindow(clipId: string, startUs: TimeUs, endUs: TimeUs): Promise<void> {\n const clip = this.model?.findClip(clipId);\n if (!clip || !hasResourceId(clip)) {\n return;\n }\n\n // Get audio samples from AudioSampleCache\n const audioRecord = this.deps.cacheManager.audioSampleCache.get(clip.resourceId);\n if (!audioRecord) {\n // Resource has no audio track (common for some video files)\n return;\n }\n\n // Filter chunks within window (aligned with video GOP filtering)\n const windowChunks = audioRecord.samples.filter((s) => {\n const sampleEndUs = s.timestamp + (s.duration ?? 0);\n return s.timestamp < endUs && sampleEndUs > startUs;\n });\n\n if (windowChunks.length === 0) {\n return;\n }\n\n // Incremental decoding with smart threshold strategy\n // SKIP threshold should be high to avoid audio gaps (e.g., 95% means max 5% missing data)\n const INCREMENTAL_THRESHOLD = 0.95; // 95% coverage: skip decode (was 0.8, increased to avoid audio gaps)\n const FULL_FALLBACK_THRESHOLD = 0.3; // <30% coverage: full decode to avoid fragmentation\n\n // Check window-level coverage\n const coverage = this.deps.cacheManager.getAudioRangeCoverage(\n clipId,\n startUs,\n endUs,\n INCREMENTAL_THRESHOLD\n );\n\n // Strategy 1: High coverage - skip decode entirely\n if (coverage.covered) {\n return;\n }\n\n // Strategy 2: Very low coverage - full decode (avoid fragmentation overhead)\n if (coverage.coverageRatio < FULL_FALLBACK_THRESHOLD) {\n await this.decodeAudioSamples(\n clipId,\n windowChunks,\n audioRecord.metadata,\n clip.durationUs,\n clip.startUs\n );\n return;\n }\n\n // Strategy 3: Medium coverage - incremental decode (30%-95%)\n // Filter out chunks that are already well-covered in L1 Cache\n const chunksToDecode = windowChunks.filter((chunk) => {\n const chunkEndUs = chunk.timestamp + (chunk.duration ?? 0);\n const chunkCoverage = this.deps.cacheManager.getAudioRangeCoverage(\n clipId,\n chunk.timestamp,\n chunkEndUs,\n 0.95 // Stricter threshold for individual chunks\n );\n return !chunkCoverage.covered;\n });\n\n if (chunksToDecode.length === 0) {\n return;\n }\n\n // Decode only missing chunks\n await this.decodeAudioSamples(\n clipId,\n chunksToDecode,\n audioRecord.metadata,\n clip.durationUs,\n clip.startUs\n );\n }\n\n /**\n * Decode audio samples to PCM and cache\n * Uses AudioChunkDecoder for consistency with project architecture\n * Resamples to AudioContext sample rate if needed for better quality\n */\n private async decodeAudioSamples(\n clipId: string,\n samples: EncodedAudioChunk[],\n config: AudioDecoderConfig,\n clipDurationUs: number,\n clipStartUs: TimeUs\n ): Promise<void> {\n // Use AudioChunkDecoder for consistency with project architecture\n // Convert description to ArrayBuffer if needed for type compatibility\n let description: ArrayBuffer | undefined;\n if (config.description) {\n if (config.description instanceof ArrayBuffer) {\n description = config.description;\n } else if (ArrayBuffer.isView(config.description)) {\n // Convert TypedArray to ArrayBuffer\n const view = config.description as Uint8Array;\n // Create a new ArrayBuffer and copy data to ensure proper type\n const newBuffer = new ArrayBuffer(view.byteLength);\n new Uint8Array(newBuffer).set(\n new Uint8Array(view.buffer, view.byteOffset, view.byteLength)\n );\n description = newBuffer;\n }\n }\n\n const decoderConfig = {\n codec: config.codec,\n sampleRate: config.sampleRate,\n numberOfChannels: config.numberOfChannels,\n description,\n };\n const decoder = new AudioChunkDecoder(`audio-${clipId}`, decoderConfig);\n\n try {\n // Create chunk stream\n const chunkStream = new ReadableStream<EncodedAudioChunk>({\n start(controller) {\n for (const sample of samples) {\n controller.enqueue(sample);\n }\n controller.close();\n },\n });\n\n // Decode through stream\n const audioDataStream = chunkStream.pipeThrough(decoder.createStream());\n const reader = audioDataStream.getReader();\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n if (value) {\n // Store original sample rate - OfflineAudioMixer will handle resampling\n const globalTimeUs = clipStartUs + (value.timestamp ?? 0);\n this.deps.cacheManager.putClipAudioData(clipId, value, clipDurationUs, globalTimeUs);\n }\n }\n } finally {\n reader.releaseLock();\n }\n } catch (error) {\n console.error(`[GlobalAudioSession] Decoder error for clip ${clipId}:`, error);\n throw error;\n } finally {\n await decoder.close();\n }\n }\n\n private audioBufferToAudioData(buffer: AudioBuffer, timestampUs: TimeUs): AudioData | null {\n const sampleRate = buffer.sampleRate;\n const numberOfChannels = buffer.numberOfChannels;\n const numberOfFrames = buffer.length;\n\n const planes: Float32Array[] = [];\n for (let channel = 0; channel < numberOfChannels; channel++) {\n planes.push(buffer.getChannelData(channel));\n }\n\n return new AudioData({\n format: 'f32-planar',\n sampleRate,\n numberOfFrames,\n numberOfChannels,\n timestamp: timestampUs,\n data: this.packPlanarF32Data(planes),\n });\n }\n\n private packPlanarF32Data(planes: Float32Array[]): ArrayBuffer {\n const numberOfChannels = planes.length;\n const numberOfFrames = planes[0]?.length ?? 0;\n const totalSamples = numberOfChannels * numberOfFrames;\n const packed = new Float32Array(totalSamples);\n\n // f32-planar layout: [ch0 frames][ch1 frames]...[chN frames]\n for (let channel = 0; channel < numberOfChannels; channel++) {\n const plane = planes[channel];\n if (!plane) continue;\n packed.set(plane, channel * numberOfFrames);\n }\n\n return packed.buffer;\n }\n}\n"],"names":["blockEndUs","remainingToBoundaryUs","resource"],"mappings":"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"}
|