//! Cross-crate port handles. //! //! Exists to break the one back-edge in the audio↔analysis dependency: //! `psysonic-analysis` needs to ask "is this track currently playing?", but //! must not depend on `psysonic-audio` (which has the real dep on analysis, //! not the other way around). //! //! Implementation note: ports are exposed as **closure handles** rather than //! `Arc` — this avoids forcing every existing `State` //! callsite to switch to `State>` (which Tauri State requires //! for trait-object registration). The shell crate creates the handle by //! capturing an `AppHandle` and looking up the audio engine at call time. use std::sync::Arc; /// Read-only queries about the live playback session, used by analysis-side /// code to break the analysis→audio back-edge. The shell crate constructs an /// instance with two closures (each capturing an `AppHandle`) and registers it /// as Tauri State; `psysonic-analysis` looks it up via `try_state::<…>()`. /// /// The closures are independent so each can be a no-op / always-false fallback /// without coupling the other. #[derive(Clone)] pub struct PlaybackQueryHandle { is_playing: Arc bool + Send + Sync + 'static>, should_defer_backfill: Arc bool + Send + Sync + 'static>, } impl PlaybackQueryHandle { pub fn new(is_playing: P, should_defer_backfill: D) -> Self where P: Fn(&str) -> bool + Send + Sync + 'static, D: Fn(&str) -> bool + Send + Sync + 'static, { Self { is_playing: Arc::new(is_playing), should_defer_backfill: Arc::new(should_defer_backfill), } } /// `true` if `track_id` is the track currently being decoded/played. pub fn is_track_currently_playing(&self, track_id: &str) -> bool { (self.is_playing)(track_id) } /// `true` if a ranged HTTP playback for `track_id` is mid-flight and will /// seed analysis on completion — the backfill enqueue should defer. pub fn ranged_loudness_backfill_should_defer(&self, track_id: &str) -> bool { (self.should_defer_backfill)(track_id) } }