move audio stuff into trait

This commit is contained in:
2026-01-24 21:19:58 -07:00
committed by Liam Warfield
parent 056a673bc0
commit 09985e6031
4 changed files with 63 additions and 50 deletions
+26 -5
View File
@@ -2,8 +2,9 @@
//! //!
//! This module defines traits that each platform (web, desktop, mobile) must implement. //! This module defines traits that each platform (web, desktop, mobile) must implement.
//! The traits make the platform boundary explicit and provide compile-time verification. //! The traits make the platform boundary explicit and provide compile-time verification.
#![allow(async_fn_in_trait)]
use crate::app::Command; use crate::{app::Command, effects::AudioProcessor};
use color_eyre::eyre::Error; use color_eyre::eyre::Error;
use dioxus::hooks::UnboundedReceiver; use dioxus::hooks::UnboundedReceiver;
use mumble_web2_common::{ClientConfig, ServerStatus}; use mumble_web2_common::{ClientConfig, ServerStatus};
@@ -14,11 +15,31 @@ use std::time::Duration;
// Trait Definitions // Trait Definitions
// ============================================================================ // ============================================================================
pub trait AudioSystemInterface { pub trait AudioSystemInterface: Sized {
type AudioPlayer: AudioPlayerInterface; type AudioPlayer: AudioPlayerInterface;
/// Initialize the audio system, including relevant state
async fn new() -> Result<Self, Error>;
/// 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<u8>, bool) + Send + 'static,
) -> Result<(), Error>;
/// Begin playback of an audio stream, returning an object that can be passed
/// with opus frames.
fn create_player(&mut self) -> Result<Self::AudioPlayer, Error>;
} }
pub trait AudioPlayerInterface {} pub trait AudioPlayerInterface {
/// Playback an opus frame.
fn play_opus(&mut self, payload: &[u8]);
}
/// This is the main trait that each platform must implement. It combines all /// This is the main trait that each platform must implement. It combines all
/// platform-specific functionality into a single interface, providing compile-time /// platform-specific functionality into a single interface, providing compile-time
@@ -72,9 +93,9 @@ pub trait PlatformInterface {
pub mod web; pub mod web;
#[cfg(any(feature = "desktop", feature = "mobile"))] #[cfg(any(feature = "desktop", feature = "mobile"))]
mod connect; pub mod connect;
#[cfg(any(feature = "desktop", feature = "mobile"))] #[cfg(any(feature = "desktop", feature = "mobile"))]
mod native_audio; pub mod native_audio;
#[cfg(feature = "desktop")] #[cfg(feature = "desktop")]
pub mod desktop; pub mod desktop;
+29 -31
View File
@@ -56,27 +56,6 @@ fn encode_and_send(
type Buffer = Arc<Mutex<dasp_ring_buffer::Bounded<Vec<i16>>>>; type Buffer = Arc<Mutex<dasp_ring_buffer::Bounded<Vec<i16>>>>;
impl NativeAudioSystem { impl NativeAudioSystem {
pub async fn new() -> Result<Self, Error> {
// TODO
let host = cpal::default_host();
let name = host.id();
let processors = AudioProcessorSender::default();
Ok(NativeAudioSystem {
output: host
.default_output_device()
.ok_or(eyre!("no output devices from {name:?}"))?,
input: host
.default_input_device()
.ok_or(eyre!("no input devices from {name:?}"))?,
processors,
recording_stream: None,
})
}
pub fn set_processor(&self, processor: AudioProcessor) {
self.processors.store(Some(processor))
}
fn choose_config( fn choose_config(
&self, &self,
configs: impl Iterator<Item = cpal::SupportedStreamConfigRange>, configs: impl Iterator<Item = cpal::SupportedStreamConfigRange>,
@@ -106,8 +85,33 @@ impl NativeAudioSystem {
.cloned() .cloned()
.ok_or(eyre!("no supported stream configs")) .ok_or(eyre!("no supported stream configs"))
} }
}
pub fn start_recording( impl super::AudioSystemInterface for NativeAudioSystem {
type AudioPlayer = NativeAudioPlayer;
async fn new() -> Result<Self, Error> {
// TODO
let host = cpal::default_host();
let name = host.id();
let processors = AudioProcessorSender::default();
Ok(NativeAudioSystem {
output: host
.default_output_device()
.ok_or(eyre!("no output devices from {name:?}"))?,
input: host
.default_input_device()
.ok_or(eyre!("no input devices from {name:?}"))?,
processors,
recording_stream: None,
})
}
fn set_processor(&self, processor: AudioProcessor) {
self.processors.store(Some(processor))
}
fn start_recording(
&mut self, &mut self,
mut each: impl FnMut(Vec<u8>, bool) + Send + 'static, mut each: impl FnMut(Vec<u8>, bool) + Send + 'static,
) -> Result<(), Error> { ) -> Result<(), Error> {
@@ -148,7 +152,7 @@ impl NativeAudioSystem {
} }
} }
pub fn create_player(&mut self) -> Result<NativeAudioPlayer, Error> { fn create_player(&mut self) -> Result<NativeAudioPlayer, Error> {
let config = self.choose_config(self.output.supported_output_configs()?)?; let config = self.choose_config(self.output.supported_output_configs()?)?;
info!( info!(
"creating player on {:?} with {:#?}", "creating player on {:?} with {:#?}",
@@ -195,10 +199,6 @@ impl NativeAudioSystem {
} }
} }
impl super::AudioSystemInterface for NativeAudioSystem {
type AudioPlayer = NativeAudioPlayer;
}
pub struct NativeAudioPlayer { pub struct NativeAudioPlayer {
decoder: opus::Decoder, decoder: opus::Decoder,
stream: cpal::Stream, stream: cpal::Stream,
@@ -206,8 +206,8 @@ pub struct NativeAudioPlayer {
tmp: Vec<i16>, tmp: Vec<i16>,
} }
impl NativeAudioPlayer { impl super::AudioPlayerInterface for NativeAudioPlayer {
pub fn play_opus(&mut self, payload: &[u8]) { fn play_opus(&mut self, payload: &[u8]) {
let len = match self.decoder.decode(payload, &mut self.tmp, false) { let len = match self.decoder.decode(payload, &mut self.tmp, false) {
Ok(l) => l, Ok(l) => l,
Err(e) => { Err(e) => {
@@ -228,5 +228,3 @@ impl NativeAudioPlayer {
} }
} }
} }
impl super::AudioPlayerInterface for NativeAudioPlayer {}
+6 -13
View File
@@ -177,10 +177,8 @@ async fn attach_worklet(audio_context: &AudioContext) -> Result<(), Error> {
impl super::AudioSystemInterface for WebAudioSystem { impl super::AudioSystemInterface for WebAudioSystem {
type AudioPlayer = WebAudioPlayer; type AudioPlayer = WebAudioPlayer;
}
impl WebAudioSystem { async fn new() -> Result<Self, Error> {
pub async fn new() -> Result<Self, Error> {
// Create MediaStreams to playback decoded audio // Create MediaStreams to playback decoded audio
// The audio context is used to reproduce audio. // The audio context is used to reproduce audio.
let webctx = configure_audio_context(); let webctx = configure_audio_context();
@@ -191,14 +189,11 @@ impl WebAudioSystem {
Ok(WebAudioSystem { webctx, processors }) Ok(WebAudioSystem { webctx, processors })
} }
pub fn set_processor(&self, processor: AudioProcessor) { fn set_processor(&self, processor: AudioProcessor) {
self.processors.store(Some(processor)) self.processors.store(Some(processor))
} }
pub fn start_recording( fn start_recording(&mut self, each: impl FnMut(Vec<u8>, bool) + 'static) -> Result<(), Error> {
&mut self,
each: impl FnMut(Vec<u8>, bool) + 'static,
) -> Result<(), Error> {
let audio_context_worklet = self.webctx.clone(); let audio_context_worklet = self.webctx.clone();
let processors = self.processors.clone(); let processors = self.processors.clone();
spawn(async move { spawn(async move {
@@ -210,7 +205,7 @@ impl WebAudioSystem {
Ok(()) Ok(())
} }
pub fn create_player(&mut self) -> Result<WebAudioPlayer, Error> { fn create_player(&mut self) -> Result<WebAudioPlayer, Error> {
let sink_node = AudioWorkletNode::new(&self.webctx, "rust_speaker_worklet").ey()?; let sink_node = AudioWorkletNode::new(&self.webctx, "rust_speaker_worklet").ey()?;
// Connect worklet to destination // Connect worklet to destination
@@ -269,10 +264,8 @@ impl WebAudioSystem {
pub struct WebAudioPlayer(AudioDecoder); pub struct WebAudioPlayer(AudioDecoder);
impl super::AudioPlayerInterface for WebAudioPlayer {} impl super::AudioPlayerInterface for WebAudioPlayer {
fn play_opus(&mut self, payload: &[u8]) {
impl WebAudioPlayer {
pub fn play_opus(&mut self, payload: &[u8]) {
let js_audio_payload = Uint8Array::from(payload); let js_audio_payload = Uint8Array::from(payload);
let _ = self.0.decode( let _ = self.0.decode(
&EncodedAudioChunk::new(&EncodedAudioChunkInit::new( &EncodedAudioChunk::new(&EncodedAudioChunkInit::new(
+2 -1
View File
@@ -29,7 +29,8 @@ use tracing::info;
use crate::effects::AudioProcessor; use crate::effects::AudioProcessor;
use crate::imp::{ use crate::imp::{
AudioPlayer, AudioSystem, AudioSystemInterface as _, Platform, PlatformInterface as _, AudioPlayer, AudioPlayerInterface as _, AudioSystem, AudioSystemInterface as _, Platform,
PlatformInterface as _,
}; };
pub mod app; pub mod app;