Add cpal player framework

This commit is contained in:
Rob Watson 2020-09-09 17:41:40 +02:00
parent b6ca40e184
commit 1821eeba14
5 changed files with 125 additions and 4 deletions

View File

@ -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

View File

@ -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

View File

@ -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 {
<input ref=self.file_input.clone() type="file"/>
</label>
<button onclick=self.link.callback(move |_| Msg::SubmitForm)>{"Open file"}</button>
<Player/>
</section>
}
}

View File

@ -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::<app::App>::new().mount_to_body();
}

114
src/player.rs Normal file
View File

@ -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<Self>,
status: Status,
stream: Option<Stream>,
audio_agent: Dispatcher<AudioAgent>,
}
pub enum Msg {
Play,
}
impl Component for Player {
type Message = Msg;
type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> 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! {
<div>
<button onclick=self.link.callback(move |_| Msg::Play)>{self.button_text()}</button>
</div>
}
}
}
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::<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
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"
}
}
}