From 1821eeba14106a6e6eb0125d687a58f0eb3bc925 Mon Sep 17 00:00:00 2001 From: Rob Watson Date: Wed, 9 Sep 2020 17:41:40 +0200 Subject: [PATCH] Add cpal player framework --- Cargo.toml | 7 ++- Makefile | 3 -- src/controls.rs | 2 + src/lib.rs | 3 ++ src/player.rs | 114 ++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 125 insertions(+), 4 deletions(-) create mode 100644 src/player.rs diff --git a/Cargo.toml b/Cargo.toml index 5b046eb..23d486b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,9 +12,9 @@ yew = "0.17" #yew = { path = "/home/rob/dev/yew/yew" } yewtil = "0.3.1" wasm-bindgen = "0.2" -gloo-events = "0.1.0" js-sys = "0.3.44" futures = "0.3.5" +cpal = { version = "0.12.1", features = ["wasm-bindgen"] } wasm-bindgen-futures = "0.4" console_error_panic_hook = "0.1.6" @@ -27,3 +27,8 @@ features = [ "HtmlCanvasElement", "CanvasRenderingContext2d", ] + +[profile.release] +lto = true + +# TODO: add wee_alloc diff --git a/Makefile b/Makefile index fb16d85..96d9ed5 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,5 @@ # Based on https://raw.githubusercontent.com/saschagrunert/webapp.rs/master/Makefile -GENERAL_ARGS = --release -FRONTEND_ARGS = $(GENERAL_ARGS) - .PHONY: \ build \ run diff --git a/src/controls.rs b/src/controls.rs index d493d70..9f684f7 100644 --- a/src/controls.rs +++ b/src/controls.rs @@ -1,3 +1,4 @@ +use super::player::Player; use crate::agents::audio_agent::{self, AudioAgent}; use web_sys::HtmlInputElement; use yew::agent::Dispatched; @@ -52,6 +53,7 @@ impl Component for Controls { + } } diff --git a/src/lib.rs b/src/lib.rs index a2d70af..829180d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,10 +10,13 @@ mod app; mod canvas; mod controls; mod home; +mod player; mod utils; #[wasm_bindgen(start)] pub fn run_app() { + #[cfg(debug_assertions)] panic::set_hook(Box::new(console_error_panic_hook::hook)); + App::::new().mount_to_body(); } diff --git a/src/player.rs b/src/player.rs new file mode 100644 index 0000000..a0a405e --- /dev/null +++ b/src/player.rs @@ -0,0 +1,114 @@ +use crate::agents::audio_agent::{self, AudioAgent}; +use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; +use cpal::{Device, SampleFormat, SampleRate, Stream, StreamConfig}; +use yew::agent::Dispatcher; +use yew::prelude::*; +use yew::services::ConsoleService; + +pub enum Status { + Stopped, + Playing, +} + +pub struct Player { + link: ComponentLink, + status: Status, + stream: Option, + audio_agent: Dispatcher, +} + +pub enum Msg { + Play, +} + +impl Component for Player { + type Message = Msg; + type Properties = (); + + fn create(_: Self::Properties, link: ComponentLink) -> Self { + Self { + link, + status: Status::Stopped, + stream: None, + audio_agent: AudioAgent::dispatcher(), + } + } + + fn rendered(&mut self, first_render: bool) {} + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + match msg { + Msg::Play => self.handle_play_button_clicked(), + } + } + + fn change(&mut self, _: Self::Properties) -> ShouldRender { + false + } + + fn view(&self) -> Html { + html! { +
+ +
+ } + } +} + +impl Player { + fn play(&mut self) { + 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::(&device, &config.into()), + SampleFormat::I16 => Player::run::(&device, &config.into()), + SampleFormat::U16 => Player::run::(&device, &config.into()), + }; + + self.stream = Some(stream); + } + + fn run(device: &Device, config: &StreamConfig) -> Stream + where + T: cpal::Sample, + { + let err_fn = |err| ConsoleService::warn(&format!("an error occurred on stream: {}", err)); + + let stream = device + .build_output_stream(config, move |data: &mut [T], _| {}, err_fn) + .unwrap(); + + stream.play().unwrap(); + stream + } + + fn handle_play_button_clicked(&mut self) -> ShouldRender { + match self.status { + Status::Stopped => { + self.status = Status::Playing; + self.play(); + } + Status::Playing => self.status = Status::Stopped, + } + true + } + + fn button_text(&self) -> &str { + if let Status::Stopped = self.status { + "Play" + } else { + "Pause" + } + } +}