use js_sys::Uint8Array; use std::collections::HashSet; use std::sync::Arc; use wasm_bindgen::JsValue; use wasm_bindgen_futures::JsFuture; use web_sys::console; use web_sys::{AudioContext, File}; use yew::services::reader::{FileChunk, ReaderTask}; use yew::services::{ConsoleService as Console, ReaderService}; use yew::worker::*; use yewtil::future::LinkFuture; const FILE_LOAD_CHUNK_SIZE: usize = 4096; #[derive(Debug)] pub struct AudioData { pub buffers: Vec>, pub num_channels: u32, pub sample_rate: f32, } #[derive(Debug)] pub struct AudioAgent { link: AgentLink, reader_service: ReaderService, reader_task: Option, audio_context: AudioContext, bytes_buffer: Vec, subscribers: HashSet, } #[derive(Debug)] pub enum Request { LoadSamplesFromFile(File), } #[derive(Debug)] pub enum Msg { FileProgress(Option), AudioDecoded(JsValue), AudioDecodingFailed(JsValue), } // Convert the result of AudioContext.decode_audio_data(): impl From> for Msg { fn from(item: Result) -> Self { match item { Ok(samples) => { console::log_2(&"Got js value".into(), &samples); Msg::AudioDecoded(samples) } Err(e) => Msg::AudioDecodingFailed(e), } } } impl Agent for AudioAgent { type Reach = Context; type Message = Msg; type Input = Request; type Output = Result, String>; fn create(link: AgentLink) -> Self { // TODO: where should the AudioContext be initialized and stored? Self { link, reader_service: ReaderService::new(), reader_task: None, audio_context: AudioContext::new().unwrap(), bytes_buffer: vec![], subscribers: HashSet::new(), } } fn update(&mut self, msg: Self::Message) { match msg { Msg::FileProgress(Some(FileChunk::DataChunk { data, progress })) => { self.handle_file_progress(data, progress) } Msg::FileProgress(Some(FileChunk::Finished)) => { if let Err(e) = self.handle_file_loaded() { Console::error(&e); } } Msg::AudioDecoded(samples) => { let audio_buffer = web_sys::AudioBuffer::from(samples); let num_channels = audio_buffer.number_of_channels(); let sample_rate = audio_buffer.sample_rate(); let buffers: Vec> = (0..num_channels) .map(|i| audio_buffer.get_channel_data(i).unwrap()) .collect(); let audio_data = AudioData { buffers, num_channels, sample_rate, }; let audio_data = Arc::new(audio_data); for subscriber in self.subscribers.iter() { self.link.respond(*subscriber, Ok(audio_data.clone())); } } Msg::AudioDecodingFailed(err) => { Console::error(&format!("Error decoding: {:?}", err)); } _ => (), }; } fn handle_input(&mut self, input: Self::Input, _: HandlerId) { match input { Request::LoadSamplesFromFile(file) => self.load_samples_from_file(file), }; } fn connected(&mut self, id: HandlerId) { if !id.is_respondable() { return; } Console::log(&format!("Subscriber connected: {:?}", id)); self.subscribers.insert(id); } fn disconnected(&mut self, id: HandlerId) { self.subscribers.remove(&id); } } impl AudioAgent { fn load_samples_from_file(&mut self, file: File) { self.bytes_buffer = Vec::with_capacity(file.size() as usize); let cb = self.link.callback(Msg::FileProgress); // TODO: error handling let task = self .reader_service .read_file_by_chunks(file, cb, FILE_LOAD_CHUNK_SIZE) .map_err(|e| e.to_string()) .unwrap(); self.reader_task = Some(task); } fn handle_file_progress(&mut self, mut data: Vec, _progress: f32) { self.bytes_buffer.append(&mut data); } fn handle_file_loaded(&mut self) -> Result<(), String> { self.reader_task = None; let audio_data = Uint8Array::from(&self.bytes_buffer[..]); match self.audio_context.decode_audio_data(&audio_data.buffer()) { Ok(promise) => { self.link.send_future(JsFuture::from(promise)); Ok(()) } Err(e) => Err(format!("Error from decode_audio_data: {:?}", e)), } } }