Closed tweisberger closed 1 year ago
This is not a bug. This is a user/consumer error. It is up to moode to:
And
Ok a further breakdown now that I'm home.
When the audio device is removed while Spotify connected to librespot endpoint or when connect while audio interface is not present, librespot terminates. librespot should handle this gracefully. This requires manual intervention.
In that case librespot logs an error message and exits with an exit status of 1 indicating an error. This is expected behavior.
This use case is using an external DAC connected to USB. When the DAC is set to another input away from USB, it removes the connection. Expected behavior would be to gracefully disconnect from Spotify. Or in the case where the audio device is not present, reject an incoming connection.
The same as above. On dev
at least and possibly on master
(?) librespot does disconnect from Spotify.
When librespot is not playing it closes the device and when it starts playing it tries to open the device. We do not monitor the status of devices we are not using and I'm unaware of any plans to do so.
There are blocking sink events you can subscribe to via an onevent script that will allow you to tell when librespot is about to try to open the device and/or has closed the device. In your onevent script you can do whatever you need to do to make sure the device you have told librespot to use actually exists and is available to use.
You can use --emit-sink-events --onevent=path/to/my/script
Events are sent as environment variables.
An example script:
#!/usr/bin/python3
import os
player_event = os.getenv('PLAYER_EVENT')
# The player thread will block until this script exits in all cases.
if player_event == 'sink':
status = os.getenv('SINK_STATUS')
if status == 'running':
# The device is not open yet.
# Do stuff to make sure the device you have told librespot
# to use actually exists and is available to use.
# The device will be opened after the script exits.
print('The device is about to be opened.')
elif status == 'temporarily_closed':
# *Transient State*
# The device has been temporarily closed, for example librespot is loading.
print('The device is closed, but more than likely will be reopened very shortly.')
elif status == 'closed':
# The device was closed before this script was called,
# for example librespot is stopped or paused.
# Other things are free to use the device after the script exits.
print('The device has been closed.')
Principally it is a fair use case as some good DACs indeed may turn off their USB circuitry when switched to another source, to minimize electrical noise.
However does this bug still happen if you stop Spotify first? Then switch input and back, start Spotify playback? From a code review I think that might work.
Otherwise on a failed write we could consider reopening the device one time only, then retry the write.
Principally it is a fair use case as some good DACs indeed may turn off their USB circuitry when switched to another source, to minimize electrical noise.
It's essentially the equivalent of unplugging a DAC during playback.
However does this bug still happen if you stop Spotify first? Then switch input and back, start Spotify playback? From a code review I think that might work.
If Spotify stops then librespot closes the device. But all these things happen asyc so you would have to stop well in advance to prevent player from pushing bytes to the device.
Otherwise on a failed write we could consider reopening the device one time only, then retry the write.
That's basically what try_recover
in the ALSA Sink does. If we get the error in player it is fatal to the sink. What I can do is stop the sink and signal a disconnect from player. I just so happen to make that possible in my latest PR, but I still stand by my statement that it's not our responsibility to keep track of devices, that's the job of whatever daemonizes librespot. I will finish up the bits to make it work.
Currently the best thing for moode to do is just restart librespot when it happens. That's what systemd does and although it's not elegant it works fine.
That's basically what
try_recover
in the ALSA Sink does. If we get the error in player it is fatal to the sink. What I can do is stop the sink and signal a disconnect from player. I just so happen to make that possible in my latest PR, but I still stand by my statement that it's not our responsibility to keep track of devices, that's the job of whatever daemonizes librespot. I will finish up the bits to make it work.
I agree. If it's relatively easy though I guess it'd be a quick win.
I got you one better. Instead of stopping and disconnecting I made it so that it just tells Spotify we're paused when the sink errors out.
So when a device disappears we just tell Spotify we're paused. While it's gone you can smash play all you want and you'll get a bunch of errors in the log but we won't exit or crash. After the device shows back up if you hit play it continues playback were you left off.
I tested it by unplugging and plugging my DAC and it seems to work pretty well.
Basically the same thing will happen if a user tells librespot to use an invalid device. We won't exit or crash but you'll get a lot of errors and Spotify will just refuse to play no matter how hard you smash the play button.
Great idea!
Ok I added it to that massive PR. Give it a test.
There is still no way currently to programmatically tell librespot to pause from the outside without using the spotify api though. I've got a REST style command API in the works so users and consumers can tell librespot to do basic commands. I'm thinking all POST's. Info would still be gotten from events.
Closing this now I think that @JasonLG1979 made quite some effort on this a few months ago.
Describe the bug When the audio device is removed while Spotify connected to
librespot
endpoint or when connect while audio interface is not present,librespot
terminates.librespot
should handle this gracefully. This requires manual intervention.This use case is using an external DAC connected to USB. When the DAC is set to another input away from USB, it removes the connection. Expected behavior would be to gracefully disconnect from Spotify. Or in the case where the audio device is not present, reject an incoming connection.
To reproduce Steps to reproduce the behavior 1: Audio interface is removed after connect:
librespot
).librespot
withsudo librespot --name Livingroom --bitrate 320 --format S16 --mixer softvol --initial-volume 70 --volume-ctrl log --volume-range 60 --autoplay --cache /var/local/www/spotify_cache --disable-audio-cache --backend alsa --device _audioout --onevent /var/local/www/commandw/spotevent.sh
Behavior 2: Audio interface is not present when connecting :
librespot
withsudo librespot --name Livingroom --bitrate 320 --format S16 --mixer softvol --initial-volume 70 --volume-ctrl log --volume-range 110 --autoplay --cache /var/local/www/spotify_cache --disable-audio-cache --backend alsa --device _audioout --onevent /var/local/www/commandw/spotevent.sh
Log
Behavior 1 :
[2022-08-31T22:09:38Z WARN librespot_playback::audio_backend::alsa] Error writing from AlsaSink buffer to PCM, trying to recover, ALSA function 'snd_pcm_writei' failed with error 'EIO: I/O error' [2022-08-31T22:09:38Z ERROR librespot_playback::player] Audio Sink Error On Write: <AlsaSink> ALSA function 'snd_pcm_recover' failed with error 'EIO: I/O error' pi@moode:~ $
Behavior 2:
[2022-08-31T22:07:05Z TRACE librespot_playback::player] == Starting sink == ALSA lib pcm_hw.c:1829:(_snd_pcm_hw_open) Invalid value for card [2022-08-31T22:07:05Z ERROR librespot_playback::player] Audio Sink Error Connection Refused: <AlsaSink> Device _audioout May be Invalid, Busy, or Already in Use, ALSA function 'snd_pcm_open' failed with error 'ENOENT: No such file or directory' pi@moode:~ $
Host
Connecting Device
Additional context In the case of moOde,
librespot
terminates, and the device becomes unusable until the user connects to the web ui and manually restartslibrespot