Spotifyd / spotifyd

A spotify daemon
https://spotifyd.rs
GNU General Public License v3.0
9.88k stars 453 forks source link

(auto)play errors on startup or reconnect of spotifyd #1155

Open JJ-Author opened 1 year ago

JJ-Author commented 1 year ago

Description after spotifyd is started up freshly (or reconnecting to AP) it seems not possible to start the playback via spotify API

( 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

  1. start spotifyd (but no other spotify device like android app or web player!)
  2. go to spotify console and click get token button
  3. select scope user-read-playback-state and user-write-playback-state
  4. get the device id of spotifyd https://developer.spotify.com/console/get-users-available-devices/
  5. browse to https://developer.spotify.com/console/put-user-player/ and set the body (replace the id from 4!) { "device_ids": [ "1ecc089171983b406deeb321b10c98f0dd62d7da" ], "play":true }
  6. see the spotifyd log "restart" --> AutoplayError: MercuryError will appear
  7. 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 with Spotify URI Jan 03 09:00:56 DietPi spotifyd[5497]: (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 with Spotify URI Jan 03 09:14:11 DietPi spotifyd[5497]: Resolved 50 tracks from <"spotify:album:6EB14IXV5oyOiItGBv7mtG"> Jan 03 09:14:11 DietPi spotifyd[5497]: (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):

eladyn commented 1 year ago

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.

JJ-Author commented 1 year ago

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 new io.github.spotifyd.TransferPlayback method. Maybe I'd need to wait some time before connecting spotifyd? 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 in spotifyd 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.

JJ-Author commented 10 months ago

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

  1. fresh start of spotifyd
  2. reconnect of spotifyd after network interruption
  3. 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:

eladyn commented 10 months ago

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!

JJ-Author commented 10 months ago

Calling play currently just uses the play method offered by librespot, 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

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

eladyn commented 10 months 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.

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!