all platform traits implemented & dumb async runtime imports

This commit is contained in:
2026-01-24 21:11:58 -07:00
committed by Liam Warfield
parent 411d923c2a
commit 056a673bc0
6 changed files with 120 additions and 271 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ use std::cell::RefCell;
use std::sync::Arc;
use tracing::{error, info};
use crate::imp::{SpawnHandle, SpawnHandleInterface as _};
use crate::imp::SpawnHandle;
static DF_MODEL: Asset = asset!("/assets/DeepFilterNet3_ll_onnx.tar.gz");
// TODO: make this user configurable.
+31 -105
View File
@@ -4,20 +4,8 @@ use dioxus::hooks::UnboundedReceiver;
use etcetera::{choose_app_strategy, AppStrategy, AppStrategyArgs};
use mumble_web2_common::{ClientConfig, ServerStatus};
use std::collections::HashMap;
use std::future::Future;
use std::time::Duration;
use super::{
PlatformConfig, PlatformInit, PlatformInterface, PlatformNetwork, PlatformRuntime,
SpawnHandleTrait,
};
pub use super::connect::*;
pub use super::native_audio::*;
pub use tokio::task::spawn;
pub use tokio::time::sleep;
// ============================================================================
// Platform Struct
// ============================================================================
@@ -25,84 +13,69 @@ pub use tokio::time::sleep;
/// Desktop platform implementation using Tokio and native audio.
pub struct DesktopPlatform;
// ============================================================================
// SpawnHandle
// ============================================================================
pub type SpawnHandle = tokio::runtime::Handle;
impl SpawnHandleTrait for SpawnHandle {
fn spawn<F>(&self, future: F)
where
F: Future<Output = ()> + Send + 'static,
{
let _ = tokio::runtime::Handle::spawn(self, future);
}
fn current() -> Self {
tokio::runtime::Handle::current()
}
}
// ============================================================================
// Trait Implementations
// ============================================================================
impl PlatformRuntime for DesktopPlatform {
type SpawnHandle = SpawnHandle;
fn spawn<F>(future: F)
where
F: Future<Output = ()> + Send + 'static,
{
let _ = tokio::task::spawn(future);
}
impl super::PlatformInterface for DesktopPlatform {
type AudioSystem = super::native_audio::NativeAudioSystem;
async fn sleep(duration: Duration) {
tokio::time::sleep(duration).await;
}
}
impl PlatformConfig for DesktopPlatform {
async fn load_config() -> color_eyre::Result<ClientConfig> {
load_config().await
Ok(ClientConfig {
proxy_url: None,
cert_hash: None,
any_server: true,
})
}
fn load_username() -> Option<String> {
load_username()
let config = load_config_map();
config.get("username").cloned()
}
fn load_server_url() -> Option<String> {
load_server_url()
let config = load_config_map();
config.get("server").cloned()
}
fn set_default_username(username: &str) -> Option<()> {
set_default_username(username)
let mut config = load_config_map();
config.insert("username".to_string(), username.to_string());
save_config_map(&config).ok()
}
fn set_default_server(server: &str) -> Option<()> {
set_default_server(server)
let mut config = load_config_map();
config.insert("server".to_string(), server.to_string());
save_config_map(&config).ok()
}
}
impl PlatformNetwork for DesktopPlatform {
async fn network_connect(
address: String,
username: String,
event_rx: &mut UnboundedReceiver<Command>,
gui_config: &ClientConfig,
) -> Result<(), Error> {
network_connect(address, username, event_rx, gui_config).await
super::connect::network_connect(address, username, event_rx, gui_config).await
}
async fn get_status(client: &reqwest::Client) -> color_eyre::Result<ServerStatus> {
get_status(client).await
super::connect::get_status(client).await
}
}
impl PlatformInit for DesktopPlatform {
fn init_logging() {
init_logging();
use tracing::level_filters::LevelFilter;
use tracing_subscriber::filter::EnvFilter;
let env_filter = EnvFilter::builder()
.with_default_directive(LevelFilter::INFO.into())
.from_env_lossy();
tracing_subscriber::fmt()
.with_target(true)
.with_level(true)
.with_env_filter(env_filter)
.init();
}
fn request_permissions() {
@@ -110,8 +83,6 @@ impl PlatformInit for DesktopPlatform {
}
}
impl PlatformInterface for DesktopPlatform {}
fn get_config_path() -> std::path::PathBuf {
let strategy = choose_app_strategy(AppStrategyArgs {
top_level_domain: "com".to_string(),
@@ -139,48 +110,3 @@ fn save_config_map(config: &HashMap<String, String>) -> color_eyre::Result<()> {
std::fs::write(&config_path, contents)?;
Ok(())
}
pub fn set_default_username(username: &str) -> Option<()> {
let mut config = load_config_map();
config.insert("username".to_string(), username.to_string());
save_config_map(&config).ok()
}
pub fn set_default_server(server: &str) -> Option<()> {
let mut config = load_config_map();
config.insert("server".to_string(), server.to_string());
save_config_map(&config).ok()
}
pub fn load_username() -> Option<String> {
let config = load_config_map();
config.get("username").cloned()
}
pub fn load_server_url() -> Option<String> {
let config = load_config_map();
config.get("server").cloned()
}
pub async fn load_config() -> color_eyre::Result<ClientConfig> {
Ok(ClientConfig {
proxy_url: None,
cert_hash: None,
any_server: true,
})
}
pub fn init_logging() {
use tracing::level_filters::LevelFilter;
use tracing_subscriber::filter::EnvFilter;
let env_filter = EnvFilter::builder()
.with_default_directive(LevelFilter::INFO.into())
.from_env_lossy();
tracing_subscriber::fmt()
.with_target(true)
.with_level(true)
.with_env_filter(env_filter)
.init();
}
+33 -102
View File
@@ -1,19 +1,11 @@
use android_permissions::{PermissionManager, RECORD_AUDIO};
use crate::app::Command;
use color_eyre::eyre::Error;
use dioxus::hooks::UnboundedReceiver;
use jni::{objects::JObject, JavaVM};
use mumble_web2_common::{ClientConfig, ServerStatus};
use std::future::Future;
use std::time::Duration;
use super::{PlatformInterface, PlatformConfig, PlatformInit, PlatformNetwork, PlatformRuntime, SpawnHandleTrait};
pub use super::connect::*;
pub use super::native_audio::*;
pub use tokio::task::spawn;
pub use tokio::time::sleep;
// ============================================================================
// Platform Struct
@@ -22,67 +14,33 @@ pub use tokio::time::sleep;
/// Mobile platform implementation using Tokio, native audio, and Android permissions.
pub struct MobilePlatform;
// ============================================================================
// SpawnHandle
// ============================================================================
impl super::PlatformInterface for MobilePlatform {
type AudioSystem = super::native_audio::NativeAudioSystem;
pub type SpawnHandle = tokio::runtime::Handle;
impl SpawnHandleTrait for SpawnHandle {
fn spawn<F>(&self, future: F)
where
F: Future<Output = ()> + Send + 'static,
{
let _ = tokio::runtime::Handle::spawn(self, future);
}
fn current() -> Self {
tokio::runtime::Handle::current()
}
}
// ============================================================================
// Trait Implementations
// ============================================================================
impl PlatformRuntime for MobilePlatform {
type SpawnHandle = SpawnHandle;
fn spawn<F>(future: F)
where
F: Future<Output = ()> + Send + 'static,
{
let _ = tokio::task::spawn(future);
}
async fn sleep(duration: Duration) {
tokio::time::sleep(duration).await;
}
}
impl PlatformConfig for MobilePlatform {
async fn load_config() -> color_eyre::Result<ClientConfig> {
load_config().await
Ok(ClientConfig {
proxy_url: None,
cert_hash: None,
any_server: true,
})
}
fn load_username() -> Option<String> {
load_username()
None
}
fn load_server_url() -> Option<String> {
load_server_url()
None
}
fn set_default_username(username: &str) -> Option<()> {
set_default_username(username)
fn set_default_username(_username: &str) -> Option<()> {
None
}
fn set_default_server(server: &str) -> Option<()> {
set_default_server(server)
None
}
}
impl PlatformNetwork for MobilePlatform {
async fn network_connect(
address: String,
username: String,
@@ -95,66 +53,39 @@ impl PlatformNetwork for MobilePlatform {
async fn get_status(client: &reqwest::Client) -> color_eyre::Result<ServerStatus> {
get_status(client).await
}
}
impl PlatformInit for MobilePlatform {
fn init_logging() {
init_logging();
use tracing::level_filters::LevelFilter;
use tracing_subscriber::filter::EnvFilter;
let env_filter = EnvFilter::builder()
.with_default_directive(LevelFilter::INFO.into())
.from_env_lossy();
tracing_subscriber::fmt()
.with_target(true)
.with_level(true)
.with_env_filter(env_filter)
.init();
}
fn request_permissions() {
request_recording_permission();
}
async fn sleep(duration: Duration) {
tokio::time::sleep(duration).await;
}
}
impl PlatformInterface for MobilePlatform {}
pub fn set_default_username(_username: &str) -> Option<()> {
None
}
pub fn set_default_server(server: &str) -> Option<()> {
None
}
pub fn load_username() -> Option<String> {
None
}
pub fn load_server_url() -> Option<String> {
None
}
pub async fn load_config() -> color_eyre::Result<ClientConfig> {
Ok(ClientConfig {
proxy_url: None,
cert_hash: None,
any_server: true,
})
}
pub fn init_logging() {
use tracing::level_filters::LevelFilter;
use tracing_subscriber::filter::EnvFilter;
let env_filter = EnvFilter::builder()
.with_default_directive(LevelFilter::INFO.into())
.from_env_lossy();
tracing_subscriber::fmt()
.with_target(true)
.with_level(true)
.with_env_filter(env_filter)
.init();
}
#[cfg(feature = "mobile")]
pub fn request_permissions() {
request_recording_permission();
}
#[cfg(not(target_os = "android"))]
pub fn request_recording_permission() {}
#[cfg(target_os = "android")]
pub fn request_recording_permission() {
use android_permissions::{PermissionManager, RECORD_AUDIO};
use jni::{objects::JObject, JavaVM};
let ctx = ndk_context::android_context();
let vm = unsafe { JavaVM::from_raw(ctx.vm().cast()).unwrap() };
let activity = unsafe { JObject::from_raw(ctx.context().cast()) };
+12 -21
View File
@@ -14,17 +14,6 @@ use std::time::Duration;
// Trait Definitions
// ============================================================================
/// Trait for spawn handles that can be stored and used to spawn tasks later.
pub trait SpawnHandleInterface: Clone + 'static {
/// Spawn an async task using this handle.
fn spawn<F>(&self, future: F)
where
F: Future<Output = ()> + 'static;
/// Get a spawn handle for the current context.
fn current() -> Self;
}
pub trait AudioSystemInterface {
type AudioPlayer: AudioPlayerInterface;
}
@@ -36,7 +25,6 @@ pub trait AudioPlayerInterface {}
/// verification that all platforms implement the required functionality.
pub trait PlatformInterface {
type AudioSystem: AudioSystemInterface;
type SpawnHandle: SpawnHandleInterface;
/// Initialize logging for the platform.
fn init_logging();
@@ -72,11 +60,6 @@ pub trait PlatformInterface {
/// Save the default server URL.
fn set_default_server(server: &str) -> Option<()>;
/// Spawn an async task.
fn spawn<F>(future: F)
where
F: Future<Output = ()> + 'static;
/// Async sleep for the given duration.
fn sleep(duration: Duration) -> impl Future<Output = ()>;
}
@@ -86,7 +69,7 @@ pub trait PlatformInterface {
// ============================================================================
#[cfg(feature = "web")]
mod web;
pub mod web;
#[cfg(any(feature = "desktop", feature = "mobile"))]
mod connect;
@@ -94,9 +77,9 @@ mod connect;
mod native_audio;
#[cfg(feature = "desktop")]
mod desktop;
pub mod desktop;
#[cfg(feature = "mobile")]
mod mobile;
pub mod mobile;
// ============================================================================
// Platform Type Alias
@@ -113,7 +96,15 @@ pub type Platform = mobile::MobilePlatform;
pub type AudioSystem = <Platform as PlatformInterface>::AudioSystem;
pub type AudioPlayer = <AudioSystem as AudioSystemInterface>::AudioPlayer;
pub type SpawnHandle = <Platform as PlatformInterface>::SpawnHandle;
// ========================
// 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 native_audio::{spawn, SpawnHandle};
#[cfg(feature = "web")]
pub use web::{spawn, SpawnHandle};
/// Compile-time assertion that CurrentPlatform implements Platform.
const _: () = {
+21 -12
View File
@@ -1,19 +1,22 @@
use crate::effects::{AudioProcessor, AudioProcessorSender, TransmitState};
use color_eyre::eyre::{eyre, Error};
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait as _};
use futures::io::{AsyncRead, AsyncWrite};
use std::mem::replace;
use std::sync::Arc;
use std::sync::Mutex;
use tracing::{error, info, warn};
pub trait ImpRead: AsyncRead + Unpin + Send + 'static {}
impl<T: AsyncRead + Unpin + Send + 'static> ImpRead for T {}
// =============
// Async runtime
// =============
pub use tokio::spawn;
pub type SpawnHandle = tokio::runtime::Handle;
pub trait ImpWrite: AsyncWrite + Unpin + Send + 'static {}
impl<T: AsyncWrite + Unpin + Send + 'static> ImpWrite for T {}
// ============
// Audio System
// ============
pub struct AudioSystem {
pub struct NativeAudioSystem {
output: cpal::Device,
input: cpal::Device,
processors: AudioProcessorSender,
@@ -52,13 +55,13 @@ fn encode_and_send(
type Buffer = Arc<Mutex<dasp_ring_buffer::Bounded<Vec<i16>>>>;
impl AudioSystem {
impl NativeAudioSystem {
pub async fn new() -> Result<Self, Error> {
// TODO
let host = cpal::default_host();
let name = host.id();
let processors = AudioProcessorSender::default();
Ok(AudioSystem {
Ok(NativeAudioSystem {
output: host
.default_output_device()
.ok_or(eyre!("no output devices from {name:?}"))?,
@@ -145,7 +148,7 @@ impl AudioSystem {
}
}
pub fn create_player(&mut self) -> Result<AudioPlayer, Error> {
pub fn create_player(&mut self) -> Result<NativeAudioPlayer, Error> {
let config = self.choose_config(self.output.supported_output_configs()?)?;
info!(
"creating player on {:?} with {:#?}",
@@ -183,7 +186,7 @@ impl AudioSystem {
)?
};
stream.play()?;
Ok(AudioPlayer {
Ok(NativeAudioPlayer {
decoder,
stream,
buffer,
@@ -192,14 +195,18 @@ impl AudioSystem {
}
}
pub struct AudioPlayer {
impl super::AudioSystemInterface for NativeAudioSystem {
type AudioPlayer = NativeAudioPlayer;
}
pub struct NativeAudioPlayer {
decoder: opus::Decoder,
stream: cpal::Stream,
buffer: Buffer,
tmp: Vec<i16>,
}
impl AudioPlayer {
impl NativeAudioPlayer {
pub fn play_opus(&mut self, payload: &[u8]) {
let len = match self.decoder.decode(payload, &mut self.tmp, false) {
Ok(l) => l,
@@ -221,3 +228,5 @@ impl AudioPlayer {
}
}
}
impl super::AudioPlayerInterface for NativeAudioPlayer {}
+22 -30
View File
@@ -38,14 +38,34 @@ use web_sys::WorkletOptions;
use web_sys::{console, window};
use web_sys::{AudioContext, AudioDataCopyToOptions};
pub use wasm_bindgen_futures::spawn_local as spawn;
pub trait ImpRead: AsyncRead + Unpin + 'static {}
impl<T: AsyncRead + Unpin + 'static> ImpRead for T {}
pub trait ImpWrite: AsyncWrite + Unpin + 'static {}
impl<T: AsyncWrite + Unpin + 'static> ImpWrite for T {}
// =============
// Async runtime
// =============
pub use wasm_bindgen_futures::spawn_local as spawn;
#[derive(Clone)]
pub struct SpawnHandle;
impl SpawnHandle {
pub fn spawn<F>(&self, future: F)
where
F: Future<Output = ()> + 'static,
{
wasm_bindgen_futures::spawn_local(future);
}
pub fn current() -> Self {
SpawnHandle
}
}
// ============================================================================
// Platform Struct
// ============================================================================
@@ -53,29 +73,8 @@ impl<T: AsyncWrite + Unpin + 'static> ImpWrite for T {}
/// Web platform implementation using WebTransport and Web Audio API.
pub struct WebPlatform;
// ============================================================================
// Trait Implementations
// ============================================================================
#[derive(Clone)]
pub struct WebSpawnHandle;
impl super::SpawnHandleInterface for WebSpawnHandle {
fn spawn<F>(&self, future: F)
where
F: Future<Output = ()> + 'static,
{
wasm_bindgen_futures::spawn_local(future);
}
fn current() -> Self {
WebSpawnHandle
}
}
impl super::PlatformInterface for WebPlatform {
type AudioSystem = WebAudioSystem;
type SpawnHandle = WebSpawnHandle;
fn init_logging() {
init_logging();
@@ -118,13 +117,6 @@ impl super::PlatformInterface for WebPlatform {
get_status(client).await
}
fn spawn<F>(future: F)
where
F: Future<Output = ()> + 'static,
{
wasm_bindgen_futures::spawn_local(future);
}
async fn sleep(duration: Duration) {
TimeoutFuture::new(duration.as_millis() as u32).await;
}