mopidy / mopidy-spotify

Mopidy extension for playing music from Spotify
https://mopidy.com/ext/spotify/
Apache License 2.0
932 stars 108 forks source link

Replace libspotify / Streaming does not work - "Spotify login error: <ErrorType.USER_NEEDS_PREMIUM: 15>" #110

Open handle2001 opened 8 years ago

handle2001 commented 8 years ago

According to the Spotify Developer website, libspotify is no longer available for download, so the mopidy-spotify plugin cannot be installed anymore. Is there an alternate source for this library?

PureTryOut commented 6 years ago

So, what is the current reason for not switching over to Librespot? It being FOSS should be good enough in itself to switch over from libspotify.

I was working on an Alpine Linux container for Mopidy, and wanted to include mopidy-spotify in it but found out that this isn't possible due to libspotify (Alpine Linux uses musl rather than glibc), so I praise any effort to get rid of it.

allquixotic commented 6 years ago

"lib"respot is more like "respotd". It spawns an external process that plays the music on the local system, and they (intentionally) don't make it very easy for you to get at the PCM data from the stream. The PCM data is what we'd need to be able to pipe into gstreamer to use the rest of the mopidy code. Without it, we'd basically be completely bypassing all the customizability and configurability of mopidy, and things like shoutcast/icecast streaming, inserting custom elements into the filter stage of the pipeline, etc. would be impossible.

Things may have changed, but last I checked, the librespot devs were afraid of exposing raw PCM extraction via API from their code because it would be more likely to raise Spotify's ire and possibly lead to it being taken down. Code is no longer free speech in the eyes of governments and websites such as Spotify and Microsoft (nee Github).

Aside from that, librespot is written in Rust, so we'd need some kind of a FFI or wrapper to call from Python to Rust even if it had the capability we need.

axelsimon commented 5 years ago

Thanks for this answer, it explains the situation much better than what info I had so far. Trying to stay optimistic: is there any possibility of working with the librespot folks to get the needed functionality / code into mopidy?

sashahilton00 commented 5 years ago

You can get PCM from librespot, we provide it via a FIFO pipe. What we don't expose is the means to just request files and corresponding decryption keys to get raw PCM in a way that would be much faster than listening to each song. The aim of this is to prevent it being turned into a spotify ripper.

kingosticks commented 5 years ago

Having worked a bit on both projects I see the state of affairs as follows:

sashahilton00 commented 5 years ago

Just weighing in on some of the above:

However, support for streaming from Spotify’s CDN is a must (librespot-org/librespot#280). This is probably the blocker right now.

This and reconnection (https://github.com/librespot-org/librespot/issues/134), which are the main sources of problems with regards to playback on slow/patchy connections

Needs a way for Mopidy to control librespot

This can be done via the connect web api. you could also hook librespot, but any kind of JSON API or similar for easier integration for control/querying belongs in librespotd, and will thus have to wait until the daemon is written.

Requires adding a Gstreamer backend to librespot(d). Is a benefit in replacing the multiple audio backends currently in librespot with one?

Audio backends are a pain to maintain at the moment. There is a basic modular system for adding sinks atm, but for choice we'd avoid adding more and more backends into librespot - what I (personally) would like to see happen would be for (once the daemon is written) all the audio backends to be moved to the daemon, and librespot core to just provide PCM samples, with potentially a cpal/rodio backend for people that don't want to run the daemon, but rather librespot as a minimal standalone.

Requires adding a Gstreamer backend to librespot(d). Is a benefit in replacing the multiple audio backends currently in librespot with one?

See above point, also there is already a GStreamer PR in the librespot repo, which might be a useful reference.

kingosticks commented 5 years ago

This can be done via the connect web api.

That's a good point. It's a basic API but basic is all we need.

there is already a GStreamer PR in the librespot repo

Oh I see at https://github.com/plietar/librespot/tree/gst. This just made the 2nd option a lot more interesting, I have amended my post above.

Linuus commented 4 years ago

Is it possible to switch to spotifyd perhaps? https://github.com/Spotifyd/spotifyd

kingosticks commented 4 years ago

No, that's a less useful (from a library point of view) version of librespot.

allquixotic commented 4 years ago

That said, a viable solution would be to stream spotifyd to pulseaudio, then capture the pulseaudio and stream and do what you want with it from there using e.g. Gstreamer. Would have to figure out how to get tags out of spotifyd though.

https://github.com/allquixotic/tribblify

The docs are out of date but it works; I've used it recently, in fact.

Linuus commented 4 years ago

I see. I guess spotifyd with https://github.com/mopidy/mopidy-mpris should work though 🤔

adamcik commented 4 years ago

On a side note, since I was thinking about this due to a question in an other bug:

In an ideal future state whatever we land on instead of libspotify should work without GStreamer's appsrc. Relying on that way of delivering audio seems to cause trouble and having something that is either a native GStreamer src element, or something that and existing src element can plug into as a URI would be much nicer. I.e. a lot of the trouble with getting gapless working well and to some degree streaming often boils down to appsrc to some degree.

The other half of this would be that I think I would prefer we use the web APIs as much as possible, relying on the libraries as little as possible

kingosticks commented 4 years ago

In regards to the first point, an updated version of https://github.com/plietar/librespot/tree/gst is at https://github.com/librespot-org/librespot/compare/dev...allquixotic:gst1.0-2020. I never got around to testing it out but it sounded promising.

allquixotic commented 4 years ago

My gst forward-port just makes it work with the latest versions of the Rust gstreamer bindings and the librespot backend rearchitecture, and fixes a few bugs that were there in the original port. It's still far from perfect, actually, because what you actually hear often gets out of sync with what's currently being streamed into the appsrc by librespot. I think there might be pipeline hacks we can do to improve that, but I'm not sure. I usually test with a pipeline that puts a queue in front of the processing elements and ends in autoaudiosink. The queue is awesome for protecting against dropouts (I never get any) but might be filling up on silence or something during pauses; not sure.

Anyway, it's far from perfect, but the idea from adamcik about appsrc "causing trouble" is probably spot-on. I'm not sure how we could write a standalone gstreamer source element that can be linked into any process that would consume audio from librespot, though. Putting in playlist or song URIs via parameter is an option, but goes against the current UI model of librespot, where you use a real Spotify client using the Spotify Connect protocol to play audio through librespot. The librespot audio is handled 100% in-process, from the time it's received from Spotify's servers until it's passed to the gstreamer pipeline, and the only way it can leave the process is if the pipeline has it sink out somewhere that's accessible by another process or sound device.

We might be able to fix the bugs in the appsrc style implementation for a first pass and then do another iteration later that creates a proper gstreamer src element for spotify via librespot.

Anyway, these are thoughts that are sort of internal to the design of librespot, so I'm not sure what impact they have (if any) on mopidy-spotify.

djmattyg007 commented 4 years ago

If we assume they do not turn off the endpoints, then what advantage does something based on librespot have over just using the current version of libspotify that we already have?

Just to be clear, I think the primary reason people want a switch to librespot (other than future-proofing due to libspotify being unmaintained) is for the Spotify Connect support. Having that seemless integration with the rest of the Spotify ecosystem would be fantastic.

mgrouch commented 3 years ago

old libspotify is not available for arm64. librespot would fill that gap.

NullSense commented 3 years ago

If we assume they do not turn off the endpoints, then what advantage does something based on librespot have over just using the current version of libspotify that we already have?

Just to be clear, I think the primary reason people want a switch to librespot (other than future-proofing due to libspotify being unmaintained) is for the Spotify Connect support. Having that seemless integration with the rest of the Spotify ecosystem would be fantastic.

Yes, definitely, Spotify Connect support is integral.

dropofwill commented 3 years ago

So I just tried playing around with librespot/librespot-java and gstreamer outside of mopidy to see what it was like. I tried librespot-java with PIPE and STDOUT, and the latest librespot with the gstreamer backend. Controlling it with the spotify connect apis. Couple of thoughts:

Personally I was mostly interested in this for the internal spotify 'mercury' apis, and thought it would be nice to have just one less thing to run, but since I don't need arm and kind of think spotify connect is a downside, I probably won't do anymore on this. But maybe this will be useful if someone else wants to try it.

Here's the deets on gst commands that work #### `librespot-java` with PIPE to a FIFO `mkfifo player.output` `gst-launch-1.0 filesrc location=player.output ! rawaudioparse ! input-selector ! playsink flags=audio` #### `librespot` with gstreamer backend Note: need to build with gstreamer support `librespot --name 'rust' --backend gstreamer --device '! rawaudioparse ! input-selector ! playsink flags=audio'` #### `decodebin` issues Internally playbin uses uridecodebin, which in turn use decodebin (note i was using input-selector -> playsink because that basically mirrors the pipeline mopidy's playbin ends up with). With the output of either librespot decodebin fails for typefind: `! decodebin ! input-selector ! playsink flags=audio` ``` ERROR: from element /GstPipeline:pipeline0/GstDecodeBin:decodebin0/GstTypeFindElement:typefind: Could not determine type of stream. Additional debug info: ../plugins/elements/gsttypefindelement.c(1000): gst_type_find_element_chain_do_typefinding (): /GstPipeline:pipeline0/GstDecodeBin:decodebin0/GstTypeFindElement:typefind ERROR: pipeline doesn't want to preroll. Setting pipeline to NULL ... ERROR: from element /GstPipeline:pipeline0/GstFileSrc:filesrc0: Internal data stream error. Additional debug info: ../libs/gst/base/gstbasesrc.c(3127): gst_base_src_loop (): /GstPipeline:pipeline0/GstFileSrc:filesrc0: streaming stopped, reason error (-5) ERROR: pipeline doesn't want to preroll. Freeing pipeline .. ``` Whereas giving it a hint based on what I see in the logs of rawaudioparse everything works. I imagine this isn't a good idea, and anyways playbin/uridecodebin don't let you pass these hints along. `! decodebin sink-caps='audio/x-raw, format=(string)S16LE, layout=(string)interleaved, rate=(int)44100, channels=(int)2, channel-mask=(bitmask)0x0000000000000003' ! input-selector ! playsink flags=audio`
kingosticks commented 3 years ago

I found the latency is improved with this version but I was using it without specifying a --device argument (so it was playing straight to autoaudiosink rather than trying to emulate Mopidy's pipeline).

blacklight commented 3 years ago

Hey folks,

I've spent the last couple of weeks working on integrating Librespot + the Spotify Web Player API in Platypush, just to have a backup plan if libspotify stops working for good. I have managed to put together something quite usable (although a bit hacky) that could also be recycled here. For those interested:

The backend acts as a wrapper around Librespot (I have only tested Librespot with the ALSA backend though, not gstreamer). Librespot is definitely not designed for asynchronous events management in mind though, but at least it can call an external script when something happens (e.g. new track, state change, volume change etc.), so a small script that synchronizes somehow with the main app upon events does the trick. The code of the backend is sufficient to get the device to appear in the Spotify Connect list on the Spotify app.

The plugin then uses the web player API to control the playback and manage the queue - which is possible now because the device appears as a Spotify Connect compatible player.

I have even managed to tweak the existing UI interface I made for mpd/mopidy to work with the new Spotify integration with just a couple of changes.

PROS

CONS

Let me know what you folks think. The alternative I see are:

  1. We may decide that a migration to Librespot may be worth investigating for the long-term (if so, understand how it behaves with gstreamer)
  2. We may decide that it may be worth a separate mopidy-librespot integration for this
  3. We decide that the Librespot way it's still too hacky (and/or the project too immature) to be worth investing the effort on a migration or another integration (in that case I'll keep using my project's integration as a backup in case libspotify goes down)
AndreKR commented 2 years ago

On the "pros" side don't forget arm64 support. I have just migrated my Raspberry Pi to the 64 bit Raspberry Pi OS because it doubles and triples the performance of some other apps and found that now mopid-spotify no longer works. For now I can just rip some playlists as a workaround but it would be nice to be able to properly use Spotify again.

chatziko commented 2 years ago

First, let me say thanks for this really great project!

My 2 cents on this, from the point of view of an external user: continuing to use a library that has been deprecated for many years now is simply not sustainable. The risk that it might stop working at any moment is only part of the story. Even if it doesn't, this choice will still lead to a slow death by stagnation, since nobody has the motivation to implement new features or fix bugs. I'm not sure what's the best migration plan is, but there should be such a plan.

ejurgensen commented 2 years ago

FYI I had the same issue with OwnTone (previously forked-daapd), which I maintain. Its Spotify integration also was based on libspotify + web API, and libspotify was of course troublesome. I made several attempts at integrating with librespot instead, but it was too difficult (also because my Rust skills aren't good). So in the end I ended up partially porting librespot to C (as a library), and OwnTone is now using that. Here is the port. I didn't port the Connect stuff, since I only needed a libspotify replacement. Might do that later.

An added benefit is that it has made the Spotify login much more smooth. The token from the OAuth web API login can be reused for the librespot login, so that is nice (and a security improvement, since OwnTone thus never has the cleartext password).

Of course this is all very unofficial and can be broken by Spotify at any time, but I suppose the more Premium users there are on this alternative channel the less likely that is.

Btw I think Spotify Connect reuses a lot from libspotify, so I think that is why it is still working despite being deprecated for many years. That also means the risk of it breaking might be smaller than you might expect.

dsynkd commented 2 years ago

My 2 cents on this discussion, considering @BlackLight 's comments:

  1. Keep using libspotify for, at the very least, playback, until it no longer works, at which point we would have to switch to librespot.
  2. Use the Web API for any other feature when possible (i.e merge mopidy-spotify-web into this repo)
Simerax commented 2 years ago
  1. Keep using libspotify for, at the very least, playback, until it no longer works, at which point we would have to switch to librespot.

I guess this happend faster then expected. Is switching to librespot feasible by now?

kingosticks commented 2 years ago

Sady, yes, it's finally dead. They quietly posted a 1-month notice this was happening. I guess they forgot to post it on the Spotify Developer mailing list (which must also be dead!).

And actually yes, there's a better option for us now in the shape of https://lib.rs/crates/gst-plugin-spotify.

As I posted earlier on our forum, there is a possible hack to libspotify we could do but I think it's beyond me.

blacklight commented 2 years ago

And actually yes, there's a better option for us now in the shape of https://lib.rs/crates/gst-plugin-spotify.

gst-launch-1.0 spotifyaudiosrc username=$USERNAME password=$PASSWORD track=spotify:track:3i3P1mGpV9eRlfKccjDjwi ! oggdemux ! vorbisdec ! audioconvert ! autoaudiosink

This is beautiful in so many ways. Wish we've had such a solution available earlier instead of sitting on libspotify's corpse while waiting for it to sink.

@kingosticks since this is already a Gst-native plugin, it probably shouldn't be too hard to integrate into the existing codebase, is that correct? It actually should simplify things - a lot of code in the playback module that manages audio buffers can go away. Is there any plan to change the implementation to gst-plugin-spotify? I can take a look when I have a bit of time, but I can't make big promises.

kingosticks commented 2 years ago

Yes, we want to use this, or something just like this. We've always wanted to get rid of using appsrc. We might have some performance problems to overcome but we can worry about them if/when we hit them.

It's a GST Rust plugin which is a little more complicated when it comes to packaging. It's also currently lacking URI handler support which I worked on a while back but never quite around to finishing/testing/upstreaming it.

blacklight commented 2 years ago

I've just noticed that it has Librespot as a dependency. I've actually built the Spotify integration in Platypush using Librespot instead of libspotify (call it "future-proofing"). I mostly like it, but I've also got concerns on size, build time and packaging.

I couldn't find a binary Librespot executable installable on RPi - there's a librespot-dev Snap image, but then I guess that Snap would become a dependency for mopidy-spotify? The alternative is to build Librespot directly on the RPi as part of the extension's build process. I've done so on three RPis, and I can assure that it's a slow and painful process. First, because it requires the whole Rust environment (which, again, isn't available on RPi OS via apt) with a whole set of dependencies. Second, because the build process itself take ~1-3hr depending on the RPi. And you eventually end up with a few hundreds of MBs of used storage space.

Performance itself IMHO isn't a big blocker. I've been running Librespot on my RPis for a while and I didn't notice any performance issues when compared to mopidy-spotify. I'm mostly concerned on how to get Librespot installed in the first place without workarounds or painful manual processes.

mgrouch commented 2 years ago

https://github.com/dtcooper/raspotify

Installs librespot and has rpi binaries

mgrouch commented 2 years ago

For armhf buster


if [ $LMARCH == 'armhf' ]; then
  #apt-get -y -q install raspotify=0.31.8~librespot.v0.3.1-54-gf4be9bb
  wget https://github.com/dtcooper/raspotify/blob/2d6ceca0921a632b73e45cb8dbed6b8a57b3b608/pool/main/r/raspotify/raspotify_0.31.8~librespot.v0.3.1-54-gf4be9bb_armhf.deb?raw=true -O rasp.deb
  dpkg -i rasp.deb && rm -f rasp.deb
  if ! grep -q raspotify /etc/group; then
    groupadd raspotify
  fi
  usermod -a -G raspotify user
  systemctl disable raspotify
  install -d -m 755 -o 1000 -g 1000 "/home/user/.config/systemd/"
  install -d -m 755 -o 1000 -g 1000 "/home/user/.config/systemd/user/"
  install -v -m 644 -o 1000 -g 1000 $FILE_FOLDER/raspotify.service "/home/user/.config/systemd/user/raspotify.service"
fi

apt-get clean

pip3 install --upgrade spotify-cli
blacklight commented 2 years ago

Hmm I'm not sure if I like this.

We would be basically installing a whole service (Raspotify), and disable it after installation, just because it provides a ready-to-use Librespot binary.

Also, mopidy-spotify currently comes with apt builds, and I'm not sure if this is an elegant solution to use into an apt package.

Not that my proposed solutions were any better (installing the Rust environment to compile a heavy project like Librespot is probably worse than this). But if we have to go down this way, I'd rather just use the librespot-dev Snap image - at least Snap is available in the official repos and it's easier to handle and package.

kingosticks commented 2 years ago

We can cross-compile and distribute our own binaries. Don't get bogged down in any of that.

The performance I am talking about is in regards to loading track data - with spotifyaudiosrc the Spotify session lives only as long as the spotifyaudiosrc element and will be created and destroyed for each track when it starts/ends. This is different to how things work now.

adamcik commented 2 years ago

@kingosticks do you know if that source supports the right URI interface for uriplaybin etc to pick the element. If that doesn't work we would need to get that fixed or come up with some workaround.

It would be real nice to kill the appsrc integration without adding new hacks.

kingosticks commented 2 years ago

As it is, it doesn't. But what I implemented in my branch should add that support and I know the gst maintainers want it done and will take it. So I'll try get a PR off to them tonight.

blacklight commented 2 years ago

@kingosticks

We can cross-compile and distribute our own binaries

If we can cross-compile and bundle the binaries in the package then of course things are much simpler - no Snap/Rust builds required.

Just a heads up on a couple of issues that I've had with the Platypush/Librespot integration:

However, it also seems that we don't have many alternatives.

Let me know if you need any help with the migration. My mopidy-and-snapcast automation is currently broken (I've tried as long as possible to avoid restarting mopidy on some devices so I could leverage cached sessions that were still valid) so I'm happy to offer any support to make sure that an alternative is shipped ASAP.

adamcik commented 2 years ago

I just changed the title in the hopes that people stop creating new duplicate issues for the problem...

Marshalldog commented 2 years ago

Will there be a patch for mopidy spotify? and are there any alternatives to mopidy that work with spotify and snapcast

Sir-Photch commented 2 years ago

and are there any alternatives to mopidy that work with spotify and snapcast

Depending on what you want to achieve, raspotify might do that.

kingosticks commented 2 years ago

I had some problems with spotifyaudiosrc which I have now resolved after getting some clues from the gst devs. Turns out "source-setup" only fires before the element starts, on Gstreamer v1.20 onwards. I'll push that stuff.

Next, I'll try and hack Mopidy-Spotify to use it. I only have some evenings to work on this, can't say when it'll be working.

Mopidy-Tidal might be an alternative. No idea what state that is in. Feel free to discuss that further in our forum.

miguellazaro commented 2 years ago

Thanks so much for your time and effort on this @kingosticks. My children found out today that they can't play their music with the rfid cards... (we have been using a rpi with cards for almost 4 years) and before getting to this post i was totally lost :)

blacklight commented 2 years ago

FYI I have migrated from Spotify to Tidal and cancelled my Spotify subscription after more than 10 years.

I started investigating gst-plugin-spotify, and in the past couple of days I have used my Librespot+Platypush integration to play music on my Raspberry Pis as a workaround, but:

  1. The latency on new track play/skip is almost unbearable (~10 seconds from the time one gives the command to the time some music actually happens).

  2. Over the past decade I've built a lot integrations and automation routines that relied on mopidy, and I don't think that it's fair to use more of my spare time to play this cat-and-mouse game with Spotify just to keep mopidy-spotify working, nor I am willing to rewrite all of my automation routines using a backend that is not MPD-compatible. If Spotify keeps breaking things without providing alternatives, and they don't care of how users actually want to consume content, then they just don't deserve my money.

I have used Soundiiz (https://soundiiz.com/) to migrate my collection from Spotify to Tidal. It requires a premium account for a full collection migration, but it's just $4.50 a month, and probably you can cancel it after moving your stuff.

mopidy-tidal doesn't look as mature as mopidy-spotify yet (among the first things I've spotted is that it doesn't support year/date on track metadata, and mopidy-iris doesn't support adding tracks to Tidal playlists), but IMHO it's worth rewarding the services that at least give us the opportunity to stream music outside of a browser.

LevitatingBusinessMan commented 2 years ago

@BlackLight Thanks for telling me about these two amazing services. I will sure make use of them.

sebrep commented 2 years ago

I have checked Tidal and for me it's sadly not an alternative yet. So if you can get mopidy-spotify working again every Phoniebox/RPi-Jukebox-RFID user would be very greatful. I'm sure we could even chime in with some well deserved donations.

adamcik commented 2 years ago

Feel free to continue discussing alternatives etc at https://discourse.mopidy.com/ or our zulip instance, it's better if this bug is just focused on the issue at hand. Good luck finding something that works well for you :-)

kingosticks commented 2 years ago

I've hacked something up as a proof of concept. Plenty more to do but it's working well and I don't see any of the performance issues I was worried about. But packaging might be the tricky part of this.

roosemberth commented 2 years ago

@kingosticks Could you push somewhere so we can test and maybe start packaging work for other distributions?

kingosticks commented 2 years ago

I've not gotten much further so there's not a lot to test yet. But you are welcome to try what's there if you can get it going, gst-plugins-rs has some instructions how to compile.

Only track lookup is done but the others are pretty much the same. I'll see if I can get further tomorrow night. Help with the Debian packaging is probably the most useful thing to do. If someone can work out what we need for cargo-deb to work, assuming that's the best option.

adamcik commented 2 years ago

Did I understand correctly that the source setup variant for setting user/pass doesn't work for the librespot gst integration, i.e. we can't do something like https://github.com/mopidy/mopidy/blob/develop/mopidy/audio/actor.py#L564 and just need to pass the auth info in the URI?

kingosticks commented 2 years ago

There's some bug/feature in GStreamer < v1.20 where source-setup fires AFTER the source has already been started, which is too late. In my tests the pipeline actually tries to start the source 2 more times after that, and for these the user and password will have been set by source-setup so they succeed. I'm trying to find out why that is and if we can rely on that. If we can't rely on that, and if we want to keep supporting GST v1.18 (in Debian stable so I think we must), seems we need to pass the auth info in the URI.

In GStreamer v1.20 it works nicely with a source-setup handler as you would expect. And we could have extensions implement that handler and remove everything Spotify specific from Audio.