@meframe/core 0.1.6 → 0.1.8
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/cache/CacheManager.d.ts +12 -0
- package/dist/cache/CacheManager.d.ts.map +1 -1
- package/dist/cache/CacheManager.js +14 -2
- package/dist/cache/CacheManager.js.map +1 -1
- package/dist/cache/l1/AudioL1Cache.d.ts +15 -0
- package/dist/cache/l1/AudioL1Cache.d.ts.map +1 -1
- package/dist/cache/l1/AudioL1Cache.js +38 -8
- package/dist/cache/l1/AudioL1Cache.js.map +1 -1
- package/dist/controllers/PlaybackController.d.ts +18 -28
- package/dist/controllers/PlaybackController.d.ts.map +1 -1
- package/dist/controllers/PlaybackController.js +343 -253
- package/dist/controllers/PlaybackController.js.map +1 -1
- package/dist/controllers/PlaybackStateMachine.d.ts +16 -0
- package/dist/controllers/PlaybackStateMachine.d.ts.map +1 -0
- package/dist/controllers/PlaybackStateMachine.js +313 -0
- package/dist/controllers/PlaybackStateMachine.js.map +1 -0
- package/dist/controllers/index.d.ts +2 -1
- package/dist/controllers/index.d.ts.map +1 -1
- package/dist/controllers/types.d.ts +172 -2
- package/dist/controllers/types.d.ts.map +1 -1
- package/dist/controllers/types.js +54 -0
- package/dist/controllers/types.js.map +1 -0
- package/dist/model/types.d.ts +0 -1
- package/dist/model/types.d.ts.map +1 -1
- package/dist/model/types.js.map +1 -1
- package/dist/orchestrator/ExportScheduler.d.ts.map +1 -1
- package/dist/orchestrator/ExportScheduler.js +22 -19
- package/dist/orchestrator/ExportScheduler.js.map +1 -1
- package/dist/orchestrator/GlobalAudioSession.d.ts +18 -2
- package/dist/orchestrator/GlobalAudioSession.d.ts.map +1 -1
- package/dist/orchestrator/GlobalAudioSession.js +79 -13
- package/dist/orchestrator/GlobalAudioSession.js.map +1 -1
- package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
- package/dist/orchestrator/Orchestrator.js +8 -3
- package/dist/orchestrator/Orchestrator.js.map +1 -1
- package/dist/orchestrator/VideoClipSession.d.ts.map +1 -1
- package/dist/orchestrator/VideoClipSession.js +4 -2
- package/dist/orchestrator/VideoClipSession.js.map +1 -1
- package/dist/orchestrator/types.d.ts +7 -1
- package/dist/orchestrator/types.d.ts.map +1 -1
- package/dist/stages/load/ResourceLoader.d.ts.map +1 -1
- package/dist/stages/load/ResourceLoader.js +13 -6
- package/dist/stages/load/ResourceLoader.js.map +1 -1
- package/dist/utils/timeout-utils.d.ts +9 -0
- package/dist/utils/timeout-utils.d.ts.map +1 -0
- package/dist/worker/BaseWorker.d.ts +2 -0
- package/dist/worker/BaseWorker.d.ts.map +1 -1
- package/dist/worker/BaseWorker.js.map +1 -1
- package/dist/worker/WorkerChannel.d.ts +2 -0
- package/dist/worker/WorkerChannel.d.ts.map +1 -1
- package/dist/worker/WorkerChannel.js +17 -1
- package/dist/worker/WorkerChannel.js.map +1 -1
- package/dist/workers/{WorkerChannel.DjBEVvEA.js → WorkerChannel.DQK8rAab.js} +18 -2
- package/dist/workers/{WorkerChannel.DjBEVvEA.js.map → WorkerChannel.DQK8rAab.js.map} +1 -1
- package/dist/workers/stages/compose/{audio-compose.worker.CiM_KP27.js → audio-compose.worker.B4Io5w9i.js} +2 -2
- package/dist/workers/stages/compose/{audio-compose.worker.CiM_KP27.js.map → audio-compose.worker.B4Io5w9i.js.map} +1 -1
- package/dist/workers/stages/compose/{video-compose.worker.CQwmNfXT.js → video-compose.worker.CA2_Kpg-.js} +2 -2
- package/dist/workers/stages/compose/{video-compose.worker.CQwmNfXT.js.map → video-compose.worker.CA2_Kpg-.js.map} +1 -1
- package/dist/workers/stages/decode/{audio-decode.worker.CpjkrZtT.js → audio-decode.worker.-DGlQrJD.js} +2 -2
- package/dist/workers/stages/decode/{audio-decode.worker.CpjkrZtT.js.map → audio-decode.worker.-DGlQrJD.js.map} +1 -1
- package/dist/workers/stages/decode/{video-decode.worker.BQtw6eWn.js → video-decode.worker.BnWVUkng.js} +2 -2
- package/dist/workers/stages/decode/{video-decode.worker.BQtw6eWn.js.map → video-decode.worker.BnWVUkng.js.map} +1 -1
- package/dist/workers/stages/demux/{audio-demux.worker.C4V11GQi.js → audio-demux.worker.D-_LoVqW.js} +2 -2
- package/dist/workers/stages/demux/{audio-demux.worker.C4V11GQi.js.map → audio-demux.worker.D-_LoVqW.js.map} +1 -1
- package/dist/workers/stages/demux/{video-demux.worker.5pJr0Ij-.js → video-demux.worker.BWDrLGni.js} +2 -2
- package/dist/workers/stages/demux/{video-demux.worker.5pJr0Ij-.js.map → video-demux.worker.BWDrLGni.js.map} +1 -1
- package/dist/workers/stages/encode/{video-encode.worker.CX2_3YhQ.js → video-encode.worker.D6aB_rF9.js} +2 -2
- package/dist/workers/stages/encode/{video-encode.worker.CX2_3YhQ.js.map → video-encode.worker.D6aB_rF9.js.map} +1 -1
- package/dist/workers/worker-manifest.json +7 -7
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlaybackController.js","sources":["../../src/controllers/PlaybackController.ts"],"sourcesContent":["import type {\n IPlaybackController,\n PlaybackState,\n PlaybackOptions,\n IEventBus,\n PreviewHandle,\n TimeUs,\n} from './types';\nimport { MeframeEvent } from '../event/events';\nimport type { GlobalAudioSession } from '../orchestrator/GlobalAudioSession';\nimport type { Orchestrator } from '../orchestrator';\nimport { WaiterReplacedError } from '../utils/errors';\nimport { VideoComposer } from '../stages/compose/VideoComposer';\nimport { isVideoClip } from '../model/types';\n\n/**\n * Playback controller for preview\n * Internal implementation - not exposed directly to external consumers\n */\nexport class PlaybackController implements IPlaybackController, PreviewHandle {\n private orchestrator: Orchestrator;\n private eventBus: IEventBus;\n private canvas: HTMLCanvasElement | OffscreenCanvas;\n private videoComposer: VideoComposer | null = null;\n\n // Playback state\n currentTimeUs: TimeUs = 0;\n private state: PlaybackState = 'idle';\n private playbackRate = 1.0;\n private volume = 1.0;\n private loop = false;\n\n // Animation loop\n private rafId: number | null = null;\n private startTimeUs: TimeUs = 0; // Playback start position in AudioContext timeline (microseconds)\n\n // Frame tracking\n private frameCount = 0;\n private lastFrameTime = 0;\n private fps = 0;\n private audioContext: AudioContext;\n private audioSession: GlobalAudioSession;\n\n // Seek tracking\n private currentSeekId = 0;\n private wasPlayingBeforeSeek = false;\n\n // Unified window management for both video and audio\n private windowEnd: TimeUs = 0;\n private readonly WINDOW_DURATION = 3_000_000; // 3s decode window\n private readonly PREHEAT_DISTANCE = 1_000_000; // 1s preheat trigger distance\n private preheatInProgress = false;\n\n // Audio scheduling throttle to reduce CPU overhead\n private lastAudioScheduleTime = 0;\n private readonly AUDIO_SCHEDULE_INTERVAL = 100_000; // 100ms (~3 frames at 30fps)\n\n constructor(orchestrator: Orchestrator, eventBus: IEventBus, options: PlaybackOptions) {\n this.orchestrator = orchestrator;\n this.audioSession = orchestrator.audioSession;\n this.eventBus = eventBus;\n this.canvas = options.canvas;\n this.audioContext = new AudioContext();\n\n // Initialize VideoComposer for real-time composition\n // Pass canvas as externalCanvas for direct rendering\n const model = orchestrator.compositionModel;\n\n // FIX: Prioritize model renderConfig over canvas current dimensions\n // The canvas should act as a viewport into the renderConfig resolution\n const width = model?.renderConfig?.width || this.canvas.width || 720;\n const height = model?.renderConfig?.height || this.canvas.height || 1280;\n\n this.videoComposer = new VideoComposer({\n width,\n height,\n fps: model?.fps || 30,\n backgroundColor: model?.renderConfig?.backgroundColor || '#000',\n externalCanvas: this.canvas,\n });\n\n // Set initial time if provided\n if (options.startUs !== undefined) {\n this.currentTimeUs = options.startUs;\n }\n\n if (options.rate !== undefined) {\n this.playbackRate = options.rate;\n }\n\n if (options.loop !== undefined) {\n this.loop = options.loop;\n }\n\n if (options.autoStart) {\n this.play();\n }\n\n this.setupEventListeners();\n }\n\n async renderCover(): Promise<void> {\n await this.renderCurrentFrame(0);\n }\n\n // Playback control\n play(): void {\n if (this.state === 'playing') return;\n\n // Always reset throttle timer when starting/resuming playback\n // This ensures audio scheduling triggers immediately on first frame\n this.lastAudioScheduleTime = 0;\n\n this.wasPlayingBeforeSeek = true; // User wants to play\n this.startPlayback();\n // Note: resetPlaybackStates() is called inside startPlayback() → audioSession.startPlayback()\n }\n\n private async startPlayback(): Promise<void> {\n const wasIdle = this.state === 'idle';\n const seekId = this.currentSeekId;\n this.state = 'playing';\n\n try {\n // Render first frame (may trigger buffering if cache miss)\n await this.renderCurrentFrame(this.currentTimeUs);\n\n // Check if seek happened during render or playback was stopped/paused\n if (seekId !== this.currentSeekId || this.state !== 'playing') {\n return;\n }\n\n // Initialize windows BEFORE starting audio playback\n this.initWindow(this.currentTimeUs);\n\n await this.audioSession.startPlayback(this.currentTimeUs, this.audioContext);\n\n this.startTimeUs =\n this.audioContext!.currentTime * 1_000_000 - this.currentTimeUs / this.playbackRate;\n\n this.playbackLoop();\n\n this.eventBus.emit(MeframeEvent.PlaybackPlay);\n } catch (error) {\n console.error('[PlaybackController] Failed to start playback:', error);\n this.state = wasIdle ? 'idle' : 'paused';\n this.eventBus.emit(MeframeEvent.PlaybackError, error as Error);\n }\n }\n\n pause(): void {\n this.state = 'paused';\n this.wasPlayingBeforeSeek = false; // User explicitly paused\n\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n\n this.audioSession.stopPlayback();\n\n // Cancel any ongoing async operations (buffering, seeking, etc.)\n this.currentSeekId++;\n\n this.eventBus.emit(MeframeEvent.PlaybackPause);\n }\n\n stop(): void {\n this.pause();\n this.currentTimeUs = 0;\n this.state = 'idle';\n this.wasPlayingBeforeSeek = false; // Reset seek state\n this.frameCount = 0; // Reset frame counter\n this.lastFrameTime = 0; // Reset frame timing\n this.lastAudioScheduleTime = 0; // Reset throttle timer\n\n // Clear canvas\n const ctx = this.canvas.getContext('2d') as\n | CanvasRenderingContext2D\n | OffscreenCanvasRenderingContext2D\n | null;\n if (ctx && 'clearRect' in ctx) {\n ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);\n }\n\n this.audioSession.reset();\n this.audioSession.resetPlaybackStates();\n\n this.eventBus.emit(MeframeEvent.PlaybackStop);\n }\n\n async seek(timeUs: TimeUs): Promise<void> {\n const previousState = this.state;\n\n this.orchestrator.cancelActiveDecoding();\n\n // Stop playback without changing wasPlayingBeforeSeek\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n this.audioSession.stopPlayback();\n\n // Reset throttle timer for seek\n this.lastAudioScheduleTime = 0;\n // Note: resetPlaybackStates() will be called in startPlayback() if needed\n // Calling it here would be premature as there may be a delay before startPlayback()\n\n const clamped = this.clampTime(timeUs);\n this.currentTimeUs = clamped;\n this.currentSeekId++; // Invalidate previous seek operations\n\n this.state = 'seeking';\n\n const seekId = this.currentSeekId;\n\n try {\n // Fast path: Try to render nearest keyframe immediately to avoid black screen\n const keyframeTimeUs = await this.orchestrator.tryRenderKeyframe(clamped);\n if (keyframeTimeUs !== null) {\n // Render keyframe from L1 cache\n const renderState = await this.orchestrator.getRenderState(clamped, {\n immediate: true,\n relativeTimeUs: keyframeTimeUs,\n });\n if (renderState && this.videoComposer) {\n await this.videoComposer.composeFrame({\n timeUs: clamped,\n layers: renderState.layers,\n transition: renderState.transition,\n });\n }\n }\n\n // Check if another seek happened during keyframe render\n if (seekId !== this.currentSeekId) {\n return;\n }\n\n // Ensure audio windows ready for seek position (blocking mode)\n // CRITICAL: Do this BEFORE updating window to prevent premature eviction\n // When seeking backwards (e.g. 10s -> 3s), updating window first would\n // evict data at 3s before we can decode it\n await this.audioSession.ensureAudioForTime(clamped, { immediate: false });\n\n // Force decode entire window even if keyframe is in L1\n // preheat: true skips L1 check and decodes full window (60-120 frames)\n await this.orchestrator.getFrame(clamped, {\n immediate: false,\n preheat: true,\n });\n\n // Update window AFTER decoding to prevent evicting data we just needed\n // This ensures newly decoded data won't be immediately evicted\n this.initWindow(clamped);\n\n if (seekId !== this.currentSeekId) {\n return;\n }\n\n await this.renderCurrentFrame(clamped);\n\n // Check if another seek happened during render\n if (seekId !== this.currentSeekId) {\n return;\n }\n\n this.eventBus.emit(MeframeEvent.PlaybackSeek, { timeUs: this.currentTimeUs });\n\n if (this.wasPlayingBeforeSeek) {\n await this.startPlayback();\n } else {\n this.state = previousState === 'idle' ? 'idle' : 'paused';\n }\n } catch (error) {\n // Check if this seek is still current\n if (seekId !== this.currentSeekId) {\n return;\n }\n console.error('[PlaybackController] Seek error:', error);\n this.eventBus.emit(MeframeEvent.PlaybackError, error as Error);\n this.state = previousState === 'idle' ? 'idle' : 'paused';\n }\n }\n\n // Playback properties\n setRate(rate: number): void {\n // Adjust audio start time to maintain current position\n const currentTimeUs = this.currentTimeUs;\n this.playbackRate = rate;\n this.startTimeUs = this.audioContext!.currentTime * 1_000_000 - currentTimeUs / rate;\n\n this.eventBus.emit(MeframeEvent.PlaybackRateChange, { rate });\n\n this.audioSession.setPlaybackRate(this.playbackRate);\n }\n\n setVolume(volume: number): void {\n this.volume = Math.max(0, Math.min(1, volume));\n this.eventBus.emit(MeframeEvent.PlaybackVolumeChange, { volume: this.volume });\n\n this.audioSession.setVolume(this.volume);\n }\n\n setMute(muted: boolean): void {\n if (muted) {\n this.audioSession.stopPlayback();\n } else if (this.state === 'playing') {\n this.audioSession.startPlayback(this.currentTimeUs, this.audioContext);\n }\n }\n\n setLoop(loop: boolean): void {\n this.loop = loop;\n }\n\n get duration(): TimeUs {\n const modelDuration = this.orchestrator.compositionModel?.durationUs;\n if (modelDuration !== undefined) {\n return modelDuration;\n }\n\n return 0;\n }\n\n get isPlaying(): boolean {\n return this.state === 'playing';\n }\n\n // Resume is just an alias for play\n resume(): void {\n this.play();\n }\n\n on(event: string, handler: (payload: any) => void): void {\n this.eventBus.on(event as MeframeEvent, handler);\n }\n\n off(event: string, handler: (payload: any) => void): void {\n this.eventBus.off(event as MeframeEvent, handler);\n }\n\n // Private methods\n private playbackLoop(): void {\n // State Check #1: Loop entry (from recursive call)\n // Prevents next frame from starting if state changed during previous frame\n // Captures: pause(), stop(), seek() called during frame processing\n if (this.state !== 'playing') {\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n return;\n }\n\n this.rafId = requestAnimationFrame(async () => {\n // State Check #2: RAF callback start (async boundary)\n // Captures state changes during RAF delay (~16ms)\n // Scenario: User pauses/seeks between requestAnimationFrame call and callback execution\n if (this.state !== 'playing') {\n return;\n }\n\n this.updateTime();\n\n // State Check #3: After updateTime (playback end detection)\n // CRITICAL: Prevents ensureAudioForTime/scheduleAudio from polluting cache\n // When playback ends, currentTimeUs is reset to 0 for clean replay\n // Without this check, audio operations would execute with wrong position\n if (this.state !== 'playing') {\n return;\n }\n\n // Throttle audio scheduling to reduce CPU overhead (every 100ms instead of every frame)\n // This reduces audio operations from 30fps to ~10fps without audible impact\n if (this.currentTimeUs - this.lastAudioScheduleTime >= this.AUDIO_SCHEDULE_INTERVAL) {\n // Lookahead audio scheduling (OfflineAudioMixer approach)\n // scheduleAudio internally calls ensureAudioForTimeRange for each 100ms chunk\n // No need for separate ensureAudioForTime call which checks large 3s window\n await this.audioSession.scheduleAudio(this.currentTimeUs, this.audioContext);\n\n this.lastAudioScheduleTime = this.currentTimeUs;\n }\n\n await this.renderCurrentFrame(this.currentTimeUs);\n\n // State Check #4: After render (buffering detection)\n // Captures: renderCurrentFrame() triggered buffering (cache miss)\n // Optimization: Early exit to avoid unnecessary FPS calculation and setWindow\n // Not strictly required (next loop's Check #1 would catch it), but improves responsiveness\n if (this.state !== 'playing') {\n return;\n }\n\n // Calculate FPS based on actual frame timing\n const now = performance.now();\n if (this.lastFrameTime > 0) {\n const deltaTime = now - this.lastFrameTime;\n const instantFps = 1000 / deltaTime;\n this.fps = this.fps > 0 ? this.fps * 0.9 + instantFps * 0.1 : instantFps;\n }\n this.lastFrameTime = now;\n\n this.frameCount++;\n\n // Update unified window for video and audio\n this.orchestrator.cacheManager.setWindow(this.currentTimeUs);\n\n this.playbackLoop();\n });\n }\n\n private updateTime(): void {\n const elapsedUs =\n (this.audioContext!.currentTime * 1_000_000 - this.startTimeUs) * this.playbackRate;\n this.currentTimeUs = elapsedUs;\n\n // Check if reached end\n if (this.currentTimeUs >= this.duration) {\n if (this.loop) {\n this.currentTimeUs = 0;\n this.startTimeUs = this.audioContext!.currentTime * 1_000_000;\n // Reset audio scheduling states to avoid stale state from previous playback\n this.audioSession.resetPlaybackStates();\n this.lastAudioScheduleTime = 0; // Reset throttle timer for loop\n // Reset window to start position\n this.initWindow(0);\n } else {\n this.pause();\n // Set to 0 instead of duration to prevent audio operations in final frame\n // from polluting cache with wrong position data\n this.currentTimeUs = 0;\n this.state = 'ended';\n this.eventBus.emit(MeframeEvent.PlaybackEnded, { timeUs: this.duration });\n }\n }\n\n // Emit time update\n this.eventBus.emit(MeframeEvent.PlaybackTimeUpdate, { timeUs: this.currentTimeUs });\n\n // Check if approaching window edge - trigger async preheat\n this.checkAndPreheatWindow();\n // Note: setWindow is now called in Orchestrator.renderFrame()\n // this.orchestrator.cacheManager.setWindow(this.currentTimeUs);\n }\n\n /**\n * Initialize window at given time (called on play/seek)\n * Sets unified window for both video and audio\n */\n private initWindow(timeUs: TimeUs): void {\n this.windowEnd = timeUs + this.WINDOW_DURATION;\n this.preheatInProgress = false; // Reset preheat state\n\n // Set unified window center for both video and audio\n this.orchestrator.cacheManager.setWindow(timeUs);\n }\n\n /**\n * Check if approaching window end and trigger preheat for next window\n *\n * Strategy: Unified sliding window for both video and audio\n * - Current window: [windowStart, windowEnd] (3s duration)\n * - When playback reaches windowEnd - 1s, preheat next window\n * - Next window: [windowEnd, windowEnd + 3s]\n */\n private checkAndPreheatWindow(): void {\n // Skip if already preheating or not playing\n if (this.preheatInProgress || this.state !== 'playing') {\n return;\n }\n\n // Check if approaching window end\n const distanceToWindowEnd = this.windowEnd - this.currentTimeUs;\n if (distanceToWindowEnd < 0) {\n this.initWindow(this.currentTimeUs);\n return;\n }\n\n // Trigger preheat when 1s from window end\n if (distanceToWindowEnd > 0 && distanceToWindowEnd <= this.PREHEAT_DISTANCE) {\n this.preheatNextWindow();\n }\n }\n\n /**\n * Preheat next window by decoding from current playback time\n * Supports cross-clip window preheating for seamless playback\n * Preheats both video and audio in parallel\n */\n async preheatNextWindow(): Promise<void> {\n this.preheatInProgress = true;\n try {\n const windowStart = this.currentTimeUs;\n const windowEnd = windowStart + this.WINDOW_DURATION;\n\n // Get all clips in window range (may span multiple clips)\n const clipsInWindow =\n this.orchestrator.compositionModel?.getClipsInRange(windowStart, windowEnd) ?? [];\n const preheatPromises: Promise<any>[] = [];\n\n // Preheat video for each clip in window\n for (const clip of clipsInWindow) {\n if (!isVideoClip(clip)) continue;\n\n // Calculate clip-relative window boundaries\n const clipWindowStart = Math.max(0, windowStart - clip.startUs);\n const clipWindowEnd = Math.min(clip.durationUs, windowEnd - clip.startUs);\n\n // Skip if window doesn't overlap with this clip\n if (clipWindowStart >= clipWindowEnd) continue;\n\n preheatPromises.push(\n this.orchestrator.preheatClipWindow(clip.id, clipWindowStart, clipWindowEnd, windowStart)\n );\n }\n\n // Preheat audio (non-blocking mode for background decoding)\n preheatPromises.push(this.audioSession.ensureAudioForTime(windowStart, { immediate: false }));\n\n await Promise.all(preheatPromises);\n\n // Update window bounds after successful preheat\n this.windowEnd = windowEnd;\n } catch (error) {\n // Preheat failures are not critical\n console.warn('[PlaybackController] Preheat failed:', error);\n } finally {\n this.preheatInProgress = false;\n }\n }\n\n async renderCurrentFrame(timeUs: TimeUs): Promise<void> {\n if (!this.videoComposer) {\n console.error('[PlaybackController] VideoComposer not initialized');\n return;\n }\n\n try {\n // Get render state (layers) from orchestrator\n // Use immediate mode when playing to detect buffering\n const renderState = await this.orchestrator.getRenderState(timeUs, {\n immediate: this.state === 'playing',\n });\n\n if (!renderState) {\n // If renderState is null during playback, we hit a cache miss/buffer underrun\n if (this.state === 'playing') {\n await this.handlePlaybackBuffering(timeUs);\n }\n return;\n }\n\n // Compose directly to canvas\n await this.videoComposer.composeFrame({\n timeUs,\n layers: renderState.layers,\n transition: renderState.transition,\n });\n } catch (error) {\n console.error('Render error:', error);\n this.eventBus.emit(MeframeEvent.PlaybackError, error as Error);\n }\n }\n\n private async handlePlaybackBuffering(timeUs: TimeUs): Promise<void> {\n // Prevent re-entrance: if not playing, skip\n if (this.state !== 'playing') {\n return;\n }\n\n const seekId = this.currentSeekId;\n this.state = 'buffering';\n this.eventBus.emit(MeframeEvent.PlaybackBuffering);\n\n // Pause audio immediately to prevent desync\n this.audioSession.stopPlayback();\n\n try {\n // Update window center for buffering position\n this.orchestrator.cacheManager.setWindow(timeUs);\n\n // Force load frame (blocking)\n // This ensures the resource is downloaded and decoded\n await this.orchestrator.getFrame(timeUs, { immediate: false });\n\n // Also ensure audio is ready (blocking mode for buffering)\n await this.audioSession.ensureAudioForTime(timeUs, { immediate: false });\n\n // Check if seek happened during buffering or playback was stopped/paused\n if (seekId !== this.currentSeekId || this.state !== 'buffering') {\n return;\n }\n\n this.state = 'playing';\n this.startTimeUs = this.audioContext!.currentTime * 1_000_000 - timeUs / this.playbackRate;\n\n // Reset throttle timer after buffering to ensure immediate audio scheduling\n this.lastAudioScheduleTime = 0;\n\n // Resume audio synced with timeline\n await this.audioSession.startPlayback(timeUs, this.audioContext);\n\n this.eventBus.emit(MeframeEvent.PlaybackPlay);\n\n if (!this.rafId) {\n this.playbackLoop();\n }\n } catch (error) {\n // Ignore WaiterReplacedError (happens during fast seeks)\n if (error instanceof WaiterReplacedError) {\n return;\n }\n // Check if seek happened during error handling\n if (seekId !== this.currentSeekId) {\n return;\n }\n console.error('[PlaybackController] Buffering error:', error);\n this.state = 'paused';\n this.eventBus.emit(MeframeEvent.PlaybackError, error as Error);\n }\n }\n\n private clampTime(timeUs: TimeUs): TimeUs {\n return Math.max(0, Math.min(timeUs, this.duration));\n }\n\n // Cleanup\n dispose(): void {\n this.stop();\n this.eventBus.off(MeframeEvent.CacheCover, this.onCacheCover);\n this.eventBus.off(MeframeEvent.ModelSet, this.onModelSet);\n if (this.videoComposer) {\n this.videoComposer.dispose();\n this.videoComposer = null;\n }\n }\n\n private onCacheCover = (): void => {\n // Optimization for Quick Start / Fast First Frame:\n // When the first frame of a resource is decoded (e.g. via side-channel parsing),\n // we immediately render it if we are at the start of the timeline.\n // This significantly improves perceived loading speed for compatible formats (e.g. fragmented MP4 with moov at start).\n // For standard formats or when not at time 0, this might be redundant as normal playback loop handles it.\n if (this.state === 'idle' && this.currentTimeUs === 0) {\n this.renderCurrentFrame(0);\n }\n };\n\n private onModelSet = (): void => {\n if (!this.videoComposer || !this.orchestrator.compositionModel) return;\n\n const model = this.orchestrator.compositionModel;\n // Update VideoComposer configuration with new model dimensions and fps\n this.videoComposer.updateConfig({\n width: model.renderConfig?.width || 720,\n height: model.renderConfig?.height || 1280,\n fps: model.fps || 30,\n backgroundColor: model.renderConfig?.backgroundColor || '#000',\n });\n\n // Preheat audio for first frame (non-blocking)\n this.audioSession.ensureAudioForTime(this.currentTimeUs, { immediate: false });\n\n // Re-render current frame to reflect dimension changes immediately\n this.renderCurrentFrame(this.currentTimeUs);\n };\n\n private setupEventListeners(): void {\n this.eventBus.on(MeframeEvent.CacheCover, this.onCacheCover);\n this.eventBus.on(MeframeEvent.ModelSet, this.onModelSet);\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PlaybackController.js","sources":["../../src/controllers/PlaybackController.ts"],"sourcesContent":["import type {\n IPlaybackController,\n PlaybackOptions,\n IEventBus,\n PreviewHandle,\n TimeUs,\n} from './types';\nimport {\n PlaybackActionType,\n PlaybackCommandType,\n PlaybackState,\n type OpToken,\n type PlaybackAction,\n type PlaybackCommand,\n} from './types';\nimport { MeframeEvent } from '../event/events';\nimport type { GlobalAudioSession } from '../orchestrator/GlobalAudioSession';\nimport type { Orchestrator } from '../orchestrator';\nimport type { RequestMode } from '../orchestrator/types';\nimport { WaiterReplacedError } from '../utils/errors';\nimport { VideoComposer } from '../stages/compose/VideoComposer';\nimport { isVideoClip } from '../model/types';\n\nimport { PlaybackStateMachine } from './PlaybackStateMachine';\n\n/**\n * Playback controller for preview\n * Internal implementation - not exposed directly to external consumers\n */\nexport class PlaybackController implements IPlaybackController, PreviewHandle {\n private orchestrator: Orchestrator;\n private eventBus: IEventBus;\n private canvas: HTMLCanvasElement | OffscreenCanvas;\n private videoComposer: VideoComposer | null = null;\n\n // Playback time (external)\n currentTimeUs: TimeUs = 0;\n private playbackRate = 1.0;\n private volume = 1.0;\n private loop = false;\n\n // Time base\n private rafId: number | null = null;\n private startTimeUs: TimeUs = 0; // AudioContext timeline origin (microseconds)\n\n // Frame stats\n private frameCount = 0;\n private lastFrameTime = 0;\n private fps = 0;\n\n // Audio\n private audioContext: AudioContext;\n private audioSession: GlobalAudioSession;\n private lastAudioScheduleTime = 0;\n private readonly AUDIO_SCHEDULE_INTERVAL = 100_000; // 100ms\n\n // State machine\n private fsm = new PlaybackStateMachine();\n\n // Unified window management for both video and audio\n private windowEnd: TimeUs = 0;\n private readonly WINDOW_DURATION = 3_000_000; // 3s decode window\n private readonly AUDIO_READY_PROBE_WINDOW = 500_000; // 500ms: gate buffering only for near-future audio\n private readonly PREHEAT_DISTANCE = 1_000_000; // 1s preheat trigger distance\n private preheatInProgress = false;\n\n constructor(orchestrator: Orchestrator, eventBus: IEventBus, options: PlaybackOptions) {\n this.orchestrator = orchestrator;\n this.audioSession = orchestrator.audioSession;\n this.eventBus = eventBus;\n this.canvas = options.canvas;\n this.audioContext = new AudioContext();\n\n const model = orchestrator.compositionModel;\n const width = model?.renderConfig?.width || this.canvas.width || 720;\n const height = model?.renderConfig?.height || this.canvas.height || 1280;\n\n this.videoComposer = new VideoComposer({\n width,\n height,\n fps: model?.fps || 30,\n backgroundColor: model?.renderConfig?.backgroundColor || '#000',\n externalCanvas: this.canvas,\n });\n\n if (options.startUs !== undefined) {\n this.currentTimeUs = options.startUs;\n }\n if (options.rate !== undefined) {\n this.playbackRate = options.rate;\n }\n if (options.loop !== undefined) {\n this.loop = options.loop;\n }\n\n this.setupEventListeners();\n\n if (options.autoStart) {\n this.play();\n }\n }\n\n async renderCover(): Promise<void> {\n await this.renderCurrentFrame(0, { mode: 'blocking' });\n }\n\n // ========= Public API =========\n\n play(): void {\n this.dispatch({ type: PlaybackActionType.Play });\n }\n\n pause(): void {\n this.dispatch({ type: PlaybackActionType.Pause });\n }\n\n stop(): void {\n this.dispatch({ type: PlaybackActionType.Stop });\n }\n\n async seek(timeUs: TimeUs): Promise<void> {\n const { done } = this.dispatch({\n type: PlaybackActionType.Seek,\n timeUs,\n durationUs: this.duration,\n });\n await done;\n }\n\n setRate(rate: number): void {\n const currentTimeUs = this.currentTimeUs;\n this.playbackRate = rate;\n\n // Keep currentTimeUs stable; update the time base for AudioContext clock mapping.\n this.startTimeUs = this.audioContext.currentTime * 1_000_000 - currentTimeUs / rate;\n this.audioSession.setPlaybackRate(this.playbackRate);\n\n this.eventBus.emit(MeframeEvent.PlaybackRateChange, { rate });\n }\n\n setVolume(volume: number): void {\n this.volume = Math.max(0, Math.min(1, volume));\n this.audioSession.setVolume(this.volume);\n this.eventBus.emit(MeframeEvent.PlaybackVolumeChange, { volume: this.volume });\n }\n\n setMute(muted: boolean): void {\n if (muted) {\n this.audioSession.stopPlayback();\n return;\n }\n if (this.fsm.snapshot.state === PlaybackState.Playing) {\n void this.audioSession.startPlayback(this.currentTimeUs, this.audioContext);\n }\n }\n\n setLoop(loop: boolean): void {\n this.loop = loop;\n }\n\n get duration(): TimeUs {\n return this.orchestrator.compositionModel?.durationUs ?? 0;\n }\n\n get isPlaying(): boolean {\n return this.fsm.snapshot.state === PlaybackState.Playing;\n }\n\n resume(): void {\n this.play();\n }\n\n on(event: string, handler: (payload: any) => void): void {\n this.eventBus.on(event as MeframeEvent, handler);\n }\n\n off(event: string, handler: (payload: any) => void): void {\n this.eventBus.off(event as MeframeEvent, handler);\n }\n\n // ========= State machine wiring =========\n\n private dispatch(action: PlaybackAction): { token: OpToken; done: Promise<void> } {\n const { token, commands } = this.fsm.dispatch(action, { currentTimeUs: this.currentTimeUs });\n const done = this.executeCommands(commands, token);\n return { token, done };\n }\n\n private executeCommands(commands: PlaybackCommand[], token: OpToken): Promise<void> {\n const maybe = this.executeSeq(commands, token, 0);\n return maybe ?? Promise.resolve();\n }\n\n private executeSeq(\n commands: PlaybackCommand[],\n token: OpToken,\n startIndex: number\n ): Promise<void> | void {\n for (let i = startIndex; i < commands.length; i++) {\n if (!this.isCurrentToken(token)) return;\n const maybe = this.executeCommand(commands[i]!, token);\n if (maybe) {\n return maybe.then(() => {\n if (!this.isCurrentToken(token)) return;\n const cont = this.executeSeq(commands, token, i + 1);\n return cont ?? Promise.resolve();\n });\n }\n }\n }\n\n private executePar(commands: PlaybackCommand[], token: OpToken): Promise<void> | void {\n const promises: Promise<void>[] = [];\n for (const c of commands) {\n if (!this.isCurrentToken(token)) return;\n const maybe = this.executeCommand(c, token);\n if (maybe) promises.push(maybe);\n }\n if (promises.length === 0) return;\n return Promise.all(promises).then(() => undefined);\n }\n\n private executeCommand(command: PlaybackCommand, token: OpToken): Promise<void> | void {\n if (!this.isCurrentToken(token)) return;\n\n switch (command.type) {\n case PlaybackCommandType.Seq:\n return this.executeSeq(command.commands, token, 0);\n case PlaybackCommandType.Par:\n return this.executePar(command.commands, token);\n case PlaybackCommandType.Try: {\n const handleError = (error: unknown): Promise<void> | void => {\n if (!this.isCurrentToken(token)) return;\n if (command.ignoreWaiterReplacedError && error instanceof WaiterReplacedError) return;\n if (command.logPrefix) console.error(command.logPrefix, error);\n const onErrorDone = command.onError ? this.dispatch(command.onError).done : undefined;\n const normalizeError = (e: unknown): Error => {\n if (e instanceof Error) return e;\n return new Error(typeof e === 'string' ? e : JSON.stringify(e));\n };\n const emit = () => {\n if (command.emitPlaybackError) {\n const err = normalizeError(error);\n // PlaybackError: direct playback channel error for advanced consumers.\n this.eventBus.emit(MeframeEvent.PlaybackError, err);\n // Error: generic error channel expected by higher-level wrappers (e.g. @meframe/axii).\n this.eventBus.emit(MeframeEvent.Error, {\n source: 'playback',\n error: err,\n context: {\n command: command.logPrefix,\n onError: command.onError?.type,\n },\n recoverable: false,\n });\n }\n };\n if (onErrorDone) {\n return onErrorDone.then(() => {\n emit();\n });\n }\n emit();\n };\n\n try {\n const maybe = this.executeCommand(command.command, token);\n if (maybe) {\n return maybe.catch(handleError);\n }\n return;\n } catch (error) {\n return handleError(error) ?? Promise.resolve();\n }\n }\n case PlaybackCommandType.Dispatch:\n return this.dispatch(command.action).done;\n case PlaybackCommandType.SetTime: {\n this.currentTimeUs = command.timeUs;\n return;\n }\n case PlaybackCommandType.SetFrozenTime:\n case PlaybackCommandType.SetWantsPlay:\n case PlaybackCommandType.SetState: {\n // managed inside fsm\n return;\n }\n case PlaybackCommandType.CancelRaf: {\n this.cancelRaf();\n return;\n }\n case PlaybackCommandType.StopAudio: {\n this.audioSession.stopPlayback();\n return;\n }\n case PlaybackCommandType.ResetAudioPlaybackStates: {\n this.audioSession.resetPlaybackStates();\n return;\n }\n case PlaybackCommandType.ResetAudioSession: {\n this.audioSession.reset();\n return;\n }\n case PlaybackCommandType.ClearCanvas: {\n this.clearCanvas();\n return;\n }\n case PlaybackCommandType.SetLastAudioScheduleTime: {\n this.lastAudioScheduleTime = command.timeUs;\n return;\n }\n case PlaybackCommandType.SetStartTimeBase: {\n this.startTimeUs = command.startTimeUs;\n return;\n }\n case PlaybackCommandType.SyncTimeBaseToAudioClock: {\n this.startTimeUs =\n this.audioContext.currentTime * 1_000_000 - command.timeUs / this.playbackRate;\n return;\n }\n case PlaybackCommandType.InitWindow: {\n this.initWindow(command.timeUs);\n return;\n }\n case PlaybackCommandType.SetCacheWindow: {\n this.orchestrator.cacheManager.setWindow(command.timeUs);\n return;\n }\n case PlaybackCommandType.Emit: {\n if (command.payload === undefined) {\n this.eventBus.emit(command.event as any);\n } else {\n this.eventBus.emit(command.event as any, command.payload);\n }\n return;\n }\n case PlaybackCommandType.RenderFrame: {\n return this.renderCurrentFrame(command.timeUs, {\n mode: command.mode,\n relativeTimeUs: command.relativeTimeUs,\n });\n }\n case PlaybackCommandType.MaybeRenderKeyframePreview: {\n return this.orchestrator.tryRenderKeyframe(command.timeUs).then((keyframeTimeUs) => {\n if (!this.isCurrentToken(token)) return;\n if (keyframeTimeUs === null) return;\n return this.orchestrator\n .getRenderState(command.timeUs, {\n mode: 'probe',\n relativeTimeUs: keyframeTimeUs,\n })\n .then((keyframeRenderState) => {\n if (!this.isCurrentToken(token)) return;\n if (!keyframeRenderState) return;\n return this.compose(command.timeUs, keyframeRenderState);\n });\n });\n }\n case PlaybackCommandType.EnsureAudio: {\n return this.audioSession.ensureAudioForTime(command.timeUs, {\n mode: command.mode,\n });\n }\n case PlaybackCommandType.GetFrame: {\n return this.orchestrator\n .getFrame(command.timeUs, {\n mode: command.mode,\n preheat: command.preheat,\n })\n .then((frame) => {\n if (!frame && command.mode === 'blocking') {\n throw new Error(\n `[PlaybackController] GetFrame miss in blocking mode at ${command.timeUs}`\n );\n }\n });\n }\n case PlaybackCommandType.StartAudioPlayback: {\n return this.audioSession.startPlayback(command.timeUs, this.audioContext);\n }\n case PlaybackCommandType.ProbeStartReady: {\n const audioWindowEnd = Math.min(\n this.duration,\n command.timeUs + this.AUDIO_READY_PROBE_WINDOW\n );\n\n const audioReady = this.audioSession.isAudioResourceWindowReady(\n command.timeUs,\n audioWindowEnd\n );\n const videoReady = this.isVideoResourceReadyAtTime(command.timeUs);\n\n if (audioReady && videoReady) return;\n\n // Kick background preparation (best-effort).\n if (!audioReady) {\n void this.audioSession.ensureAudioForTime(command.timeUs, { mode: 'probe' });\n }\n if (!videoReady) {\n void this.orchestrator.getFrame(command.timeUs, { mode: 'probe' });\n }\n\n // Enter buffering and bump token to cancel the remaining start sequence.\n this.dispatch({\n type: PlaybackActionType.EnterBuffering,\n timeUs: command.timeUs,\n bumpToken: true,\n reason: 'startup',\n });\n return;\n }\n case PlaybackCommandType.StartRafLoop: {\n this.startPlaybackLoop(token);\n return;\n }\n }\n }\n\n private cancelRaf(): void {\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n }\n\n private isCurrentToken(token: OpToken): boolean {\n return token === this.fsm.snapshot.token;\n }\n\n private startPlaybackLoop(token: OpToken): void {\n this.rafId = requestAnimationFrame(() => {\n void this.onRafTick(token);\n });\n }\n\n private async onRafTick(token: OpToken): Promise<void> {\n if (!this.isCurrentToken(token) || this.fsm.snapshot.state !== PlaybackState.Playing) {\n return;\n }\n\n const candidateTimeUs =\n (this.audioContext.currentTime * 1_000_000 - this.startTimeUs) * this.playbackRate;\n this.dispatch({\n type: PlaybackActionType.ClockTick,\n candidateTimeUs,\n durationUs: this.duration,\n loop: this.loop,\n audioNowUs: this.audioContext.currentTime * 1_000_000,\n });\n\n if (!this.isCurrentToken(token) || this.fsm.snapshot.state !== PlaybackState.Playing) {\n return;\n }\n\n // Audio probe: if the audio window isn't ready, enter buffering (freeze timeline)\n // and kick background audio preparation.\n const audioWindowEnd = Math.min(\n this.duration,\n this.currentTimeUs + this.AUDIO_READY_PROBE_WINDOW\n );\n if (!this.audioSession.isAudioResourceWindowReady(this.currentTimeUs, audioWindowEnd)) {\n void this.audioSession.ensureAudioForTime(this.currentTimeUs, { mode: 'probe' });\n this.dispatch({ type: PlaybackActionType.EnterBuffering, timeUs: this.currentTimeUs });\n return;\n }\n\n // Throttle audio scheduling.\n if (this.currentTimeUs - this.lastAudioScheduleTime >= this.AUDIO_SCHEDULE_INTERVAL) {\n await this.audioSession.scheduleAudio(this.currentTimeUs, this.audioContext);\n if (!this.isCurrentToken(token) || this.fsm.snapshot.state !== PlaybackState.Playing) return;\n this.lastAudioScheduleTime = this.currentTimeUs;\n }\n\n const renderState = await this.orchestrator.getRenderState(this.currentTimeUs, {\n mode: 'probe',\n });\n if (!this.isCurrentToken(token) || this.fsm.snapshot.state !== PlaybackState.Playing) {\n return;\n }\n\n if (!renderState) {\n this.dispatch({ type: PlaybackActionType.EnterBuffering, timeUs: this.currentTimeUs });\n return;\n }\n\n await this.compose(this.currentTimeUs, renderState);\n if (!this.isCurrentToken(token) || this.fsm.snapshot.state !== PlaybackState.Playing) return;\n\n this.updateFps();\n this.frameCount++;\n\n // Unified cache window update.\n this.orchestrator.cacheManager.setWindow(this.currentTimeUs);\n\n this.checkAndPreheatWindow();\n if (!this.isCurrentToken(token) || this.fsm.snapshot.state !== PlaybackState.Playing) return;\n\n this.startPlaybackLoop(token);\n }\n\n private updateFps(): void {\n const now = performance.now();\n if (this.lastFrameTime > 0) {\n const deltaTime = now - this.lastFrameTime;\n const instantFps = 1000 / deltaTime;\n this.fps = this.fps > 0 ? this.fps * 0.9 + instantFps * 0.1 : instantFps;\n }\n this.lastFrameTime = now;\n }\n\n private initWindow(timeUs: TimeUs): void {\n this.windowEnd = timeUs + this.WINDOW_DURATION;\n this.preheatInProgress = false;\n this.orchestrator.cacheManager.setWindow(timeUs);\n }\n\n private checkAndPreheatWindow(): void {\n if (this.preheatInProgress || this.fsm.snapshot.state !== PlaybackState.Playing) {\n return;\n }\n\n const distanceToWindowEnd = this.windowEnd - this.currentTimeUs;\n if (distanceToWindowEnd < 0) {\n this.initWindow(this.currentTimeUs);\n return;\n }\n\n if (distanceToWindowEnd > 0 && distanceToWindowEnd <= this.PREHEAT_DISTANCE) {\n void this.preheatNextWindow();\n }\n }\n\n async preheatNextWindow(): Promise<void> {\n if (this.preheatInProgress) return;\n\n this.preheatInProgress = true;\n try {\n const windowStart = this.currentTimeUs;\n const windowEnd = windowStart + this.WINDOW_DURATION;\n\n const clipsInWindow =\n this.orchestrator.compositionModel?.getClipsInRange(windowStart, windowEnd) ?? [];\n const preheatPromises: Promise<any>[] = [];\n\n for (const clip of clipsInWindow) {\n if (!isVideoClip(clip)) continue;\n\n const clipWindowStart = Math.max(0, windowStart - clip.startUs);\n const clipWindowEnd = Math.min(clip.durationUs, windowEnd - clip.startUs);\n if (clipWindowStart >= clipWindowEnd) continue;\n\n preheatPromises.push(\n this.orchestrator.preheatClipWindow(clip.id, clipWindowStart, clipWindowEnd, windowStart)\n );\n }\n\n // Non-blocking audio preheat.\n preheatPromises.push(this.audioSession.ensureAudioForTime(windowStart, { mode: 'blocking' }));\n\n await Promise.all(preheatPromises);\n this.windowEnd = windowEnd;\n } catch (error) {\n console.warn('[PlaybackController] Preheat failed:', error);\n } finally {\n this.preheatInProgress = false;\n }\n }\n\n private async renderCurrentFrame(\n timeUs: TimeUs,\n options: { mode: RequestMode; relativeTimeUs?: TimeUs }\n ): Promise<void> {\n if (!this.videoComposer) {\n console.error('[PlaybackController] VideoComposer not initialized');\n return;\n }\n\n const renderState = await this.orchestrator.getRenderState(timeUs, {\n mode: options.mode,\n relativeTimeUs: options.relativeTimeUs,\n });\n\n if (!renderState) {\n if (options.mode === 'blocking') {\n throw new Error(`[PlaybackController] RenderFrame miss in blocking mode at ${timeUs}`);\n }\n return;\n }\n\n await this.compose(timeUs, renderState);\n }\n\n private async compose(\n timeUs: TimeUs,\n renderState: { layers: any[]; transition?: any }\n ): Promise<void> {\n if (!this.videoComposer) return;\n await this.videoComposer.composeFrame({\n timeUs,\n layers: renderState.layers,\n transition: renderState.transition,\n });\n }\n\n private clearCanvas(): void {\n const ctx = this.canvas.getContext('2d') as\n | CanvasRenderingContext2D\n | OffscreenCanvasRenderingContext2D\n | null;\n if (ctx && 'clearRect' in ctx) {\n ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);\n }\n }\n\n // ========= Cleanup / event handlers =========\n\n dispose(): void {\n this.stop();\n this.eventBus.off(MeframeEvent.CacheCover, this.onCacheCover);\n this.eventBus.off(MeframeEvent.ModelSet, this.onModelSet);\n if (this.videoComposer) {\n this.videoComposer.dispose();\n this.videoComposer = null;\n }\n }\n\n private onCacheCover = (): void => {\n if (this.fsm.snapshot.state === PlaybackState.Idle && this.currentTimeUs === 0) {\n void this.renderCurrentFrame(0, { mode: 'blocking' });\n }\n };\n\n private onModelSet = (): void => {\n if (!this.videoComposer || !this.orchestrator.compositionModel) return;\n\n const model = this.orchestrator.compositionModel;\n this.videoComposer.updateConfig({\n width: model.renderConfig?.width || 720,\n height: model.renderConfig?.height || 1280,\n fps: model.fps || 30,\n backgroundColor: model.renderConfig?.backgroundColor || '#000',\n });\n\n // Best-effort background preheat.\n void this.audioSession.ensureAudioForTime(this.currentTimeUs, { mode: 'blocking' });\n void this.renderCurrentFrame(this.currentTimeUs, { mode: 'blocking' });\n };\n\n private setupEventListeners(): void {\n this.eventBus.on(MeframeEvent.CacheCover, this.onCacheCover);\n this.eventBus.on(MeframeEvent.ModelSet, this.onModelSet);\n }\n\n private isVideoResourceReadyAtTime(timeUs: TimeUs): boolean {\n const model = this.orchestrator.compositionModel;\n if (!model) return true;\n const clip = model.getClipsAtTime(timeUs, model.mainTrackId)[0];\n if (!clip || !('resourceId' in clip) || typeof (clip as any).resourceId !== 'string')\n return true;\n const resourceId = (clip as any).resourceId as string;\n const resource = model.getResource(resourceId);\n return resource?.state === 'ready';\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { OpToken, PlaybackAction, PlaybackCommand, PlaybackMachineSnapshot, TimeUs } from './types';
|
|
2
|
+
|
|
3
|
+
export declare class PlaybackStateMachine {
|
|
4
|
+
private state;
|
|
5
|
+
private wantsPlay;
|
|
6
|
+
private frozenTimeUs;
|
|
7
|
+
private token;
|
|
8
|
+
get snapshot(): PlaybackMachineSnapshot;
|
|
9
|
+
dispatch(action: PlaybackAction, ctx: {
|
|
10
|
+
currentTimeUs: TimeUs;
|
|
11
|
+
}): {
|
|
12
|
+
token: OpToken;
|
|
13
|
+
commands: PlaybackCommand[];
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=PlaybackStateMachine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PlaybackStateMachine.d.ts","sourceRoot":"","sources":["../../src/controllers/PlaybackStateMachine.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,OAAO,EACZ,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAC5B,KAAK,MAAM,EACZ,MAAM,SAAS,CAAC;AAEjB,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,KAAK,CAAqC;IAClD,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,KAAK,CAAc;IAE3B,IAAI,QAAQ,IAAI,uBAAuB,CAOtC;IAED,QAAQ,CACN,MAAM,EAAE,cAAc,EACtB,GAAG,EAAE;QAAE,aAAa,EAAE,MAAM,CAAA;KAAE,GAC7B;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,eAAe,EAAE,CAAA;KAAE;CA6UnD"}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import { MeframeEvent } from "../event/events.js";
|
|
2
|
+
import { PlaybackState, PlaybackActionType, PlaybackCommandType } from "./types.js";
|
|
3
|
+
class PlaybackStateMachine {
|
|
4
|
+
state = PlaybackState.Idle;
|
|
5
|
+
wantsPlay = false;
|
|
6
|
+
frozenTimeUs = null;
|
|
7
|
+
token = 0;
|
|
8
|
+
get snapshot() {
|
|
9
|
+
return {
|
|
10
|
+
state: this.state,
|
|
11
|
+
wantsPlay: this.wantsPlay,
|
|
12
|
+
frozenTimeUs: this.frozenTimeUs,
|
|
13
|
+
token: this.token
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
dispatch(action, ctx) {
|
|
17
|
+
const commands = [];
|
|
18
|
+
const bumpToken = () => {
|
|
19
|
+
this.token++;
|
|
20
|
+
return this.token;
|
|
21
|
+
};
|
|
22
|
+
const setState = (state) => {
|
|
23
|
+
this.state = state;
|
|
24
|
+
commands.push({ type: PlaybackCommandType.SetState, state });
|
|
25
|
+
};
|
|
26
|
+
const setWantsPlay = (wantsPlay) => {
|
|
27
|
+
this.wantsPlay = wantsPlay;
|
|
28
|
+
commands.push({ type: PlaybackCommandType.SetWantsPlay, wantsPlay });
|
|
29
|
+
};
|
|
30
|
+
const setFrozenTime = (timeUs) => {
|
|
31
|
+
this.frozenTimeUs = timeUs;
|
|
32
|
+
commands.push({ type: PlaybackCommandType.SetFrozenTime, timeUs });
|
|
33
|
+
};
|
|
34
|
+
const setTime = (timeUs) => {
|
|
35
|
+
commands.push({ type: PlaybackCommandType.SetTime, timeUs });
|
|
36
|
+
};
|
|
37
|
+
const clampTime = (timeUs, durationUs) => {
|
|
38
|
+
return Math.max(0, Math.min(timeUs, durationUs));
|
|
39
|
+
};
|
|
40
|
+
switch (action.type) {
|
|
41
|
+
case PlaybackActionType.Play: {
|
|
42
|
+
const token = bumpToken();
|
|
43
|
+
setWantsPlay(true);
|
|
44
|
+
if (this.state === PlaybackState.Ended) {
|
|
45
|
+
setTime(0);
|
|
46
|
+
}
|
|
47
|
+
if (this.state === PlaybackState.Playing) {
|
|
48
|
+
return { token, commands };
|
|
49
|
+
}
|
|
50
|
+
setState(PlaybackState.Playing);
|
|
51
|
+
setFrozenTime(null);
|
|
52
|
+
commands.push({ type: PlaybackCommandType.SetLastAudioScheduleTime, timeUs: 0 });
|
|
53
|
+
commands.push({ type: PlaybackCommandType.CancelRaf });
|
|
54
|
+
const fallbackState = this.state === PlaybackState.Idle || this.state === PlaybackState.Ended ? PlaybackState.Idle : PlaybackState.Paused;
|
|
55
|
+
commands.push({
|
|
56
|
+
type: PlaybackCommandType.Try,
|
|
57
|
+
logPrefix: "[PlaybackController] Failed to start playback:",
|
|
58
|
+
emitPlaybackError: true,
|
|
59
|
+
onError: { type: PlaybackActionType.StartFailed, fallbackState },
|
|
60
|
+
command: {
|
|
61
|
+
type: PlaybackCommandType.Seq,
|
|
62
|
+
commands: [
|
|
63
|
+
{ type: PlaybackCommandType.ProbeStartReady, timeUs: ctx.currentTimeUs },
|
|
64
|
+
{
|
|
65
|
+
type: PlaybackCommandType.RenderFrame,
|
|
66
|
+
timeUs: ctx.currentTimeUs,
|
|
67
|
+
mode: "blocking"
|
|
68
|
+
},
|
|
69
|
+
{ type: PlaybackCommandType.InitWindow, timeUs: ctx.currentTimeUs },
|
|
70
|
+
{ type: PlaybackCommandType.StartAudioPlayback, timeUs: ctx.currentTimeUs },
|
|
71
|
+
{ type: PlaybackCommandType.SyncTimeBaseToAudioClock, timeUs: ctx.currentTimeUs },
|
|
72
|
+
{ type: PlaybackCommandType.StartRafLoop },
|
|
73
|
+
{ type: PlaybackCommandType.Emit, event: MeframeEvent.PlaybackPlay }
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
return { token, commands };
|
|
78
|
+
}
|
|
79
|
+
case PlaybackActionType.Pause: {
|
|
80
|
+
bumpToken();
|
|
81
|
+
const prev = this.state;
|
|
82
|
+
setWantsPlay(false);
|
|
83
|
+
setFrozenTime(null);
|
|
84
|
+
commands.push({ type: PlaybackCommandType.CancelRaf });
|
|
85
|
+
commands.push({ type: PlaybackCommandType.StopAudio });
|
|
86
|
+
if (prev !== PlaybackState.Idle && prev !== PlaybackState.Ended) {
|
|
87
|
+
setState(PlaybackState.Paused);
|
|
88
|
+
}
|
|
89
|
+
if (prev === PlaybackState.Playing || prev === PlaybackState.Buffering || prev === PlaybackState.Seeking) {
|
|
90
|
+
commands.push({ type: PlaybackCommandType.Emit, event: MeframeEvent.PlaybackPause });
|
|
91
|
+
}
|
|
92
|
+
return { token: this.token, commands };
|
|
93
|
+
}
|
|
94
|
+
case PlaybackActionType.Stop: {
|
|
95
|
+
bumpToken();
|
|
96
|
+
setWantsPlay(false);
|
|
97
|
+
setFrozenTime(null);
|
|
98
|
+
setState(PlaybackState.Idle);
|
|
99
|
+
setTime(0);
|
|
100
|
+
commands.push({ type: PlaybackCommandType.CancelRaf });
|
|
101
|
+
commands.push({ type: PlaybackCommandType.StopAudio });
|
|
102
|
+
commands.push({ type: PlaybackCommandType.ClearCanvas });
|
|
103
|
+
commands.push({ type: PlaybackCommandType.ResetAudioSession });
|
|
104
|
+
commands.push({ type: PlaybackCommandType.ResetAudioPlaybackStates });
|
|
105
|
+
commands.push({ type: PlaybackCommandType.SetLastAudioScheduleTime, timeUs: 0 });
|
|
106
|
+
commands.push({ type: PlaybackCommandType.Emit, event: MeframeEvent.PlaybackStop });
|
|
107
|
+
return { token: this.token, commands };
|
|
108
|
+
}
|
|
109
|
+
case PlaybackActionType.Seek: {
|
|
110
|
+
const token = bumpToken();
|
|
111
|
+
const previousState = this.state;
|
|
112
|
+
const toUs = clampTime(action.timeUs, action.durationUs);
|
|
113
|
+
setTime(toUs);
|
|
114
|
+
setFrozenTime(toUs);
|
|
115
|
+
setState(PlaybackState.Seeking);
|
|
116
|
+
commands.push({ type: PlaybackCommandType.CancelRaf });
|
|
117
|
+
commands.push({ type: PlaybackCommandType.StopAudio });
|
|
118
|
+
commands.push({ type: PlaybackCommandType.SetLastAudioScheduleTime, timeUs: 0 });
|
|
119
|
+
commands.push({
|
|
120
|
+
type: PlaybackCommandType.Try,
|
|
121
|
+
logPrefix: "[PlaybackController] Seek error:",
|
|
122
|
+
emitPlaybackError: true,
|
|
123
|
+
onError: { type: PlaybackActionType.Pause },
|
|
124
|
+
command: {
|
|
125
|
+
type: PlaybackCommandType.Seq,
|
|
126
|
+
commands: [
|
|
127
|
+
{ type: PlaybackCommandType.MaybeRenderKeyframePreview, timeUs: toUs },
|
|
128
|
+
{
|
|
129
|
+
type: PlaybackCommandType.Par,
|
|
130
|
+
commands: [
|
|
131
|
+
{ type: PlaybackCommandType.EnsureAudio, timeUs: toUs, mode: "blocking" },
|
|
132
|
+
{
|
|
133
|
+
type: PlaybackCommandType.GetFrame,
|
|
134
|
+
timeUs: toUs,
|
|
135
|
+
mode: "blocking",
|
|
136
|
+
preheat: true
|
|
137
|
+
}
|
|
138
|
+
]
|
|
139
|
+
},
|
|
140
|
+
{ type: PlaybackCommandType.InitWindow, timeUs: toUs },
|
|
141
|
+
{ type: PlaybackCommandType.RenderFrame, timeUs: toUs, mode: "blocking" },
|
|
142
|
+
{
|
|
143
|
+
type: PlaybackCommandType.Dispatch,
|
|
144
|
+
action: { type: PlaybackActionType.SeekResolved, previousState }
|
|
145
|
+
}
|
|
146
|
+
]
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
return { token, commands };
|
|
150
|
+
}
|
|
151
|
+
case PlaybackActionType.EnterBuffering: {
|
|
152
|
+
const shouldBump = action.bumpToken ?? false;
|
|
153
|
+
if (shouldBump) {
|
|
154
|
+
bumpToken();
|
|
155
|
+
}
|
|
156
|
+
if (this.state !== PlaybackState.Playing) {
|
|
157
|
+
return { token: this.token, commands };
|
|
158
|
+
}
|
|
159
|
+
setFrozenTime(action.timeUs);
|
|
160
|
+
setState(PlaybackState.Buffering);
|
|
161
|
+
commands.push({ type: PlaybackCommandType.Emit, event: MeframeEvent.PlaybackBuffering });
|
|
162
|
+
commands.push({ type: PlaybackCommandType.CancelRaf });
|
|
163
|
+
commands.push({ type: PlaybackCommandType.StopAudio });
|
|
164
|
+
commands.push({
|
|
165
|
+
type: PlaybackCommandType.Try,
|
|
166
|
+
logPrefix: "[PlaybackController] Buffering error:",
|
|
167
|
+
emitPlaybackError: true,
|
|
168
|
+
ignoreWaiterReplacedError: true,
|
|
169
|
+
onError: { type: PlaybackActionType.Pause },
|
|
170
|
+
command: {
|
|
171
|
+
type: PlaybackCommandType.Seq,
|
|
172
|
+
commands: [
|
|
173
|
+
{ type: PlaybackCommandType.SetCacheWindow, timeUs: action.timeUs },
|
|
174
|
+
{
|
|
175
|
+
type: PlaybackCommandType.Par,
|
|
176
|
+
commands: [
|
|
177
|
+
{ type: PlaybackCommandType.GetFrame, timeUs: action.timeUs, mode: "blocking" },
|
|
178
|
+
{
|
|
179
|
+
type: PlaybackCommandType.EnsureAudio,
|
|
180
|
+
timeUs: action.timeUs,
|
|
181
|
+
mode: "blocking"
|
|
182
|
+
}
|
|
183
|
+
]
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
type: PlaybackCommandType.Dispatch,
|
|
187
|
+
action: { type: PlaybackActionType.BufferingResolved, timeUs: action.timeUs }
|
|
188
|
+
}
|
|
189
|
+
]
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
return { token: this.token, commands };
|
|
193
|
+
}
|
|
194
|
+
case PlaybackActionType.BufferingResolved: {
|
|
195
|
+
if (this.state !== PlaybackState.Buffering) {
|
|
196
|
+
return { token: this.token, commands };
|
|
197
|
+
}
|
|
198
|
+
setFrozenTime(null);
|
|
199
|
+
if (this.wantsPlay) {
|
|
200
|
+
setState(PlaybackState.Playing);
|
|
201
|
+
commands.push({
|
|
202
|
+
type: PlaybackCommandType.Try,
|
|
203
|
+
logPrefix: "[PlaybackController] Failed to start playback:",
|
|
204
|
+
emitPlaybackError: true,
|
|
205
|
+
onError: { type: PlaybackActionType.StartFailed, fallbackState: PlaybackState.Paused },
|
|
206
|
+
command: {
|
|
207
|
+
type: PlaybackCommandType.Seq,
|
|
208
|
+
commands: [
|
|
209
|
+
{ type: PlaybackCommandType.RenderFrame, timeUs: action.timeUs, mode: "blocking" },
|
|
210
|
+
{ type: PlaybackCommandType.InitWindow, timeUs: action.timeUs },
|
|
211
|
+
{ type: PlaybackCommandType.StartAudioPlayback, timeUs: action.timeUs },
|
|
212
|
+
{ type: PlaybackCommandType.SyncTimeBaseToAudioClock, timeUs: action.timeUs },
|
|
213
|
+
{ type: PlaybackCommandType.StartRafLoop },
|
|
214
|
+
{ type: PlaybackCommandType.Emit, event: MeframeEvent.PlaybackPlay }
|
|
215
|
+
]
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
} else {
|
|
219
|
+
setState(PlaybackState.Paused);
|
|
220
|
+
}
|
|
221
|
+
return { token: this.token, commands };
|
|
222
|
+
}
|
|
223
|
+
case PlaybackActionType.SeekResolved: {
|
|
224
|
+
if (this.state !== PlaybackState.Seeking) {
|
|
225
|
+
return { token: this.token, commands };
|
|
226
|
+
}
|
|
227
|
+
setFrozenTime(null);
|
|
228
|
+
commands.push({
|
|
229
|
+
type: PlaybackCommandType.Emit,
|
|
230
|
+
event: MeframeEvent.PlaybackSeek,
|
|
231
|
+
payload: { timeUs: ctx.currentTimeUs }
|
|
232
|
+
});
|
|
233
|
+
if (this.wantsPlay) {
|
|
234
|
+
setState(PlaybackState.Playing);
|
|
235
|
+
commands.push({
|
|
236
|
+
type: PlaybackCommandType.Try,
|
|
237
|
+
logPrefix: "[PlaybackController] Failed to start playback:",
|
|
238
|
+
emitPlaybackError: true,
|
|
239
|
+
onError: { type: PlaybackActionType.StartFailed, fallbackState: PlaybackState.Paused },
|
|
240
|
+
command: {
|
|
241
|
+
type: PlaybackCommandType.Seq,
|
|
242
|
+
commands: [
|
|
243
|
+
{
|
|
244
|
+
type: PlaybackCommandType.RenderFrame,
|
|
245
|
+
timeUs: ctx.currentTimeUs,
|
|
246
|
+
mode: "blocking"
|
|
247
|
+
},
|
|
248
|
+
{ type: PlaybackCommandType.InitWindow, timeUs: ctx.currentTimeUs },
|
|
249
|
+
{ type: PlaybackCommandType.StartAudioPlayback, timeUs: ctx.currentTimeUs },
|
|
250
|
+
{ type: PlaybackCommandType.SyncTimeBaseToAudioClock, timeUs: ctx.currentTimeUs },
|
|
251
|
+
{ type: PlaybackCommandType.StartRafLoop },
|
|
252
|
+
{ type: PlaybackCommandType.Emit, event: MeframeEvent.PlaybackPlay }
|
|
253
|
+
]
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
} else {
|
|
257
|
+
setState(
|
|
258
|
+
action.previousState === PlaybackState.Idle ? PlaybackState.Idle : PlaybackState.Paused
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
return { token: this.token, commands };
|
|
262
|
+
}
|
|
263
|
+
case PlaybackActionType.StartFailed: {
|
|
264
|
+
setState(action.fallbackState);
|
|
265
|
+
return { token: this.token, commands };
|
|
266
|
+
}
|
|
267
|
+
case PlaybackActionType.ClockTick: {
|
|
268
|
+
if (this.state !== PlaybackState.Playing) {
|
|
269
|
+
return { token: this.token, commands };
|
|
270
|
+
}
|
|
271
|
+
if (this.frozenTimeUs !== null) {
|
|
272
|
+
return { token: this.token, commands };
|
|
273
|
+
}
|
|
274
|
+
const t = action.candidateTimeUs;
|
|
275
|
+
if (t >= action.durationUs) {
|
|
276
|
+
if (action.loop) {
|
|
277
|
+
setTime(0);
|
|
278
|
+
commands.push({
|
|
279
|
+
type: PlaybackCommandType.SetStartTimeBase,
|
|
280
|
+
startTimeUs: action.audioNowUs
|
|
281
|
+
});
|
|
282
|
+
commands.push({ type: PlaybackCommandType.ResetAudioPlaybackStates });
|
|
283
|
+
commands.push({ type: PlaybackCommandType.SetLastAudioScheduleTime, timeUs: 0 });
|
|
284
|
+
commands.push({ type: PlaybackCommandType.InitWindow, timeUs: 0 });
|
|
285
|
+
} else {
|
|
286
|
+
setWantsPlay(false);
|
|
287
|
+
setState(PlaybackState.Ended);
|
|
288
|
+
commands.push({ type: PlaybackCommandType.CancelRaf });
|
|
289
|
+
commands.push({ type: PlaybackCommandType.StopAudio });
|
|
290
|
+
setTime(0);
|
|
291
|
+
commands.push({
|
|
292
|
+
type: PlaybackCommandType.Emit,
|
|
293
|
+
event: MeframeEvent.PlaybackEnded,
|
|
294
|
+
payload: { timeUs: action.durationUs }
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
return { token: this.token, commands };
|
|
298
|
+
}
|
|
299
|
+
setTime(t);
|
|
300
|
+
commands.push({
|
|
301
|
+
type: PlaybackCommandType.Emit,
|
|
302
|
+
event: MeframeEvent.PlaybackTimeUpdate,
|
|
303
|
+
payload: { timeUs: t }
|
|
304
|
+
});
|
|
305
|
+
return { token: this.token, commands };
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
export {
|
|
311
|
+
PlaybackStateMachine
|
|
312
|
+
};
|
|
313
|
+
//# sourceMappingURL=PlaybackStateMachine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PlaybackStateMachine.js","sources":["../../src/controllers/PlaybackStateMachine.ts"],"sourcesContent":["import { MeframeEvent } from '../event/events';\nimport {\n PlaybackActionType,\n PlaybackCommandType,\n PlaybackState,\n type OpToken,\n type PlaybackAction,\n type PlaybackCommand,\n type PlaybackMachineSnapshot,\n type TimeUs,\n} from './types';\n\nexport class PlaybackStateMachine {\n private state: PlaybackState = PlaybackState.Idle;\n private wantsPlay = false;\n private frozenTimeUs: TimeUs | null = null;\n private token: OpToken = 0;\n\n get snapshot(): PlaybackMachineSnapshot {\n return {\n state: this.state,\n wantsPlay: this.wantsPlay,\n frozenTimeUs: this.frozenTimeUs,\n token: this.token,\n };\n }\n\n dispatch(\n action: PlaybackAction,\n ctx: { currentTimeUs: TimeUs }\n ): { token: OpToken; commands: PlaybackCommand[] } {\n const commands: PlaybackCommand[] = [];\n\n const bumpToken = () => {\n this.token++;\n return this.token;\n };\n\n const setState = (state: PlaybackState) => {\n this.state = state;\n commands.push({ type: PlaybackCommandType.SetState, state });\n };\n\n const setWantsPlay = (wantsPlay: boolean) => {\n this.wantsPlay = wantsPlay;\n commands.push({ type: PlaybackCommandType.SetWantsPlay, wantsPlay });\n };\n\n const setFrozenTime = (timeUs: TimeUs | null) => {\n this.frozenTimeUs = timeUs;\n commands.push({ type: PlaybackCommandType.SetFrozenTime, timeUs });\n };\n\n const setTime = (timeUs: TimeUs) => {\n commands.push({ type: PlaybackCommandType.SetTime, timeUs });\n };\n\n const clampTime = (timeUs: TimeUs, durationUs: TimeUs) => {\n return Math.max(0, Math.min(timeUs, durationUs));\n };\n\n switch (action.type) {\n case PlaybackActionType.Play: {\n const token = bumpToken();\n setWantsPlay(true);\n\n if (this.state === PlaybackState.Ended) {\n setTime(0);\n }\n\n if (this.state === PlaybackState.Playing) {\n return { token, commands };\n }\n\n // Set playing immediately; failures will rollback via START_FAILED.\n setState(PlaybackState.Playing);\n setFrozenTime(null);\n commands.push({ type: PlaybackCommandType.SetLastAudioScheduleTime, timeUs: 0 });\n commands.push({ type: PlaybackCommandType.CancelRaf });\n\n const fallbackState =\n this.state === PlaybackState.Idle || this.state === PlaybackState.Ended\n ? PlaybackState.Idle\n : PlaybackState.Paused;\n\n commands.push({\n type: PlaybackCommandType.Try,\n logPrefix: '[PlaybackController] Failed to start playback:',\n emitPlaybackError: true,\n onError: { type: PlaybackActionType.StartFailed, fallbackState },\n command: {\n type: PlaybackCommandType.Seq,\n commands: [\n { type: PlaybackCommandType.ProbeStartReady, timeUs: ctx.currentTimeUs },\n {\n type: PlaybackCommandType.RenderFrame,\n timeUs: ctx.currentTimeUs,\n mode: 'blocking',\n },\n { type: PlaybackCommandType.InitWindow, timeUs: ctx.currentTimeUs },\n { type: PlaybackCommandType.StartAudioPlayback, timeUs: ctx.currentTimeUs },\n { type: PlaybackCommandType.SyncTimeBaseToAudioClock, timeUs: ctx.currentTimeUs },\n { type: PlaybackCommandType.StartRafLoop },\n { type: PlaybackCommandType.Emit, event: MeframeEvent.PlaybackPlay },\n ],\n },\n });\n return { token, commands };\n }\n\n case PlaybackActionType.Pause: {\n bumpToken();\n const prev = this.state;\n setWantsPlay(false);\n setFrozenTime(null);\n commands.push({ type: PlaybackCommandType.CancelRaf });\n commands.push({ type: PlaybackCommandType.StopAudio });\n\n if (prev !== PlaybackState.Idle && prev !== PlaybackState.Ended) {\n setState(PlaybackState.Paused);\n }\n\n if (\n prev === PlaybackState.Playing ||\n prev === PlaybackState.Buffering ||\n prev === PlaybackState.Seeking\n ) {\n commands.push({ type: PlaybackCommandType.Emit, event: MeframeEvent.PlaybackPause });\n }\n\n return { token: this.token, commands };\n }\n\n case PlaybackActionType.Stop: {\n bumpToken();\n setWantsPlay(false);\n setFrozenTime(null);\n setState(PlaybackState.Idle);\n setTime(0);\n\n commands.push({ type: PlaybackCommandType.CancelRaf });\n commands.push({ type: PlaybackCommandType.StopAudio });\n commands.push({ type: PlaybackCommandType.ClearCanvas });\n commands.push({ type: PlaybackCommandType.ResetAudioSession });\n commands.push({ type: PlaybackCommandType.ResetAudioPlaybackStates });\n commands.push({ type: PlaybackCommandType.SetLastAudioScheduleTime, timeUs: 0 });\n commands.push({ type: PlaybackCommandType.Emit, event: MeframeEvent.PlaybackStop });\n\n return { token: this.token, commands };\n }\n\n case PlaybackActionType.Seek: {\n const token = bumpToken();\n const previousState = this.state;\n const toUs = clampTime(action.timeUs, action.durationUs);\n\n setTime(toUs);\n setFrozenTime(toUs);\n setState(PlaybackState.Seeking);\n commands.push({ type: PlaybackCommandType.CancelRaf });\n commands.push({ type: PlaybackCommandType.StopAudio });\n commands.push({ type: PlaybackCommandType.SetLastAudioScheduleTime, timeUs: 0 });\n commands.push({\n type: PlaybackCommandType.Try,\n logPrefix: '[PlaybackController] Seek error:',\n emitPlaybackError: true,\n onError: { type: PlaybackActionType.Pause },\n command: {\n type: PlaybackCommandType.Seq,\n commands: [\n { type: PlaybackCommandType.MaybeRenderKeyframePreview, timeUs: toUs },\n {\n type: PlaybackCommandType.Par,\n commands: [\n { type: PlaybackCommandType.EnsureAudio, timeUs: toUs, mode: 'blocking' },\n {\n type: PlaybackCommandType.GetFrame,\n timeUs: toUs,\n mode: 'blocking',\n preheat: true,\n },\n ],\n },\n { type: PlaybackCommandType.InitWindow, timeUs: toUs },\n { type: PlaybackCommandType.RenderFrame, timeUs: toUs, mode: 'blocking' },\n {\n type: PlaybackCommandType.Dispatch,\n action: { type: PlaybackActionType.SeekResolved, previousState },\n },\n ],\n },\n });\n\n return { token, commands };\n }\n\n case PlaybackActionType.EnterBuffering: {\n const shouldBump = action.bumpToken ?? false;\n if (shouldBump) {\n bumpToken();\n }\n if (this.state !== PlaybackState.Playing) {\n return { token: this.token, commands };\n }\n // Do NOT bump token; buffering is part of the current playback operation.\n setFrozenTime(action.timeUs);\n setState(PlaybackState.Buffering);\n commands.push({ type: PlaybackCommandType.Emit, event: MeframeEvent.PlaybackBuffering });\n commands.push({ type: PlaybackCommandType.CancelRaf });\n commands.push({ type: PlaybackCommandType.StopAudio });\n commands.push({\n type: PlaybackCommandType.Try,\n logPrefix: '[PlaybackController] Buffering error:',\n emitPlaybackError: true,\n ignoreWaiterReplacedError: true,\n onError: { type: PlaybackActionType.Pause },\n command: {\n type: PlaybackCommandType.Seq,\n commands: [\n { type: PlaybackCommandType.SetCacheWindow, timeUs: action.timeUs },\n {\n type: PlaybackCommandType.Par,\n commands: [\n { type: PlaybackCommandType.GetFrame, timeUs: action.timeUs, mode: 'blocking' },\n {\n type: PlaybackCommandType.EnsureAudio,\n timeUs: action.timeUs,\n mode: 'blocking',\n },\n ],\n },\n {\n type: PlaybackCommandType.Dispatch,\n action: { type: PlaybackActionType.BufferingResolved, timeUs: action.timeUs },\n },\n ],\n },\n });\n return { token: this.token, commands };\n }\n\n case PlaybackActionType.BufferingResolved: {\n if (this.state !== PlaybackState.Buffering) {\n return { token: this.token, commands };\n }\n setFrozenTime(null);\n if (this.wantsPlay) {\n setState(PlaybackState.Playing);\n commands.push({\n type: PlaybackCommandType.Try,\n logPrefix: '[PlaybackController] Failed to start playback:',\n emitPlaybackError: true,\n onError: { type: PlaybackActionType.StartFailed, fallbackState: PlaybackState.Paused },\n command: {\n type: PlaybackCommandType.Seq,\n commands: [\n { type: PlaybackCommandType.RenderFrame, timeUs: action.timeUs, mode: 'blocking' },\n { type: PlaybackCommandType.InitWindow, timeUs: action.timeUs },\n { type: PlaybackCommandType.StartAudioPlayback, timeUs: action.timeUs },\n { type: PlaybackCommandType.SyncTimeBaseToAudioClock, timeUs: action.timeUs },\n { type: PlaybackCommandType.StartRafLoop },\n { type: PlaybackCommandType.Emit, event: MeframeEvent.PlaybackPlay },\n ],\n },\n });\n } else {\n setState(PlaybackState.Paused);\n }\n return { token: this.token, commands };\n }\n\n case PlaybackActionType.SeekResolved: {\n if (this.state !== PlaybackState.Seeking) {\n return { token: this.token, commands };\n }\n setFrozenTime(null);\n commands.push({\n type: PlaybackCommandType.Emit,\n event: MeframeEvent.PlaybackSeek,\n payload: { timeUs: ctx.currentTimeUs },\n });\n if (this.wantsPlay) {\n setState(PlaybackState.Playing);\n commands.push({\n type: PlaybackCommandType.Try,\n logPrefix: '[PlaybackController] Failed to start playback:',\n emitPlaybackError: true,\n onError: { type: PlaybackActionType.StartFailed, fallbackState: PlaybackState.Paused },\n command: {\n type: PlaybackCommandType.Seq,\n commands: [\n {\n type: PlaybackCommandType.RenderFrame,\n timeUs: ctx.currentTimeUs,\n mode: 'blocking',\n },\n { type: PlaybackCommandType.InitWindow, timeUs: ctx.currentTimeUs },\n { type: PlaybackCommandType.StartAudioPlayback, timeUs: ctx.currentTimeUs },\n { type: PlaybackCommandType.SyncTimeBaseToAudioClock, timeUs: ctx.currentTimeUs },\n { type: PlaybackCommandType.StartRafLoop },\n { type: PlaybackCommandType.Emit, event: MeframeEvent.PlaybackPlay },\n ],\n },\n });\n } else {\n setState(\n action.previousState === PlaybackState.Idle ? PlaybackState.Idle : PlaybackState.Paused\n );\n }\n return { token: this.token, commands };\n }\n\n case PlaybackActionType.StartFailed: {\n // Only meaningful if we were trying to play.\n setState(action.fallbackState);\n return { token: this.token, commands };\n }\n\n case PlaybackActionType.ClockTick: {\n if (this.state !== PlaybackState.Playing) {\n return { token: this.token, commands };\n }\n if (this.frozenTimeUs !== null) {\n return { token: this.token, commands };\n }\n\n const t = action.candidateTimeUs;\n\n if (t >= action.durationUs) {\n if (action.loop) {\n setTime(0);\n commands.push({\n type: PlaybackCommandType.SetStartTimeBase,\n startTimeUs: action.audioNowUs,\n });\n commands.push({ type: PlaybackCommandType.ResetAudioPlaybackStates });\n commands.push({ type: PlaybackCommandType.SetLastAudioScheduleTime, timeUs: 0 });\n commands.push({ type: PlaybackCommandType.InitWindow, timeUs: 0 });\n } else {\n setWantsPlay(false);\n setState(PlaybackState.Ended);\n commands.push({ type: PlaybackCommandType.CancelRaf });\n commands.push({ type: PlaybackCommandType.StopAudio });\n setTime(0);\n commands.push({\n type: PlaybackCommandType.Emit,\n event: MeframeEvent.PlaybackEnded,\n payload: { timeUs: action.durationUs },\n });\n }\n return { token: this.token, commands };\n }\n\n setTime(t);\n commands.push({\n type: PlaybackCommandType.Emit,\n event: MeframeEvent.PlaybackTimeUpdate,\n payload: { timeUs: t },\n });\n return { token: this.token, commands };\n }\n }\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -3,5 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export { PlaybackController } from './PlaybackController';
|
|
5
5
|
export { ExportController } from './ExportController';
|
|
6
|
-
export
|
|
6
|
+
export { PlaybackState } from './types';
|
|
7
|
+
export type { IPlaybackController, PlaybackEvent, PlaybackOptions, PreviewHandle, TimeRange, PrioritizedClip, IEventBus, FrameStats, } from './types';
|
|
7
8
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/controllers/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,YAAY,EAEV,mBAAmB,EACnB,aAAa,EACb,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/controllers/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAExC,YAAY,EAEV,mBAAmB,EACnB,aAAa,EACb,eAAe,EACf,aAAa,EAGb,SAAS,EACT,eAAe,EAGf,SAAS,EAGT,UAAU,GACX,MAAM,SAAS,CAAC"}
|