//! Platform abstraction layer //! //! This module defines traits that each platform (web, desktop, mobile) must implement. //! The traits make the platform boundary explicit and provide compile-time verification. #![allow(async_fn_in_trait)] use crate::{app::Command, effects::AudioProcessor}; use color_eyre::eyre::Error; use dioxus::hooks::UnboundedReceiver; use mumble_web2_common::{ProxyOverrides, ServerStatus}; use std::collections::HashMap; use std::future::Future; use std::time::Duration; // ============================================================================ // Trait Definitions // ============================================================================ /// Platform-specific audio subsystem for capturing microphone input and creating playback streams. /// /// The audio system handles Opus encoding internally - callers receive encoded frames /// ready for network transmission. pub trait AudioSystemInterface: Sized { /// The player type returned by [`create_player`](Self::create_player). type AudioPlayer: AudioPlayerInterface; /// Initialize the audio system. async fn new() -> Result; /// Set the processor for the microphone input, mainly noise cancellation settings. fn set_processor(&self, processor: AudioProcessor); /// Begin listening to microphone input, calling the `each` function with /// encoded opus frames. fn start_recording( &mut self, each: impl FnMut(Vec, bool) + Send + 'static, ) -> Result<(), Error>; /// Begin playback of an audio stream, returning an object that can be passed opus frames. fn create_player(&mut self) -> Result; } /// A handle to an active audio playback stream for a single remote user. /// /// Each connected user gets their own `AudioPlayer` instance, which decodes /// incoming Opus frames and outputs PCM audio to the platform's audio device. /// The player manages its own decoder state and output buffer. pub trait AudioPlayerInterface { /// Decode and play an Opus-encoded audio frame. fn play_opus(&mut self, payload: &[u8]); } pub trait ConfigSystemInterface: Sized { fn new() -> Result; fn config_get(&self, key: &str) -> Option where T: serde::de::DeserializeOwned; fn config_set(&self, key: &str, value: &T) where T: serde::Serialize; } /// This is the main trait that each platform must implement. It combines all /// platform-specific functionality into a single interface, providing compile-time /// verification that all platforms implement the required functionality. pub trait PlatformInterface { type AudioSystem: AudioSystemInterface; type ConfigSystem: ConfigSystemInterface; /// Initialize logging for the platform. fn init_logging(); /// Request runtime permissions (Android audio recording, etc.). fn request_permissions(); /// Establish a connection to the Mumble server and run the network loop. fn network_connect( address: String, username: String, event_rx: &mut UnboundedReceiver, proxy_overrides: &ProxyOverrides, ) -> impl Future>; /// Get server status (user count, version, etc.). fn get_status( client: &reqwest::Client, ) -> impl Future>; /// Load the proxy overrides (proxy URL, cert hash, etc.). fn load_proxy_overrides() -> impl Future>; /// Async sleep for the given duration. fn sleep(duration: Duration) -> impl Future; } // ============================================================================ // Platform Modules // ============================================================================ mod stub; #[cfg(any(feature = "desktop", feature = "mobile"))] mod connect; #[cfg(any(feature = "desktop", feature = "mobile"))] mod native_audio; #[cfg(any(feature = "desktop", feature = "mobile"))] mod native_config; #[cfg(feature = "desktop")] mod desktop; #[cfg(feature = "mobile")] mod mobile; #[cfg(feature = "web")] mod web; // ============================================================================ // Platform Type Alias // ============================================================================ #[cfg(feature = "web")] pub type Platform = web::WebPlatform; #[cfg(all(feature = "desktop", not(feature = "web")))] pub type Platform = desktop::DesktopPlatform; #[cfg(all(feature = "mobile", not(feature = "web"), not(feature = "desktop")))] pub type Platform = mobile::MobilePlatform; #[cfg(all( not(feature = "mobile"), not(feature = "web"), not(feature = "desktop") ))] pub type Platform = stub::StubPlatform; pub type AudioSystem = ::AudioSystem; pub type AudioPlayer = ::AudioPlayer; pub type ConfigSystem = ::ConfigSystem; // ======================== // Platform Async Runtime // ======================== // Note: these can not be part of the Platform because they differ in Send requiremets #[cfg(all(any(feature = "desktop", feature = "mobile"), not(feature = "web")))] pub use connect::{spawn, SpawnHandle}; #[cfg(all( not(feature = "desktop"), not(feature = "mobile"), not(feature = "web") ))] pub use stub::{spawn, SpawnHandle}; #[cfg(feature = "web")] pub use web::{spawn, SpawnHandle}; // ======================= // Compile-time Assertions // ======================= const _: () = { fn assert_platform() {} // Check each implementation, and prevent warnings that the implementations are unused. #[cfg(feature = "web")] let _ = assert_platform::; #[cfg(feature = "desktop")] let _ = assert_platform::; #[cfg(feature = "mobile")] let _ = assert_platform::; let _ = assert_platform::; }; fn global_default_config() -> HashMap { serde_json::json!({}) .as_object() .unwrap() .clone() .into_iter() .collect() }