142 lines
3.9 KiB
Rust
142 lines
3.9 KiB
Rust
use crate::agents::audio_agent::{self, AudioAgent, AudioData};
|
|
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: Box<dyn Bridge<AudioAgent>>,
|
|
audio_data: Option<AudioData>,
|
|
}
|
|
|
|
pub enum Msg {
|
|
Play,
|
|
AudioAgentMessage(Result<AudioData, String>),
|
|
}
|
|
|
|
impl Component for Player {
|
|
type Message = Msg;
|
|
type Properties = ();
|
|
|
|
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
|
|
let cb = link.callback(Msg::AudioAgentMessage);
|
|
|
|
Self {
|
|
link,
|
|
status: Status::Stopped,
|
|
stream: None,
|
|
audio_agent: AudioAgent::bridge(cb),
|
|
audio_data: None,
|
|
}
|
|
}
|
|
|
|
fn rendered(&mut self, first_render: bool) {}
|
|
|
|
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
|
match msg {
|
|
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),
|
|
}
|
|
}
|
|
|
|
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 handle_samples_loaded(&mut self, audio_data: AudioData) -> ShouldRender {
|
|
ConsoleService::log("Player: samples loaded");
|
|
self.audio_data = Some(audio_data);
|
|
true
|
|
}
|
|
|
|
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
|
|
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"
|
|
}
|
|
}
|
|
}
|