Add cpal player framework
This commit is contained in:
parent
b6ca40e184
commit
1821eeba14
|
@ -12,9 +12,9 @@ yew = "0.17"
|
||||||
#yew = { path = "/home/rob/dev/yew/yew" }
|
#yew = { path = "/home/rob/dev/yew/yew" }
|
||||||
yewtil = "0.3.1"
|
yewtil = "0.3.1"
|
||||||
wasm-bindgen = "0.2"
|
wasm-bindgen = "0.2"
|
||||||
gloo-events = "0.1.0"
|
|
||||||
js-sys = "0.3.44"
|
js-sys = "0.3.44"
|
||||||
futures = "0.3.5"
|
futures = "0.3.5"
|
||||||
|
cpal = { version = "0.12.1", features = ["wasm-bindgen"] }
|
||||||
wasm-bindgen-futures = "0.4"
|
wasm-bindgen-futures = "0.4"
|
||||||
console_error_panic_hook = "0.1.6"
|
console_error_panic_hook = "0.1.6"
|
||||||
|
|
||||||
|
@ -27,3 +27,8 @@ features = [
|
||||||
"HtmlCanvasElement",
|
"HtmlCanvasElement",
|
||||||
"CanvasRenderingContext2d",
|
"CanvasRenderingContext2d",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
lto = true
|
||||||
|
|
||||||
|
# TODO: add wee_alloc
|
||||||
|
|
3
Makefile
3
Makefile
|
@ -1,8 +1,5 @@
|
||||||
# Based on https://raw.githubusercontent.com/saschagrunert/webapp.rs/master/Makefile
|
# Based on https://raw.githubusercontent.com/saschagrunert/webapp.rs/master/Makefile
|
||||||
|
|
||||||
GENERAL_ARGS = --release
|
|
||||||
FRONTEND_ARGS = $(GENERAL_ARGS)
|
|
||||||
|
|
||||||
.PHONY: \
|
.PHONY: \
|
||||||
build \
|
build \
|
||||||
run
|
run
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use super::player::Player;
|
||||||
use crate::agents::audio_agent::{self, AudioAgent};
|
use crate::agents::audio_agent::{self, AudioAgent};
|
||||||
use web_sys::HtmlInputElement;
|
use web_sys::HtmlInputElement;
|
||||||
use yew::agent::Dispatched;
|
use yew::agent::Dispatched;
|
||||||
|
@ -52,6 +53,7 @@ impl Component for Controls {
|
||||||
<input ref=self.file_input.clone() type="file"/>
|
<input ref=self.file_input.clone() type="file"/>
|
||||||
</label>
|
</label>
|
||||||
<button onclick=self.link.callback(move |_| Msg::SubmitForm)>{"Open file"}</button>
|
<button onclick=self.link.callback(move |_| Msg::SubmitForm)>{"Open file"}</button>
|
||||||
|
<Player/>
|
||||||
</section>
|
</section>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,10 +10,13 @@ mod app;
|
||||||
mod canvas;
|
mod canvas;
|
||||||
mod controls;
|
mod controls;
|
||||||
mod home;
|
mod home;
|
||||||
|
mod player;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
#[wasm_bindgen(start)]
|
#[wasm_bindgen(start)]
|
||||||
pub fn run_app() {
|
pub fn run_app() {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
panic::set_hook(Box::new(console_error_panic_hook::hook));
|
panic::set_hook(Box::new(console_error_panic_hook::hook));
|
||||||
|
|
||||||
App::<app::App>::new().mount_to_body();
|
App::<app::App>::new().mount_to_body();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue