car-voice 0.15.1

Voice I/O capability for CAR — mic capture, VAD, listener/speaker traits
Documentation
//! Process-global tokio runtime for voice listeners' long-lived
//! consumer tasks.
//!
//! Why this exists (#120):
//!
//! Listeners spawn STT consumer tasks that need to outlive the
//! caller. In an FFI binary (NAPI / PyO3) the entry-point function
//! returning to JS / Python doesn't necessarily mean the napi-rs /
//! pyo3-asyncio runtime that ran the entry-point is still in scope
//! to poll background tasks. Empirically, the consumer task spawned
//! via `tokio::spawn` from inside a `#[napi] pub async fn` was
//! never being polled — `audio_rx.recv().await` resolved zero
//! times even after `audio_tx.try_send` calls succeeded
//! (@ian-siddiqi's meet-bot canary on `transcribe_stream`).
//!
//! Instead of spawning on the caller's runtime, listeners spawn on
//! a process-lifetime runtime owned by this module. Same shape that
//! already works for the capture thread (`std::thread::spawn` —
//! also process-lifetime). Multi-threaded so transcribe blocking
//! tasks don't starve event polling.

use std::sync::OnceLock;
use tokio::runtime::{Handle, Runtime};

/// Returns a long-lived `Handle` for spawning voice consumer tasks.
/// The runtime is built lazily on first call and lives until the
/// process exits.
pub(crate) fn voice_runtime_handle() -> Handle {
    static RT: OnceLock<Runtime> = OnceLock::new();
    let rt = RT.get_or_init(|| Runtime::new().expect("failed to build car-voice tokio runtime"));
    rt.handle().clone()
}