spotifyd
spotifyd copied to clipboard
(auto)play errors on startup or reconnect of spotifyd
Description after spotifyd is started up freshly (or reconnecting to AP) it seems not possible to start the playback via spotify API
- using play https://developer.spotify.com/documentation/web-api/reference/#/operations/start-a-users-playback nothing happens (given that no other spotify device of your account played recently)
- using transfer-playback https://developer.spotify.com/documentation/web-api/reference/#/operations/transfer-a-users-playback it throws an error in the log (when it is the only device connected at the moment)
( I can not test MPRIS since armv6 binary has no dbus support but probably that is affected also in similar way) EDIT yes it is: see #1157
To Reproduce
- start spotifyd (but no other spotify device like android app or web player!)
- go to spotify console and click get token button
- select scope user-read-playback-state and user-write-playback-state
- get the device id of spotifyd https://developer.spotify.com/console/get-users-available-devices/
- browse to https://developer.spotify.com/console/put-user-player/ and set the body (replace the id from 4!)
{ "device_ids": [ "1ecc089171983b406deeb321b10c98f0dd62d7da" ], "play":true }
- see the spotifyd log "restart" --> AutoplayError: MercuryError will appear
- See error -->
Expected behavior I think if spotifyd loses its "playback context" on (re)connect to spotify it would make sense to restore it somehow. I think a convenient strategy would be to continue with the last played song and its context (e.g. the playlist or autoplay station) when the play command is sent. Using this API call https://developer.spotify.com/console/get-recently-played/ this should be possible. For the transfer playback call I would expect that no error is shown and the same strategy could be used.
Logs
Click to show logs
reconnect
Jan 03 09:00:56 DietPi spotifyd[5497]: Loading <Cannons> with Spotify URI <spotify:track:1tgLMyNVmgtLJcOfE0wbyJ>
Jan 03 09:00:56 DietPi spotifyd[5497]: <Cannons> (224773 ms) loaded
Jan 03 09:14:11 DietPi spotifyd[5497]: Fetching autoplay context uri
Jan 03 09:14:11 DietPi spotifyd[5497]: Autoplay uri resolved to <"spotify:station:album:6EB14IXV5oyOiItGBv7mtG">
Jan 03 09:14:11 DietPi spotifyd[5497]: Loading <Cannons> with Spotify URI <spotify:track:1tgLMyNVmgtLJcOfE0wbyJ>
Jan 03 09:14:11 DietPi spotifyd[5497]: Resolved 50 tracks from <"spotify:album:6EB14IXV5oyOiItGBv7mtG">
Jan 03 09:14:11 DietPi spotifyd[5497]: <Cannons> (224773 ms) loaded
Jan 03 18:28:39 DietPi spotifyd[5497]: subscription terminated
Jan 03 18:28:39 DietPi spotifyd[5497]: Connecting to AP "ap.spotify.com:443"
Jan 03 18:28:40 DietPi spotifyd[5497]: Connection reset by peer (os error 104)
Jan 03 18:28:40 DietPi spotifyd[5497]: Authenticated as "xxx" !
Jan 03 18:28:40 DietPi spotifyd[5497]: Country: "DE"
Jan 03 18:28:40 DietPi spotifyd[5497]: Using Alsa sink with format: S16
Jan 03 19:39:35 DietPi spotifyd[5497]: Fetching autoplay context uri
Jan 03 19:39:35 DietPi spotifyd[5497]: No more tracks left in queue
Jan 03 19:39:35 DietPi spotifyd[5497]: error 400 for uri hm://autoplay-enabled/query?uri=
Jan 03 19:39:35 DietPi spotifyd[5497]: AutoplayError: MercuryError
restart
Jan 03 19:57:59 DietPi systemd[1]: Stopping Spotifyd (DietPi)...
Jan 03 19:57:59 DietPi systemd[1]: spotifyd.service: Succeeded.
Jan 03 19:57:59 DietPi systemd[1]: Stopped Spotifyd (DietPi).
Jan 03 19:57:59 DietPi systemd[1]: spotifyd.service: Consumed 26min 34.637s CPU time.
Jan 03 19:57:59 DietPi systemd[1]: Started Spotifyd (DietPi).
Jan 03 19:57:59 DietPi spotifyd[6762]: Loading config from "/mnt/dietpi_userdata/spotifyd/spotifyd.conf"
Jan 03 19:57:59 DietPi spotifyd[6762]: No password specified. Checking password_cmd
Jan 03 19:57:59 DietPi spotifyd[6762]: No password_cmd specified
Jan 03 19:57:59 DietPi spotifyd[6762]: No proxy specified
Jan 03 19:57:59 DietPi spotifyd[6762]: Using software volume controller.
Jan 03 19:57:59 DietPi spotifyd[6762]: Connecting to AP "ap.spotify.com:443"
Jan 03 19:57:59 DietPi spotifyd[6762]: Authenticated as "xxx" !
Jan 03 19:57:59 DietPi spotifyd[6762]: Country: "DE"
Jan 03 19:57:59 DietPi spotifyd[6762]: Using Alsa sink with format: S16
Jan 03 19:58:33 DietPi spotifyd[6762]: Fetching autoplay context uri
Jan 03 19:58:33 DietPi spotifyd[6762]: No more tracks left in queue
Jan 03 19:58:33 DietPi spotifyd[6762]: error 400 for uri hm://autoplay-enabled/query?uri=
Jan 03 19:58:33 DietPi spotifyd[6762]: AutoplayError: MercuryError
Versions (please complete the following information):
- OS: DietPi 8.0.2
- Spotifyd: 0.3.4-armv6-slim
Thank you for the detailed report, and sorry for the long response time.
it seems not possible to start the playback via spotify API
- using transfer-playback
There's currently ongoing work on adding a TransferPlayback
method to D-Bus (#1162), although that API call seems to be not working for you as well? Does it just not start playing music, but transfer the playback to the device, or does it fail in general?
For me personally, it works when I close all clients, start spotifyd
and call the new io.github.spotifyd.TransferPlayback
method. Maybe I'd need to wait some time before connecting spotifyd
?
Apart from that, since we're completely relying on librespot
for the backend handling, I imagine it would be quite difficult, to implement what you describe in spotifyd
itself. So let's hope that the new D-Bus method helps you get further.
There's currently ongoing work on adding a
TransferPlayback
method to D-Bus (#1162), although that API call seems to be not working for you as well? Does it just not start playing music, but transfer the playback to the device, or does it fail in general? if another device is/was playing very recently then that worked. But is doest not transfer the playback (I guess since there is nothing to transfer since there is no other device)For me personally, it works when I close all clients, start
spotifyd
and call the newio.github.spotifyd.TransferPlayback
method. Maybe I'd need to wait some time before connectingspotifyd
? This is the new DBUS method that will be released in the future? Or how do I invoke that method?
Yes you need to wait a bit (4 hours should be save) if another client was playing before and also restart spotifyd after that.
Apart from that, since we're completely relying on
librespot
for the backend handling, I imagine it would be quite difficult, to implement what you describe inspotifyd
itself. So let's hope that the new D-Bus method helps you get further.
Yeah of course we can also try to forward relevant parts of issue at the librespot issue tracker. I am not sure which errors are caused by what tool. the "mercury error" e.g. is it caused in librespot or is it just a wrong usage of its autoplay feature in spotifyd?
EDIT: Just saw that there is the 0.3.5 release. will try that when I have time to compile that with DBus on ARMv6.
Took me quite some time to figure it out but I managed to compile spotifyd-0.3.5+5565f24 with DBUS for armv6l using qemu.
good news: transfer-playback both from dbus as well as from the API work now from an autoplay context smoothly and without error.
bad news: the play function still does not work from either MPRIS or the API (without any error in the log though) in the following states
- fresh start of spotifyd
- reconnect of spotifyd after network interruption
- when playback was transferred from spotifyd to another device that was then paused and put offline after playing 1-2 tracks
Note: it works however if i just play some tracks and pause, then I can resume with play button hours later... assuming stable internet and that I did not use any other spotify device to play on my account.
My questions now are:
- why is that? with the official spotify devices this does not happen.
- why not use the code that drives the transferplayback DBUS function also for MPRIS play function as a fallback if play routine does not succeed? Otherwise I think spotifyd MPRIS interface seems implemented in an incorrect way since the CanPlay property to be true constantly obviously breaks the standard because it does not follow the definition since playback can not be started using Play or PlayPause in above states 1-3. for this specific standard violation I can open another issue, but setting this correctly to false in states 1-3 would not help much with the problem that spotifyd can still practically not be used (or very much limited only) by standard MPRIS clients/controllers.
Calling play
currently just uses the play
method offered by librespot
, which forwards it to whatever device is the currently active playing device.
Transferring the playback to itself is (to my knowledge) not implemented in librespot
, so we have to rely on the Spotify API for that, which is not very elegant and also slow. So I would personally rather spend the time implementing the "better" version that librespot
can claim the currently active device itself without the help of the Spotify API instead of abusing the API once more for fixing MPRIS behaviour. But if you really need that feature, patches are very welcome!
Calling
play
currently just uses theplay
method offered bylibrespot
, which forwards it to whatever device is the currently active playing device. Hm I can not confirm that in practice. MPRIS play of spotifyd never started my other spotify devices (even if I paused them a second ago)
I am fine and happy at the moment, with a modified version of this https://github.com/FreekBes/spotify_web_controller and the dbus transfer-playback call. just wanted to report back. but I wonder how spotifyd users start playing music, given that mpris is not of help here - is everybody using spotify web app, or spotify app on a smartphone? And I had the vision of letting buttons of an VCR IR remote control trigger MPRIS calls to control spotifyd, bluetooth sources or whatever mpris player is running on my Pi, but with some extra logic and the transfer-playback DBUS call this should be also possible.
so maybe it makes sense to close this issue and create a new and more specific one for improving MPRIS implementation?
Nevertheless I had a look but I think I can not be of help here, would just break things. many mpris implementation decisions are too cryptic to me (adding to the cryptic rust syntax) It seems to be a combination of
- librespot_connect spirc (mostly for control)
- rspotify (mostly for metadata)
but I don't understand why not directly https://docs.rs/librespot-playback/0.4.2/librespot_playback/player/struct.Player.html#method.play has been used instead of librespot_connect and https://docs.rs/librespot-metadata/0.4.2/librespot_metadata/struct.Track.html instead of rspotify
also properties that emit changes are defined as emits_changed_false
the seeked signal is not defined
I am fine and happy at the moment, with a modified version of this https://github.com/FreekBes/spotify_web_controller and the dbus transfer-playback call. just wanted to report back. but I wonder how spotifyd users start playing music, given that mpris is not of help here - is everybody using spotify web app, or spotify app on a smartphone? And I had the vision of letting buttons of an VCR IR remote control trigger MPRIS calls to control spotifyd, bluetooth sources or whatever mpris player is running on my Pi, but with some extra logic and the transfer-playback DBUS call this should be also possible.
In comparison to the official clients or things like ncspot
or spot
, this project is not meant to provide the full client experience from searching for music to playing it, but rather a stripped down version, mainly the playback part. This makes it similar to many third-party speakers or audio systems which also only support playback and need a controlling Spotify client. In addition to that, spotifyd
can control itself - to some extent, and that can also be used to get something like a basic player interface. But since that part is an optional feature, the primary way to control spotifyd
is still some third-party application.
so maybe it makes sense to close this issue and create a new and more specific one for improving MPRIS implementation?
I think, there's a lot of room for improvement with new sessions like continuing playback or, as you proposed, automatically transferring playback to the device, when play
is called.
Nevertheless I had a look but I think I can not be of help here, would just break things. many mpris implementation decisions are too cryptic to me (adding to the cryptic rust syntax) It seems to be a combination of
* librespot_connect spirc (mostly for control) * rspotify (mostly for metadata)
Yes, the rspotify
part is unfortunately needed, since librespot
doesn't expose most of the metadata it has available. This will change in the next release of librespot
, but until then we need to stick with those API requests unfortunately.
but I don't understand why not directly https://docs.rs/librespot-playback/0.4.2/librespot_playback/player/struct.Player.html#method.play has been used instead of librespot_connect and https://docs.rs/librespot-metadata/0.4.2/librespot_metadata/struct.Track.html instead of rspotify
As for the Player
part: Since we are a Spotify connect device, we need to communicate all player changes through "Spirc". In the librespot
architecture, Spirc
wraps the player and makes sure that playback changes are synchronized with the Spotify servers / other clients. So using Player
directly would probably work, but cause issues with other clients.
also properties that emit changes are defined as
emits_changed_false
To be honest, I'm not really sure what that method does, probably some introspection things? If so, it might make sense to change that to true
.
the seeked signal is not defined
Oh, good point. If you want, feel free to add that definition!