Introduce Arc-based sharing of audio data

This commit is contained in:
Rob Watson 2020-09-09 22:06:50 +02:00
parent 1821eeba14
commit 62fda0fe6c
2 changed files with 55 additions and 28 deletions

View File

@ -1,6 +1,6 @@
use js_sys::Uint8Array; use js_sys::Uint8Array;
use std::collections::HashSet; use std::collections::HashSet;
use std::rc::Rc; use std::sync::Arc;
use wasm_bindgen::JsValue; use wasm_bindgen::JsValue;
use wasm_bindgen_futures::JsFuture; use wasm_bindgen_futures::JsFuture;
use web_sys::console; use web_sys::console;
@ -12,7 +12,7 @@ use yewtil::future::LinkFuture;
const FILE_LOAD_CHUNK_SIZE: usize = 4096; const FILE_LOAD_CHUNK_SIZE: usize = 4096;
pub type AudioData = Rc<web_sys::AudioBuffer>; pub type AudioData = Arc<web_sys::AudioBuffer>;
#[derive(Debug)] #[derive(Debug)]
pub struct AudioAgent { pub struct AudioAgent {
@ -79,7 +79,7 @@ impl Agent for AudioAgent {
} }
Msg::AudioDecoded(samples) => { Msg::AudioDecoded(samples) => {
let audio_buffer = web_sys::AudioBuffer::from(samples); let audio_buffer = web_sys::AudioBuffer::from(samples);
let audio_data = Rc::new(audio_buffer); let audio_data = Arc::new(audio_buffer);
for subscriber in self.subscribers.iter() { for subscriber in self.subscribers.iter() {
self.link.respond(*subscriber, Ok(audio_data.clone())); self.link.respond(*subscriber, Ok(audio_data.clone()));

View File

@ -1,4 +1,4 @@
use crate::agents::audio_agent::{self, AudioAgent}; use crate::agents::audio_agent::{self, AudioAgent, AudioData};
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use cpal::{Device, SampleFormat, SampleRate, Stream, StreamConfig}; use cpal::{Device, SampleFormat, SampleRate, Stream, StreamConfig};
use yew::agent::Dispatcher; use yew::agent::Dispatcher;
@ -14,11 +14,13 @@ pub struct Player {
link: ComponentLink<Self>, link: ComponentLink<Self>,
status: Status, status: Status,
stream: Option<Stream>, stream: Option<Stream>,
audio_agent: Dispatcher<AudioAgent>, audio_agent: Box<dyn Bridge<AudioAgent>>,
audio_data: Option<AudioData>,
} }
pub enum Msg { pub enum Msg {
Play, Play,
AudioAgentMessage(Result<AudioData, String>),
} }
impl Component for Player { impl Component for Player {
@ -26,11 +28,14 @@ impl Component for Player {
type Properties = (); type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self { fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
let cb = link.callback(Msg::AudioAgentMessage);
Self { Self {
link, link,
status: Status::Stopped, status: Status::Stopped,
stream: None, stream: None,
audio_agent: AudioAgent::dispatcher(), audio_agent: AudioAgent::bridge(cb),
audio_data: None,
} }
} }
@ -39,6 +44,8 @@ impl Component for Player {
fn update(&mut self, msg: Self::Message) -> ShouldRender { fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg { match msg {
Msg::Play => self.handle_play_button_clicked(), Msg::Play => self.handle_play_button_clicked(),
Msg::AudioAgentMessage(Ok(audio_data)) => self.handle_samples_loaded(audio_data),
Msg::AudioAgentMessage(Err(err)) => self.handle_samples_loaded_error(&err),
} }
} }
@ -56,30 +63,50 @@ impl Component for Player {
} }
impl Player { impl Player {
fn play(&mut self) { fn handle_samples_loaded(&mut self, audio_data: AudioData) -> ShouldRender {
let host = cpal::default_host(); ConsoleService::log("Player: samples loaded");
let device = host.default_output_device().unwrap(); self.audio_data = Some(audio_data);
true
// TODO: improve
let config = device
.supported_output_configs()
.unwrap()
.nth(1)
.unwrap()
.with_sample_rate(SampleRate(44100));
ConsoleService::log(&format!("Using output config: {:?}", config));
let stream = match config.sample_format() {
SampleFormat::F32 => Player::run::<f32>(&device, &config.into()),
SampleFormat::I16 => Player::run::<i16>(&device, &config.into()),
SampleFormat::U16 => Player::run::<u16>(&device, &config.into()),
};
self.stream = Some(stream);
} }
fn run<T>(device: &Device, config: &StreamConfig) -> Stream fn handle_samples_loaded_error(&mut self, err: &str) -> ShouldRender {
ConsoleService::log(&format!("Player: error loading samples: {:?}", err));
self.audio_data = None;
false
}
fn play(&mut self) {
if let Some(audio_data) = &self.audio_data {
let host = cpal::default_host();
let device = host.default_output_device().unwrap();
// TODO: improve
let config = device
.supported_output_configs()
.unwrap()
.nth(1)
.unwrap()
.with_sample_rate(SampleRate(44100));
ConsoleService::log(&format!("Using output config: {:?}", config));
let stream = match config.sample_format() {
SampleFormat::F32 => {
Player::run::<f32>(&device, &config.into(), audio_data.clone())
}
SampleFormat::I16 => {
Player::run::<i16>(&device, &config.into(), audio_data.clone())
}
SampleFormat::U16 => {
Player::run::<u16>(&device, &config.into(), audio_data.clone())
}
};
self.stream = Some(stream);
}
}
fn run<T>(device: &Device, config: &StreamConfig, audio_data: AudioData) -> Stream
where where
T: cpal::Sample, T: cpal::Sample,
{ {