My first rust project that goes beyond todo-app or hangman.
I am trying to get a Audio Visualizer App going (FFT wave), but am a bit stuck on the start of my little learning project.
I cant make my mp3 play in the background without freezing the main thread.
Here is my code:
use iced::{
widget::{Button, Column, Text},
Application, Command,
};
use native_dialog::FileDialog;
use rodio::{Decoder, OutputStream, OutputStreamHandle, Sink};
use std::fs::File;
use std::path::PathBuf;
pub struct AudioVisualizer {
file_path: Option<PathBuf>,
audio_output: Option<OutputStreamHandle>,
audio_sink: Option<Sink>,
}
#[derive(Debug, Clone)]
pub enum Message {
OpenPressed,
PlayPressed,
}
impl Application for AudioVisualizer {
type Executor = iced::executor::Default;
type Message = Message;
type Flags = ();
type Theme = iced::Theme;
fn new(_flags: Self::Flags) -> (AudioVisualizer, Command<Self::Message>) {
let (_stream, stream_handle) = OutputStream::try_default().unwrap();
(
AudioVisualizer {
file_path: None,
audio_output: Some(stream_handle),
audio_sink: None,
},
Command::none(),
)
}
fn title(&self) -> String {
String::from("Audio Visualizer")
}
fn update(&mut self, _message: Self::Message) -> Command<Self::Message> {
match _message {
Message::OpenPressed => {
match FileDialog::new()
.add_filter("Audio Files", &["mp3"])
.show_open_single_file()
{
Ok(Some(path)) => {
println!("File selected: {:?}", path.file_name());
self.file_path = Some(path);
}
Ok(None) => {
self.file_path = None;
}
Err(err) => {
println!("File dialog error: {:?}", err);
self.file_path = None;
}
}
}
Message::PlayPressed => {
println!("Start pressed");
if let Some(path) = &self.file_path {
match File::open(path) {
Ok(file) => match Decoder::new(std::io::BufReader::new(file)) {
Ok(source) => {
let (_stream, stream_handle) = OutputStream::try_default().unwrap();
match Sink::try_new(&stream_handle) {
Ok(sink) => {
sink.append(source);
// FIXME keep ref to sink and stream_handle and let rodio stream in the background without blocking UI thread
// self.audio_sink = Some(sink);
// self.audio_output = Some(stream_handle);
sink.sleep_until_end()
}
Err(e) => println!("Error creating sink: {:?}", e),
}
}
Err(e) => println!("Error decoding audio: {:?}", e),
},
Err(e) => println!("Error opening file: {:?}", e),
}
}
}
}
Command::none()
}
fn view(&self) -> iced::Element<'_, Self::Message> {
let file_name = self
.file_path
.as_ref()
.and_then(|path| path.file_name())
.and_then(|os_str| os_str.to_str())
.map(|s| s.to_string())
.unwrap_or("-".to_string());
let open_button = Button::new(Text::new("Open")).on_press(Message::OpenPressed);
let file_text = Text::new(file_name);
let play_button = Button::new(Text::new("Play")).on_press(Message::PlayPressed);
Column::new()
.push(open_button)
.push(file_text)
.push(play_button)
.into()
}
fn theme(&self) -> Self::Theme {
Self::Theme::Dark
}
}
This is due to rodio::sink::Sink::sleep_until_end() ofc, but for me that was the only way to get the mp3 playing at all.
According to this post on r/rust_gamedev it should work without spawning an extra thread.
I use:
iced = "0.5.2"
native-dialog = "0.7.0"
rodio = "0.17.3"
(OS Windows 10)
Could someone point me towards my mistake?