Playing Audio with rodio blocks UI thread

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?

According to the docs if you drop a Sink, the audio will stop. You either need to keep the Sink alive until it is finished playing the sound, or call detach() on it to prevent this stopping behavior.

I tried that with the commented out code:

                                        // self.audio_sink = Some(sink);
                                        // self.audio_output = Some(stream_handle);

and then ofc not calling sink.sleep_until_end() but it does not play for me then

I ended up creating a separate audio thread to manage my sink.

the loop is not quite as reactive as I would like but I can probably fix that with tokio or async-std later

it gets the job done for now

        thread::spawn(move || {
            let (_stream, stream_handle) = OutputStream::try_default().unwrap();
            let sink = Sink::try_new(&stream_handle).unwrap();

            loop {
                if let Ok(command) = receiver.try_recv() {
                    process_audio_command(command, &sink);
                }
                thread::sleep(std::time::Duration::from_millis(100));
            }
        });

fn process_audio_command(command: AudioCommand, sink: &Sink) {
    match command {
        AudioCommand::Play(path) => {
            if let Ok(file) = File::open(path) {
                if let Ok(source) = Decoder::new(BufReader::new(file)) {
                    sink.append(source);
                }
            }
        }
        AudioCommand::Stop => {
            sink.stop();
        }
    }
}

feel free to give constructive feedback for my noobish rust adventure

I believe I just use play_raw to avoid the issue