@ai-matrx/media 0.6.67 → 0.7.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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/live-audio/pcm-player.ts","../src/live-audio/mp3-player.ts","../src/live-audio/controller.ts","../src/live-audio/registry.ts","../src/live-audio/media-frames.ts"],"sourcesContent":["// @ai-matrx/media/live-audio — pcm-player.ts\n//\n// Moved verbatim from matrx-frontend features/audio/streamingPcmPlayer.ts\n// (2026-10-01) so every client plays live TTS identically.\n//\n// Generic progressive PCM stream player — the client half of the server's\n// `audio_stream_chunk` / `audio_stream_end` events (matrx_connect\n// AudioStreamChunkData: base64 signed 16-bit little-endian PCM segments).\n//\n// Unlike the conversational playback in features/voice-agent (turn-based,\n// interrupt-driven, fire-and-forget), this player BUFFERS every chunk so a\n// long render (a podcast episode arriving over minutes) supports the full\n// listen-while-it-renders UX: play/pause, seek within what has buffered,\n// position tracking, and graceful underrun (playback catches up with the\n// buffer, then resumes as the next chunk lands).\n//\n// Memory: chunks are kept as Int16 (half the size of Float32) and converted\n// per-chunk at schedule time. 24 kHz mono ≈ 2.9 MB/min — fine for the\n// transient live-preview window; callers destroy() once the canonical file\n// URL takes over.\n//\n// AudioContext is created lazily inside play() so the browser's user-gesture\n// requirement is satisfied by the user's own click.\n\nexport interface StreamingPcmFormat {\n sampleRate: number;\n /** Interleaved channel count. The podcast/TTS streams are mono (1). */\n channels: number;\n}\n\nexport interface StreamingAudioPlayer {\n /** Buffer one base64 chunk of s16le PCM. Safe to call before play(). */\n enqueueBase64: (b64: string) => void;\n /** No more chunks are coming — playback may drain to the true end. */\n end: () => void;\n play: () => void;\n pause: () => void;\n /** Seek within the buffered range (clamped). Keeps playing if playing. */\n seekMs: (ms: number) => void;\n getPositionMs: () => number;\n getBufferedMs: () => number;\n isPlaying: () => boolean;\n hasEnded: () => boolean;\n /** Subscribe to position/buffer changes. Returns unsubscribe. */\n onUpdate: (cb: () => void) => () => void;\n /** Tear down: stop audio, close the context, release buffers. */\n destroy: () => void;\n}\n\n/** Backward-compatible name for PCM-specific callers. */\nexport type StreamingPcmPlayer = StreamingAudioPlayer;\n\nfunction base64ToInt16(b64: string): Int16Array {\n const binary = atob(b64);\n const len = binary.length;\n const bytes = new Uint8Array(len);\n for (let i = 0; i < len; i++) bytes[i] = binary.charCodeAt(i);\n return new Int16Array(bytes.buffer, 0, len >> 1);\n}\n\nexport function createStreamingPcmPlayer(\n format: StreamingPcmFormat,\n): StreamingAudioPlayer {\n const sampleRate = format.sampleRate > 0 ? format.sampleRate : 24000;\n const channels = format.channels > 0 ? format.channels : 1;\n\n /** Buffered PCM, one Int16Array per received chunk (interleaved frames). */\n const chunks: Int16Array[] = [];\n /** Cumulative frame count at the END of each chunk — for offset lookup. */\n const chunkEndFrames: number[] = [];\n let totalFrames = 0;\n\n let ctx: AudioContext | null = null;\n const scheduled: AudioBufferSourceNode[] = [];\n /** Index of the next buffered chunk to schedule while playing. */\n let nextChunkIndex = 0;\n /** ctx time at which the next scheduled buffer should start (gapless chain). */\n let nextPlayTime = 0;\n\n let playing = false;\n let ended = false;\n let destroyed = false;\n /** Playhead when paused; while playing it's derived from the ctx clock. */\n let pausedAtFrame = 0;\n /** Anchors mapping ctx.currentTime → stream frame while playing. */\n let anchorCtxTime = 0;\n let anchorFrame = 0;\n\n let updateTimer: ReturnType<typeof setInterval> | null = null;\n const updateCallbacks = new Set<() => void>();\n\n function notify(): void {\n for (const cb of updateCallbacks) {\n try {\n cb();\n } catch {\n // subscriber errors must never break playback\n }\n }\n }\n\n function startUpdateLoop(): void {\n if (updateTimer !== null) return;\n updateTimer = setInterval(() => {\n notify();\n if (!playing && updateTimer !== null) {\n clearInterval(updateTimer);\n updateTimer = null;\n }\n }, 250);\n }\n\n function framesToMs(frames: number): number {\n return (frames / sampleRate) * 1000;\n }\n\n function getPositionFrames(): number {\n if (!playing || !ctx) return pausedAtFrame;\n const elapsed = Math.max(0, ctx.currentTime - anchorCtxTime);\n return Math.min(\n anchorFrame + Math.round(elapsed * sampleRate),\n totalFrames,\n );\n }\n\n function ensureContext(): AudioContext | null {\n if (typeof window === \"undefined\") return null;\n if (!ctx) {\n const Ctor: typeof AudioContext =\n window.AudioContext ||\n (window as unknown as { webkitAudioContext: typeof AudioContext })\n .webkitAudioContext;\n if (!Ctor) return null;\n ctx = new Ctor({ sampleRate });\n }\n if (ctx.state === \"suspended\") void ctx.resume();\n return ctx;\n }\n\n function int16ToBuffer(\n c: AudioContext,\n pcm: Int16Array,\n fromFrame: number,\n ): AudioBuffer | null {\n const frames = pcm.length / channels - fromFrame;\n if (frames <= 0) return null;\n const buf = c.createBuffer(channels, frames, sampleRate);\n for (let ch = 0; ch < channels; ch++) {\n const data = buf.getChannelData(ch);\n for (let i = 0; i < frames; i++) {\n data[i] = (pcm[(fromFrame + i) * channels + ch] ?? 0) / 0x8000;\n }\n }\n return buf;\n }\n\n function scheduleChunk(index: number, fromFrame: number): void {\n const pcm = chunks[index];\n if (!ctx || !pcm) return;\n const buf = int16ToBuffer(ctx, pcm, fromFrame);\n if (!buf) return;\n const src = ctx.createBufferSource();\n src.buffer = buf;\n src.connect(ctx.destination);\n\n const now = ctx.currentTime;\n const startAt = Math.max(now, nextPlayTime);\n // An underrun gap (playback drained before this chunk arrived) must not\n // count toward the playhead — shift the anchor by the silent gap.\n if (startAt > nextPlayTime && nextPlayTime > 0) {\n anchorCtxTime += startAt - nextPlayTime;\n }\n src.start(startAt);\n nextPlayTime = startAt + buf.duration;\n scheduled.push(src);\n src.onended = () => {\n const i = scheduled.indexOf(src);\n if (i !== -1) scheduled.splice(i, 1);\n try {\n src.disconnect();\n } catch {\n // already disconnected\n }\n if (\n playing &&\n scheduled.length === 0 &&\n ended &&\n nextChunkIndex >= chunks.length\n ) {\n // True end of the stream — settle as paused at the end.\n pausedAtFrame = totalFrames;\n playing = false;\n notify();\n }\n };\n }\n\n function scheduleFrom(frame: number): void {\n if (!ctx) return;\n // Locate the chunk containing `frame`.\n let index = chunkEndFrames.findIndex((end) => frame < end);\n if (index === -1) index = chunks.length; // at/past the buffered end\n const chunkStart = index === 0 ? 0 : (chunkEndFrames[index - 1] ?? 0);\n\n nextPlayTime = ctx.currentTime;\n anchorCtxTime = ctx.currentTime;\n anchorFrame = frame;\n if (index < chunks.length) {\n scheduleChunk(index, frame - chunkStart);\n for (let i = index + 1; i < chunks.length; i++) scheduleChunk(i, 0);\n }\n nextChunkIndex = chunks.length;\n }\n\n function stopScheduled(): void {\n for (const src of scheduled) {\n try {\n src.onended = null;\n src.stop(0);\n src.disconnect();\n } catch {\n // already stopped\n }\n }\n scheduled.length = 0;\n }\n\n function enqueueBase64(b64: string): void {\n if (destroyed || ended) return;\n let pcm: Int16Array;\n try {\n pcm = base64ToInt16(b64);\n } catch {\n return; // a malformed chunk is dropped, never fatal\n }\n if (pcm.length === 0) return;\n chunks.push(pcm);\n totalFrames += pcm.length / channels;\n chunkEndFrames.push(totalFrames);\n if (playing && ctx) {\n // Chain the new chunk onto the live schedule.\n scheduleChunk(chunks.length - 1, 0);\n nextChunkIndex = chunks.length;\n }\n notify();\n }\n\n function play(): void {\n if (destroyed || playing) return;\n const c = ensureContext();\n if (!c) return;\n if (ended && pausedAtFrame >= totalFrames) pausedAtFrame = 0; // replay\n playing = true;\n scheduleFrom(pausedAtFrame);\n startUpdateLoop();\n notify();\n }\n\n function pause(): void {\n if (!playing) return;\n pausedAtFrame = getPositionFrames();\n playing = false;\n stopScheduled();\n notify();\n }\n\n function seekMs(ms: number): void {\n const frame = Math.max(\n 0,\n Math.min(Math.round((ms / 1000) * sampleRate), totalFrames),\n );\n if (playing) {\n stopScheduled();\n scheduleFrom(frame);\n } else {\n pausedAtFrame = frame;\n }\n notify();\n }\n\n function end(): void {\n ended = true;\n notify();\n }\n\n function destroy(): void {\n if (destroyed) return;\n destroyed = true;\n playing = false;\n stopScheduled();\n if (updateTimer !== null) {\n clearInterval(updateTimer);\n updateTimer = null;\n }\n updateCallbacks.clear();\n chunks.length = 0;\n chunkEndFrames.length = 0;\n if (ctx) {\n void ctx.close().catch(() => undefined);\n ctx = null;\n }\n }\n\n return {\n enqueueBase64,\n end,\n play,\n pause,\n seekMs,\n getPositionMs: () => framesToMs(getPositionFrames()),\n getBufferedMs: () => framesToMs(totalFrames),\n isPlaying: () => playing,\n hasEnded: () => ended,\n onUpdate: (cb) => {\n updateCallbacks.add(cb);\n return () => updateCallbacks.delete(cb);\n },\n destroy,\n };\n}\n","// @ai-matrx/media/live-audio — mp3-player.ts\n//\n// Moved verbatim from matrx-frontend features/audio/streamingMp3Player.ts\n// (2026-10-01).\n//\n// Progressive MP3 player for encoded `audio_stream_chunk` events. ElevenLabs\n// returns one continuous MP3 byte stream in provider-sized chunks; MediaSource\n// lets the browser decode and play those bytes while later chunks are still\n// arriving. The interface intentionally matches StreamingAudioPlayer so the\n// podcast UI can swap PCM/Web Audio and MP3/MediaSource by event metadata.\n\nimport type { StreamingAudioPlayer } from \"./pcm-player\";\n\nexport interface StreamingMp3PlayerOptions {\n mimeType?: string;\n onError?: (error: Error) => void;\n}\n\nfunction base64ToArrayBuffer(b64: string): ArrayBuffer {\n const binary = atob(b64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes.buffer;\n}\n\nexport function createStreamingMp3Player(\n options: StreamingMp3PlayerOptions = {},\n): StreamingAudioPlayer | null {\n if (typeof window === \"undefined\" || typeof MediaSource === \"undefined\") {\n console.info(\n \"[streaming-mp3] MediaSource is unavailable — live MP3 playback disabled; waiting for the canonical audio file\",\n );\n return null;\n }\n\n const mimeType = options.mimeType || \"audio/mpeg\";\n if (!MediaSource.isTypeSupported(mimeType)) {\n console.info(\n `[streaming-mp3] browser does not support MediaSource ${mimeType} — live playback disabled; waiting for the canonical audio file`,\n );\n return null;\n }\n\n const mediaSource = new MediaSource();\n const objectUrl = URL.createObjectURL(mediaSource);\n const audio = document.createElement(\"audio\");\n audio.preload = \"auto\";\n audio.hidden = true;\n audio.setAttribute(\"aria-hidden\", \"true\");\n audio.src = objectUrl;\n // Keep the media element attached. Chromium will play a detached element,\n // but WebKit's MediaSource implementation is not reliable unless the\n // element belongs to the document. The visible transport remains the\n // LiveAudioPlayer component; this element is only the decoder/output sink.\n document.body.appendChild(audio);\n\n const pending: ArrayBuffer[] = [];\n const callbacks = new Set<() => void>();\n let sourceBuffer: SourceBuffer | null = null;\n let streamEnded = false;\n let destroyed = false;\n let failed = false;\n\n function notify(): void {\n for (const callback of callbacks) callback();\n }\n\n function fail(message: string, cause?: unknown): void {\n if (failed || destroyed) return;\n failed = true;\n const error =\n cause instanceof Error\n ? new Error(message, { cause })\n : new Error(message);\n console.error(`[streaming-mp3] ${message}`, cause ?? \"\");\n options.onError?.(error);\n notify();\n }\n\n function finishIfReady(): void {\n if (\n !destroyed &&\n !failed &&\n streamEnded &&\n pending.length === 0 &&\n sourceBuffer &&\n !sourceBuffer.updating &&\n mediaSource.readyState === \"open\"\n ) {\n try {\n mediaSource.endOfStream();\n } catch (error) {\n fail(\"could not finalize the MediaSource stream\", error);\n }\n }\n }\n\n function appendNext(): void {\n if (\n destroyed ||\n failed ||\n !sourceBuffer ||\n sourceBuffer.updating ||\n mediaSource.readyState !== \"open\"\n ) {\n return;\n }\n const next = pending.shift();\n if (!next) {\n finishIfReady();\n return;\n }\n try {\n sourceBuffer.appendBuffer(next);\n } catch (error) {\n fail(\"could not append an MP3 stream chunk\", error);\n }\n }\n\n mediaSource.addEventListener(\n \"sourceopen\",\n () => {\n if (destroyed || failed || sourceBuffer) return;\n try {\n sourceBuffer = mediaSource.addSourceBuffer(mimeType);\n sourceBuffer.mode = \"sequence\";\n sourceBuffer.addEventListener(\"updateend\", () => {\n notify();\n appendNext();\n });\n sourceBuffer.addEventListener(\"error\", () => {\n fail(\"MediaSource rejected the MP3 stream\");\n });\n appendNext();\n } catch (error) {\n fail(\"could not initialize an MP3 SourceBuffer\", error);\n }\n },\n { once: true },\n );\n\n for (const event of [\"timeupdate\", \"progress\", \"play\", \"pause\", \"ended\"]) {\n audio.addEventListener(event, notify);\n }\n audio.addEventListener(\"error\", () => {\n fail(\n `browser could not decode the live MP3 stream${audio.error?.message ? `: ${audio.error.message}` : \"\"}`,\n );\n });\n\n function getBufferedMs(): number {\n try {\n const ranges = audio.buffered;\n return ranges.length > 0 ? ranges.end(ranges.length - 1) * 1000 : 0;\n } catch {\n return 0;\n }\n }\n\n return {\n enqueueBase64: (b64) => {\n if (destroyed || failed || streamEnded) return;\n try {\n const bytes = base64ToArrayBuffer(b64);\n if (bytes.byteLength === 0) return;\n pending.push(bytes);\n appendNext();\n notify();\n } catch (error) {\n fail(\"received a malformed base64 MP3 chunk\", error);\n }\n },\n end: () => {\n if (destroyed || failed) return;\n streamEnded = true;\n finishIfReady();\n notify();\n },\n play: () => {\n if (destroyed || failed) return;\n void audio.play().catch((error) => {\n fail(\"browser refused live MP3 playback\", error);\n });\n },\n pause: () => audio.pause(),\n seekMs: (ms) => {\n if (destroyed || failed) return;\n const clampedMs = Math.max(0, Math.min(ms, getBufferedMs()));\n audio.currentTime = clampedMs / 1000;\n notify();\n },\n getPositionMs: () =>\n Number.isFinite(audio.currentTime) ? audio.currentTime * 1000 : 0,\n getBufferedMs,\n isPlaying: () => !audio.paused && !audio.ended,\n hasEnded: () => streamEnded,\n onUpdate: (callback) => {\n callbacks.add(callback);\n return () => callbacks.delete(callback);\n },\n destroy: () => {\n if (destroyed) return;\n destroyed = true;\n audio.pause();\n pending.length = 0;\n callbacks.clear();\n if (sourceBuffer?.updating) {\n try {\n sourceBuffer.abort();\n } catch {\n // The MediaSource may already have closed between the state check and abort.\n }\n }\n audio.removeAttribute(\"src\");\n audio.load();\n audio.remove();\n URL.revokeObjectURL(objectUrl);\n sourceBuffer = null;\n },\n };\n}\n","/**\n * The live-audio controller — the ONE client interpretation of the server's\n * `audio_stream_chunk` / `audio_stream_end` data events\n * (matrx_connect AudioStreamChunkData / AudioStreamEndData):\n *\n * chunk: { stream_id, seq, audio_base64, mime_type,\n * encoding: \"pcm_s16le\" | \"mp3\", sample_rate, bits_per_sample,\n * channels } — frames ≤32 KiB raw, contiguous `seq` from 0\n * end: { stream_id, total_chunks, url, file_id, cdn_url, … }\n *\n * Framework-agnostic: feed it every `data` payload; it picks the codec player\n * (PCM → Web Audio, MP3 → MediaSource) from the chunk metadata, enforces one\n * stream identity and a gap-free `seq`, and hands the player to the host via\n * `onPlayer`. Any violation DROPS live playback loudly (the saved file takes\n * over when the end event lands) — playing a reordered or spliced stream is\n * worse than waiting. Behavior moved verbatim from matrx-frontend's podcast\n * studio (`useStudioRun`) so every client plays live TTS identically.\n */\n\nimport { createStreamingMp3Player } from \"./mp3-player\";\nimport {\n createStreamingPcmPlayer,\n type StreamingAudioPlayer,\n type StreamingPcmFormat,\n} from \"./pcm-player\";\n\nexport type LiveAudioEncoding = \"pcm_s16le\" | \"mp3\";\n\n/** `data` payload `{ type: \"audio_stream_chunk\", … }`. */\nexport interface LiveAudioChunkEvent {\n type: \"audio_stream_chunk\";\n stream_id: string;\n seq: number;\n audio_base64: string;\n mime_type: string;\n encoding?: string | null;\n sample_rate?: number | null;\n bits_per_sample?: number | null;\n channels?: number | null;\n}\n\n/** `data` payload `{ type: \"audio_stream_end\", … }` — the saved file's identity. */\nexport interface LiveAudioEndEvent {\n type: \"audio_stream_end\";\n stream_id: string;\n total_chunks?: number | null;\n url?: string | null;\n file_id?: string | null;\n cdn_url?: string | null;\n mime_type?: string | null;\n duration_ms?: number | null;\n}\n\nexport const LIVE_AUDIO_CHUNK = \"audio_stream_chunk\";\nexport const LIVE_AUDIO_END = \"audio_stream_end\";\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/** True for a `data` payload of either live-audio event type. */\nexport function isLiveAudioEvent(\n data: unknown,\n): data is LiveAudioChunkEvent | LiveAudioEndEvent {\n return (\n isRecord(data) && (data.type === LIVE_AUDIO_CHUNK || data.type === LIVE_AUDIO_END)\n );\n}\n\n/** The codec of a chunk, from `encoding` first, else its MIME type. Null = unsupported. */\nexport function liveAudioEncodingOf(\n chunk: Pick<LiveAudioChunkEvent, \"encoding\" | \"mime_type\">,\n): LiveAudioEncoding | null {\n const mime = chunk.mime_type ?? \"\";\n if (chunk.encoding === \"mp3\" || /(?:mpeg|mp3)/i.test(mime)) return \"mp3\";\n if (chunk.encoding === \"pcm_s16le\" || /(?:l16|pcm)/i.test(mime)) return \"pcm_s16le\";\n return null;\n}\n\nexport interface LiveAudioControllerOptions {\n /** The current player (null after a drop/reset) — e.g. a React state setter. */\n onPlayer: (player: StreamingAudioPlayer | null) => void;\n /** The stream finished; the saved file (`url` / `cdn_url` / `file_id`) is ready. */\n onEnd?: (event: LiveAudioEndEvent) => void;\n /** Live playback was dropped. Default: `console.warn` — never silent. */\n onDrop?: (reason: string) => void;\n /** Codec player factories (tests, non-DOM hosts). Defaults: the package players. */\n createPcmPlayer?: (format: StreamingPcmFormat) => StreamingAudioPlayer | null;\n createMp3Player?: (options: {\n mimeType: string;\n onError: (error: Error) => void;\n }) => StreamingAudioPlayer | null;\n}\n\nexport interface LiveAudioController {\n /**\n * Feed one `data` payload. Returns true when it was a live-audio event\n * (consumed here — never put base64 chunks into UI state); false otherwise.\n */\n handle(data: unknown): boolean;\n /** A fresh stream (new run, resume, rejoin replay from frame one): seq restarts at 0. */\n reset(): void;\n /** Tear down the player for good. */\n destroy(): void;\n readonly player: StreamingAudioPlayer | null;\n}\n\nexport function createLiveAudioController(\n options: LiveAudioControllerOptions,\n): LiveAudioController {\n const makePcm = options.createPcmPlayer ?? createStreamingPcmPlayer;\n const makeMp3 = options.createMp3Player ?? createStreamingMp3Player;\n\n let player: StreamingAudioPlayer | null = null;\n let expectedSeq = 0;\n let streamId: string | null = null;\n let encoding: LiveAudioEncoding | null = null;\n let broken = false;\n\n function clearPlayer(): void {\n player?.destroy();\n player = null;\n options.onPlayer(null);\n }\n\n function drop(reason: string): void {\n const message = `${reason} — dropping live playback; the saved file takes over when rendering finishes`;\n if (options.onDrop) options.onDrop(message);\n else console.warn(`[live-audio] ${message}`);\n broken = true;\n clearPlayer();\n }\n\n function onChunk(e: LiveAudioChunkEvent): void {\n if (broken) return;\n const codec = liveAudioEncodingOf(e);\n if (!codec) {\n drop(`unsupported audio stream format ${e.encoding || e.mime_type}`);\n return;\n }\n if (streamId !== null && (streamId !== e.stream_id || encoding !== codec)) {\n drop(\n `audio stream identity/format changed mid-render (${streamId}/${encoding} → ${e.stream_id}/${codec})`,\n );\n return;\n }\n if (e.seq !== expectedSeq) {\n drop(`audio stream gap (expected seq ${expectedSeq}, got ${e.seq})`);\n return;\n }\n streamId = e.stream_id;\n encoding = codec;\n expectedSeq = e.seq + 1;\n if (!player) {\n const created =\n codec === \"mp3\"\n ? makeMp3({\n mimeType: e.mime_type || \"audio/mpeg\",\n onError: (error) => drop(error.message),\n })\n : makePcm({\n sampleRate: e.sample_rate || 24000,\n channels: e.channels || 1,\n });\n if (!created) {\n drop(`no browser player is available for ${codec}`);\n return;\n }\n player = created;\n options.onPlayer(created);\n }\n player.enqueueBase64(e.audio_base64);\n }\n\n return {\n handle(data: unknown): boolean {\n if (!isLiveAudioEvent(data)) return false;\n if (data.type === LIVE_AUDIO_CHUNK) {\n onChunk(data);\n return true;\n }\n player?.end();\n options.onEnd?.(data);\n return true;\n },\n reset(): void {\n expectedSeq = 0;\n streamId = null;\n encoding = null;\n broken = false;\n if (player) clearPlayer();\n },\n destroy(): void {\n player?.destroy();\n player = null;\n },\n get player() {\n return player;\n },\n };\n}\n\n/**\n * Hand a live player over to the saved file: pause it (never two audio\n * sources at once) and return where the listener was, so the full player can\n * pick up at the same moment. Null when nothing has been heard yet.\n */\nexport function takeLiveAudioHandoff(\n player: StreamingAudioPlayer,\n): { timeSec: number; resume: boolean } | null {\n const resume = player.isPlaying();\n const timeSec = player.getPositionMs() / 1000;\n player.pause();\n return timeSec > 0 ? { timeSec, resume } : null;\n}\n","/**\n * Many live streams at once — one live-audio controller per key (a workflow\n * run's `(node_id, dispatch_id, item_index)`, a chat request id, …) with a\n * change subscription, so a viewer can render the player for the step it\n * shows. Framework-agnostic; React reads it through `useLiveAudioPlayer`.\n */\n\nimport {\n createLiveAudioController,\n isLiveAudioEvent,\n type LiveAudioController,\n type LiveAudioControllerOptions,\n type LiveAudioEndEvent,\n} from \"./controller\";\nimport type { StreamingAudioPlayer } from \"./pcm-player\";\n\nexport interface LiveAudioRegistryOptions\n extends Omit<LiveAudioControllerOptions, \"onPlayer\" | \"onEnd\" | \"onDrop\"> {\n onEnd?: (key: string, event: LiveAudioEndEvent) => void;\n onDrop?: (key: string, reason: string) => void;\n}\n\nexport interface LiveAudioRegistry {\n /** Feed one `data` payload for `key`. True when it was a live-audio event (consumed). */\n handle(key: string, payload: unknown): boolean;\n /** The live player for `key`, or null. */\n player(key: string): StreamingAudioPlayer | null;\n /** Called whenever any key's player appears or goes away. */\n subscribe(listener: () => void): () => void;\n /** Start `key` over (a fresh stream for that step). */\n reset(key: string): void;\n /** Tear down every player. */\n destroy(): void;\n}\n\nexport function createLiveAudioRegistry(\n options: LiveAudioRegistryOptions = {},\n): LiveAudioRegistry {\n const controllers = new Map<string, LiveAudioController>();\n const players = new Map<string, StreamingAudioPlayer>();\n const listeners = new Set<() => void>();\n\n const notify = (): void => {\n for (const listener of listeners) {\n try {\n listener();\n } catch {\n // a subscriber error never breaks playback\n }\n }\n };\n\n const controllerFor = (key: string): LiveAudioController => {\n let controller = controllers.get(key);\n if (!controller) {\n controller = createLiveAudioController({\n onPlayer: (player) => {\n if (player) players.set(key, player);\n else players.delete(key);\n notify();\n },\n onEnd: (event) => options.onEnd?.(key, event),\n onDrop: (reason) =>\n options.onDrop\n ? options.onDrop(key, reason)\n : console.warn(`[live-audio] ${key}: ${reason}`),\n ...(options.createPcmPlayer ? { createPcmPlayer: options.createPcmPlayer } : {}),\n ...(options.createMp3Player ? { createMp3Player: options.createMp3Player } : {}),\n });\n controllers.set(key, controller);\n }\n return controller;\n };\n\n return {\n handle(key, payload) {\n if (!isLiveAudioEvent(payload)) return false;\n return controllerFor(key).handle(payload);\n },\n player: (key) => players.get(key) ?? null,\n subscribe(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n reset(key) {\n controllers.get(key)?.reset();\n },\n destroy() {\n for (const controller of controllers.values()) controller.destroy();\n controllers.clear();\n const had = players.size > 0;\n players.clear();\n if (had) notify();\n },\n };\n}\n","/**\n * Reassemble a workflow run's live-media `node_stream` frames into the exact\n * `data` payloads `/chat` streams (`audio_stream_chunk`, `audio_stream_end`,\n * `partial_image`, `media_block`).\n *\n * Wire contract (aidream `services/runtime/FEATURE.md` — \"Live media reaches a\n * workflow run as node_stream kind=media frames\", producer `_publish_framed`):\n *\n * {\"event\":\"node_stream\",\"run_id\",\"node_id\",\"dispatch_id\",\"item_index\",\n * \"kind\":\"media\",\"delta\":\"<slice of the payload JSON>\",\"stream_seq\",\"ts\",\n * \"frame_id\":\"media:<hex>\",\"frame_index\":i,\"frame_count\":n,…}\n *\n * Group by `frame_id`, join `delta` in `frame_index` order once all\n * `frame_count` slices are held, `JSON.parse`. Payload order = the\n * `stream_seq` of each set's first frame; an incomplete set is dropped (the\n * channel is ephemeral — a late attach or a lost frame never hands half a\n * payload, or a reordered audio chunk, to a player).\n *\n * `stream_seq` is counted PER EMITTER (one step's `ProgressTrackingEmitter`),\n * and one emitter publishes its sets in order through one wire queue — so\n * ordering and abandonment are judged per source `(node_id, dispatch_id,\n * item_index)`; parallel steps interleave freely.\n */\n\n/** The fields of one `node_stream` frame this assembler reads. */\nexport interface MediaNodeStreamFrame {\n kind?: string | null;\n delta: string;\n node_id?: string | null;\n dispatch_id?: string | null;\n item_index?: number | null;\n stream_seq?: number | null;\n frame_id?: string | null;\n frame_index?: number | null;\n frame_count?: number | null;\n}\n\n/** Which step produced a payload: `(node_id, dispatch_id, item_index)`. */\nexport interface MediaFrameSource {\n nodeId: string | null;\n dispatchId: string | null;\n itemIndex: number | null;\n}\n\nexport interface ReassembledMediaPayload {\n /** The parsed `data` payload — route it by `type` exactly as the chat stream does. */\n payload: Record<string, unknown>;\n source: MediaFrameSource;\n /** `stream_seq` of the set's first frame (the ordering key). */\n streamSeq: number;\n}\n\n/** Stable key for a step's media (one live player per key). */\nexport function mediaFrameSourceKey(source: MediaFrameSource): string {\n return `${source.nodeId ?? \"\"}|${source.dispatchId ?? \"\"}|${source.itemIndex ?? \"\"}`;\n}\n\n/** Frame sets held open at once before the oldest is abandoned. */\nexport const MAX_OPEN_MEDIA_FRAME_SETS = 32;\n\ninterface OpenSet {\n key: string;\n count: number;\n slices: Map<number, string>;\n firstSeq: number;\n source: MediaFrameSource;\n}\n\nexport interface MediaFrameAssemblerOptions {\n /** Payloads in `stream_seq` order. */\n onPayload: (payload: ReassembledMediaPayload) => void;\n /** An incomplete or unparseable set was dropped. Default: `console.warn`. */\n onDrop?: (frameId: string, reason: string) => void;\n}\n\nexport interface MediaFrameAssembler {\n /** Feed one `node_stream` frame. Returns true when it was a media frame (consumed). */\n push(frame: MediaNodeStreamFrame): boolean;\n /** The stream ended: drop every incomplete set, release what is complete. */\n flush(): void;\n readonly openCount: number;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nexport function createMediaFrameAssembler(\n options: MediaFrameAssemblerOptions,\n): MediaFrameAssembler {\n const open = new Map<string, OpenSet>();\n const ready: ReassembledMediaPayload[] = [];\n let arrival = 0;\n\n const drop = (frameId: string, reason: string): void => {\n open.delete(frameId);\n if (options.onDrop) options.onDrop(frameId, reason);\n else console.warn(`[media-frames] dropped ${frameId}: ${reason}`);\n };\n\n /** Release every ready payload no still-open, earlier-started set of the SAME source must precede. */\n const release = (): void => {\n if (ready.length === 0) return;\n ready.sort((a, b) => a.streamSeq - b.streamSeq);\n const blocking = new Map<string, number>();\n for (const set of open.values()) {\n blocking.set(set.key, Math.min(blocking.get(set.key) ?? Infinity, set.firstSeq));\n }\n for (let i = 0; i < ready.length; ) {\n const item = ready[i]!;\n const key = mediaFrameSourceKey(item.source);\n if (item.streamSeq < (blocking.get(key) ?? Infinity)) {\n ready.splice(i, 1);\n options.onPayload(item);\n } else {\n i += 1;\n }\n }\n };\n\n return {\n push(frame: MediaNodeStreamFrame): boolean {\n if (frame.kind !== \"media\") return false;\n const frameId = frame.frame_id;\n const count = frame.frame_count ?? 1;\n const index = frame.frame_index ?? 0;\n if (\n !frameId ||\n !Number.isInteger(count) ||\n count < 1 ||\n !Number.isInteger(index) ||\n index < 0 ||\n index >= count\n ) {\n if (options.onDrop) options.onDrop(frameId ?? \"?\", \"malformed frame\");\n else console.warn(\"[media-frames] malformed media frame dropped\");\n return true;\n }\n arrival += 1;\n const seq =\n typeof frame.stream_seq === \"number\" && Number.isFinite(frame.stream_seq)\n ? frame.stream_seq\n : arrival;\n\n let set = open.get(frameId);\n if (!set) {\n if (open.size >= MAX_OPEN_MEDIA_FRAME_SETS) {\n const oldest = open.keys().next();\n if (!oldest.done) drop(oldest.value, \"abandoned (too many open sets)\");\n }\n const source: MediaFrameSource = {\n nodeId: frame.node_id ?? null,\n dispatchId: frame.dispatch_id ?? null,\n itemIndex: frame.item_index ?? null,\n };\n set = {\n key: mediaFrameSourceKey(source),\n count,\n slices: new Map(),\n firstSeq: seq,\n source,\n };\n open.set(frameId, set);\n }\n set.slices.set(index, frame.delta);\n if (index === 0) set.firstSeq = Math.min(set.firstSeq, seq);\n if (set.slices.size < set.count) {\n release();\n return true;\n }\n\n open.delete(frameId);\n let body = \"\";\n for (let i = 0; i < set.count; i++) body += set.slices.get(i) ?? \"\";\n let parsed: unknown;\n try {\n parsed = JSON.parse(body);\n } catch {\n if (options.onDrop) options.onDrop(frameId, \"unparseable payload\");\n else console.warn(`[media-frames] dropped ${frameId}: unparseable payload`);\n release();\n return true;\n }\n if (!isRecord(parsed) || typeof parsed.type !== \"string\") {\n if (options.onDrop) options.onDrop(frameId, \"payload has no type\");\n else console.warn(`[media-frames] dropped ${frameId}: payload has no type`);\n release();\n return true;\n }\n // One emitter publishes its sets contiguously, so an earlier-started set\n // of the SAME source that is still open will never complete — drop it so\n // it cannot hold that source's later payloads hostage.\n for (const [id, other] of [...open.entries()]) {\n if (other.key === set.key && other.firstSeq < set.firstSeq) {\n drop(id, \"incomplete set\");\n }\n }\n ready.push({ payload: parsed, source: set.source, streamSeq: set.firstSeq });\n release();\n return true;\n },\n flush(): void {\n for (const id of [...open.keys()]) drop(id, \"incomplete at stream end\");\n release();\n },\n get openCount() {\n return open.size;\n },\n };\n}\n"],"mappings":"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","names":["end","isRecord"]}
package/dist/react.cjs CHANGED
@@ -52,7 +52,9 @@ __export(react_exports, {
52
52
  MediaThumbnail: () => MediaThumbnail,
53
53
  MusicIcon: () => MusicIcon,
54
54
  PackageBoxIcon: () => PackageBoxIcon,
55
+ PauseIcon: () => PauseIcon,
55
56
  PenToolIcon: () => PenToolIcon,
57
+ PlayIcon: () => PlayIcon,
56
58
  PlusIcon: () => PlusIcon,
57
59
  SearchIcon: () => SearchIcon,
58
60
  Settings2Icon: () => Settings2Icon,
@@ -350,6 +352,15 @@ function SearchIcon(props) {
350
352
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m21 21-4.3-4.3" })
351
353
  ] });
352
354
  }
355
+ function PauseIcon(props) {
356
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...svgProps(props), children: [
357
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "14", y: "3", width: "5", height: "18", rx: "1" }),
358
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: "5", y: "3", width: "5", height: "18", rx: "1" })
359
+ ] });
360
+ }
361
+ function PlayIcon(props) {
362
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { ...svgProps(props), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z" }) });
363
+ }
353
364
 
354
365
  // src/core/file-kind.ts
355
366
  var ROWS = [