all platform traits implemented & dumb async runtime imports
This commit is contained in:
+1
-1
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
+24
-93
@@ -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,45 +53,8 @@ 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();
|
||||
}
|
||||
|
||||
fn request_permissions() {
|
||||
request_recording_permission();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -148,13 +69,23 @@ pub fn init_logging() {
|
||||
.init();
|
||||
}
|
||||
|
||||
#[cfg(feature = "mobile")]
|
||||
pub fn request_permissions() {
|
||||
fn request_permissions() {
|
||||
request_recording_permission();
|
||||
}
|
||||
|
||||
async fn sleep(duration: Duration) {
|
||||
tokio::time::sleep(duration).await;
|
||||
}
|
||||
}
|
||||
|
||||
#[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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user