//! The audio engine, owned by the PROCESS rather than by Tauri. //! //! Android Auto can start this app with no Activity: the car binds //! `MediaService`, the service starts the process, and there is no WebView and //! no `tauri::AppHandle` anywhere in it. Playback still has to work, so the //! engine cannot live in Tauri's managed state the way it does on the desktop. //! //! **One `Player` per process, and that is the whole point.** `Player::spawn` //! opens an output stream; two of them in one process is two AAudio streams //! playing over each other, which is the failure this module exists to make //! impossible. Whoever needs audio first creates it — `playback::init` when the //! app launches normally, or [`play_media_id`] when a car taps a row into a //! process that has no UI — and everyone after that gets the same one. //! //! The event receiver is single-consumer, so the pump lives here too and //! offers a **subscription slot**: when the WebView does come up, //! `playback::init` registers its forwarder and starts seeing events, without //! a second engine and without the events it missed being replayed at it. //! //! The queue here is deliberately NOT the app's queue. The JS one keeps //! shuffle, repeat, `alts` failover, provenance and the three-ahead preload; //! this is a `Vec` and an index whose only job is that the album keeps //! playing when there is no JS alive to advance it. use player::{Player, PlayerEvent}; use std::path::PathBuf; use std::sync::mpsc::Sender; use std::sync::{Arc, Mutex, OnceLock}; pub struct Audio { pub player: Player, /// Where the WebView's pump listens, once there is a WebView. `None` in a /// service-only process, and every event is simply dropped — nothing is /// buffered for a UI that may never exist. subscriber: Mutex>>, queue: Mutex, } /// Enough of a track to play it and to describe it on a card, read once when /// the queue is built so that advancing never needs the database again. #[derive(Clone)] pub struct PlayTrack { pub path: PathBuf, pub title: String, pub artist: String, pub album: String, pub duration: Option, /// Absolute path to the album's cover. The card is built from this, and /// so is the colour Android Auto washes the playback screen with — it /// extracts from whatever artwork it is given, which is why supplying the /// app icon tinted every record the same blue. pub art: Option, } #[derive(Default)] struct Queue { items: Vec, idx: usize, } static AUDIO: OnceLock> = OnceLock::new(); /// The process's one engine, created on first use. /// /// `device` is honoured only by the caller that wins the race to create it; /// afterwards it is ignored, because changing the output device is /// `Player::set_output_device` on the live engine rather than a new engine. pub fn audio(device: Option) -> Arc