[GH-ISSUE #1515] Spirc task not working in spawn task? #685

Closed
opened 2026-02-27 19:31:57 +03:00 by kerem · 7 comments
Owner

Originally created by @GChalony on GitHub (Jul 9, 2025).
Original GitHub issue: https://github.com/librespot-org/librespot/issues/1515

I'm using librespot as a library for a player I've developping (which reads NFC tags and plays the corresponding music). This works fine, but I tried to refactor the code so that I can control the playback with buttons on the box. I moved the logic to a tokio task, and nothing happens anymore. The player is not playing any music, nor pausing / skipping to the next song.

To make it clearer, here is the code that works

fn main() {
    let mut reader = nfc_reader::Pn532Nfc::new().expect("Couldn't setup PN532");
    let mut music_player = MusicImpl::new().await.expect("Couldn't create music player");

    loop {
        info!("Waiting for tag");
        let tag = reader.read().await.expect("Couldn't read tag");

        info!("Reading music {tag}");
        music_player.play(&tag);

        info!("Waiting for removal");
        reader.wait_for_removal().await;

        info!("Pausing music");
        music_player.pause();
    }
}

And here is what I have tried:

fn main() {
    let mut reader = nfc_reader::Pn532Nfc::new().expect("Couldn't setup PN532");
    let mut music_player = MusicImpl::new().await.expect("Couldn't create music player");

    // Spawn music player task
    let (button_tx, mut button_rx) = mpsc::channel();
    
    tokio::spawn(async move {
        info!("Created music player");

        music_player.play("spotify:album:6Xvc1TfpVEhDeHhmTQEtp0").expect("Couldn't play");    // This works, the album starts playing

        while let Ok(request) = button_rx.recv() {
            info!("Received music request {request:?}");                    // Requests are correctly received
            match request {
                MusicRequest::Next => music_player.next(),
                MusicRequest::Previous => music_player.prev(),
                MusicRequest::PlayPause => music_player.play_pause(),
                MusicRequest::Pause => music_player.pause(),
                MusicRequest::PlayUri(uri) => music_player.play(&uri).expect("Couldn't play")      // But this doesn't seem to do anything
            }
        }
    });


    loop {
        info!("Waiting for tag");
        let tag = reader.read().await.expect("Couldn't read tag");

        info!("Reading music {tag}");
        button_tx.send(MusicRequest::PlayUri(tag));
        
        info!("Waiting for removal");
        reader.wait_for_removal().await;

        info!("Pausing music");
        button_tx.send(MusicRequest::Pause);
    }
}

In the background, the music_player is just delegating to a Spirc instance (calling load, next, play_pause). The spirc is created in MusicPlayer::new(), and the spirc_task is spawn there too. I'm really confused, but I'm thinking that the spirc might not be working in this context ? Do you have any idea ?

Thanks a lot !

Originally created by @GChalony on GitHub (Jul 9, 2025). Original GitHub issue: https://github.com/librespot-org/librespot/issues/1515 I'm using librespot as a library for a player I've developping (which reads NFC tags and plays the corresponding music). This works fine, but I tried to refactor the code so that I can control the playback with buttons on the box. I moved the logic to a `tokio` task, and nothing happens anymore. The player is not playing any music, nor pausing / skipping to the next song. To make it clearer, here is the code that works ```rust fn main() { let mut reader = nfc_reader::Pn532Nfc::new().expect("Couldn't setup PN532"); let mut music_player = MusicImpl::new().await.expect("Couldn't create music player"); loop { info!("Waiting for tag"); let tag = reader.read().await.expect("Couldn't read tag"); info!("Reading music {tag}"); music_player.play(&tag); info!("Waiting for removal"); reader.wait_for_removal().await; info!("Pausing music"); music_player.pause(); } } ``` And here is what I have tried: ```rust fn main() { let mut reader = nfc_reader::Pn532Nfc::new().expect("Couldn't setup PN532"); let mut music_player = MusicImpl::new().await.expect("Couldn't create music player"); // Spawn music player task let (button_tx, mut button_rx) = mpsc::channel(); tokio::spawn(async move { info!("Created music player"); music_player.play("spotify:album:6Xvc1TfpVEhDeHhmTQEtp0").expect("Couldn't play"); // This works, the album starts playing while let Ok(request) = button_rx.recv() { info!("Received music request {request:?}"); // Requests are correctly received match request { MusicRequest::Next => music_player.next(), MusicRequest::Previous => music_player.prev(), MusicRequest::PlayPause => music_player.play_pause(), MusicRequest::Pause => music_player.pause(), MusicRequest::PlayUri(uri) => music_player.play(&uri).expect("Couldn't play") // But this doesn't seem to do anything } } }); loop { info!("Waiting for tag"); let tag = reader.read().await.expect("Couldn't read tag"); info!("Reading music {tag}"); button_tx.send(MusicRequest::PlayUri(tag)); info!("Waiting for removal"); reader.wait_for_removal().await; info!("Pausing music"); button_tx.send(MusicRequest::Pause); } } ``` In the background, the `music_player` is just delegating to a Spirc instance (calling `load`, `next`, `play_pause`). The `spirc` is created in `MusicPlayer::new()`, and the `spirc_task` is spawn there too. I'm really confused, but I'm thinking that the spirc might not be working in this context ? Do you have any idea ? Thanks a lot !
kerem closed this issue 2026-02-27 19:31:57 +03:00
Author
Owner

@photovoltex commented on GitHub (Jul 10, 2025):

Please provide the code part where you spawn the spirc_task. Without it, it's just guessing in the dark which wouldn't be much of a help.

<!-- gh-comment-id:3058732969 --> @photovoltex commented on GitHub (Jul 10, 2025): Please provide the code part where you spawn the `spirc_task`. Without it, it's just guessing in the dark which wouldn't be much of a help.
Author
Owner

@GChalony commented on GitHub (Jul 10, 2025):

Sure, here is the constructor:


impl MusicImpl {
    pub async fn new() -> Result<MusicImpl, MusicError> {
        let config = SessionConfig::default();
        let player_config = PlayerConfig::default();
        let audio_format = AudioFormat::default();
        let backend = audio_backend::find(None).ok_or(MusicError::AudioBackendNotFound)?;
        let cache = Cache::new(Some("cache"), Some("cache"), Some("cache"), None).map_err(|e| MusicError::CacheSetupError)?;
        let session = Session::new(config, Some(cache.clone()));

        println!("Starting discovery");
        let mut disco: Discovery = Discovery::builder(session.device_id(), &session.client_id())
            .name("Boombox")
            .device_type(DeviceType::Speaker)
            .zeroconf_backend(discovery::find(None).expect("Couldn't find discovery"))
            .launch()
            .map_err(|e| MusicError::ZeroconfSetupError(e))?;

        let credentials = match cache.credentials() {
            Some(c) => {
                println!("Using cache credentials for {}", c.username.clone().unwrap_or("?".to_string()));
                c
            },
            None => {
                println!("Waiting for credentials via discovery");
                disco.next().await.expect("No credentials?")
            }
        };
        
        let mixer = mixer::find(None).ok_or(MusicError::MixerNotFound)?;
        let mixer_config = MixerConfig::default();

        let player = Player::new(
            player_config,
            session.clone(),
            mixer(mixer_config.clone()).get_soft_volume(),
            move || backend(None, audio_format),
        );

        let mut connect_config = ConnectConfig::default();
        connect_config.name = "Boombox".to_string();
        connect_config.device_type = DeviceType::Speaker;
        
        let (spirc, spirc_task) = Spirc::new(
            connect_config,
            session,
            credentials,
            player.clone(),
            mixer(mixer_config),
        )
        .await
        .map_err(|e| MusicError::SpircSetupError(e))?;
        
        spirc.activate().unwrap();

        let handle = tokio::spawn(spirc_task);

        Ok(MusicImpl { player, spirc, spirc_handle: handle })
    }
}
<!-- gh-comment-id:3059197392 --> @GChalony commented on GitHub (Jul 10, 2025): Sure, here is the constructor: ```rust impl MusicImpl { pub async fn new() -> Result<MusicImpl, MusicError> { let config = SessionConfig::default(); let player_config = PlayerConfig::default(); let audio_format = AudioFormat::default(); let backend = audio_backend::find(None).ok_or(MusicError::AudioBackendNotFound)?; let cache = Cache::new(Some("cache"), Some("cache"), Some("cache"), None).map_err(|e| MusicError::CacheSetupError)?; let session = Session::new(config, Some(cache.clone())); println!("Starting discovery"); let mut disco: Discovery = Discovery::builder(session.device_id(), &session.client_id()) .name("Boombox") .device_type(DeviceType::Speaker) .zeroconf_backend(discovery::find(None).expect("Couldn't find discovery")) .launch() .map_err(|e| MusicError::ZeroconfSetupError(e))?; let credentials = match cache.credentials() { Some(c) => { println!("Using cache credentials for {}", c.username.clone().unwrap_or("?".to_string())); c }, None => { println!("Waiting for credentials via discovery"); disco.next().await.expect("No credentials?") } }; let mixer = mixer::find(None).ok_or(MusicError::MixerNotFound)?; let mixer_config = MixerConfig::default(); let player = Player::new( player_config, session.clone(), mixer(mixer_config.clone()).get_soft_volume(), move || backend(None, audio_format), ); let mut connect_config = ConnectConfig::default(); connect_config.name = "Boombox".to_string(); connect_config.device_type = DeviceType::Speaker; let (spirc, spirc_task) = Spirc::new( connect_config, session, credentials, player.clone(), mixer(mixer_config), ) .await .map_err(|e| MusicError::SpircSetupError(e))?; spirc.activate().unwrap(); let handle = tokio::spawn(spirc_task); Ok(MusicImpl { player, spirc, spirc_handle: handle }) } } ```
Author
Owner

@photovoltex commented on GitHub (Jul 14, 2025):

Hmm, I tested your issue with the play_connect-example by replaying the spirc_task.await with the following, which seems to work just fine.

tokio::spawn(spirc_task);
loop {
    tokio::time::sleep(Duration::from_secs(5)).await;
    spirc.play_pause()?
}

My test is based on the latest dev version. Which version did you use?

But from everything provided I don't see any obvious issue when handling the spirc and the correlating task. There maybe something wrong with your tokio setup?

<!-- gh-comment-id:3070053606 --> @photovoltex commented on GitHub (Jul 14, 2025): Hmm, I tested your issue with the [`play_connect`](https://github.com/librespot-org/librespot/blob/80c27ec476666b40aba98327b3ba52d620dd6d06/examples/play_connect.rs#L74)-example by replaying the `spirc_task.await` with the following, which seems to work just fine. ```rs tokio::spawn(spirc_task); loop { tokio::time::sleep(Duration::from_secs(5)).await; spirc.play_pause()? } ``` My test is based on the latest dev version. Which version did you use? But from everything provided I don't see any obvious issue when handling the `spirc` and the correlating `task`. There maybe something wrong with your `tokio` setup?
Author
Owner

@photovoltex commented on GitHub (Jul 15, 2025):

I think the best way forward would be if you could provide a minimal version of your issue to us. The easiest way would be to check in the minimal version on github and share the link to it. Then we can maybe help you, otherwise this issue seems out of our control. (At least from my perspective currently)

<!-- gh-comment-id:3072018084 --> @photovoltex commented on GitHub (Jul 15, 2025): I think the best way forward would be if you could provide a minimal version of your issue to us. The easiest way would be to check in the minimal version on github and share the link to it. Then we can maybe help you, otherwise this issue seems out of our control. (At least from my perspective currently)
Author
Owner

@GChalony commented on GitHub (Jul 17, 2025):

Thanks for answering and sorry for the delay. I was using the dev branch, at commit 11c3df8eb1. I will pull again, then try to reproduce the issue from the play_connect example.
I'm just not sure when I'll be able to test this (holidays), I'll keep you posted.
Thanks for your help

<!-- gh-comment-id:3084769211 --> @GChalony commented on GitHub (Jul 17, 2025): Thanks for answering and sorry for the delay. I was using the `dev` branch, at commit 11c3df8eb1ab2c6a8f31a02cb8833caf825f415b. I will pull again, then try to reproduce the issue from the `play_connect` example. I'm just not sure when I'll be able to test this (holidays), I'll keep you posted. Thanks for your help
Author
Owner

@GChalony commented on GitHub (Aug 2, 2025):

Just tested, it works correctly when running the play_connect sample with the modification you suggested. It does play / pause without any issue. I'll dig a little more when I have time. Do you know what I could look for in the logs to know if the requests are received ?

<!-- gh-comment-id:3146634372 --> @GChalony commented on GitHub (Aug 2, 2025): Just tested, it works correctly when running the `play_connect` sample with the modification you suggested. It does play / pause without any issue. I'll dig a little more when I have time. Do you know what I could look for in the logs to know if the requests are received ?
Author
Owner

@roderickvd commented on GitHub (Aug 3, 2025):

Turning this into a discussion, as I don't believe it's an issue with librepot.

<!-- gh-comment-id:3148699067 --> @roderickvd commented on GitHub (Aug 3, 2025): Turning this into a discussion, as I don't believe it's an issue with librepot.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/librespot#685
No description provided.