Open handle2001 opened 8 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.
"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.
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?
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.
Having worked a bit on both projects I see the state of affairs as follows:
mopidy.audio
by ditching appsrc and getting Spotify audio data from:filesrc
).
ipcpipeline
.
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.
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.
Is it possible to switch to spotifyd perhaps? https://github.com/Spotifyd/spotifyd
No, that's a less useful (from a library point of view) version of librespot.
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.
I see. I guess spotifyd with https://github.com/mopidy/mopidy-mpris should work though 🤔
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
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.
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.
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.
old libspotify is not available for arm64. librespot would fill that gap.
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.
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:
I couldn't get playbin
to work with either, seemingly because decodebin
couldn't figure out that the source was raw audio. When I used rawaudioparse
or passed the correct sink-caps to decodebin
it worked, but there doesn't seem to be a way to get playbin
to pass that down to uridecodebin
-> decodebin
.
I also noticed was that latency for pause / next track was 2-4 seconds, maybe even a bit more for the rust gstreamer output, which sort of makes sense with gstreamer queueing and what not, but its quite a bit worst than mopidy-spotify which also uses gstreamer (and obviously librespot with just an os mixer output, so it wasn't spotify connect that was slow). Just speculation, because idk much about how this works, but I guess that this is because in the mopidy-spotify case the appsrc
is 'closer' to the input controls and can throw away what's already been buffered on pause/next/etc?
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.
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).
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
/v1/me/player
Spotify endpoint is actually quite flaky (not sure if it's deliberate). Even if a player is active/not asleep (like it's the case of the background Librespot process), it won't appear in the list unless it has recently been paired to a Spotify app/client (but it appears if you go to the "Devices" section of the Spotify app). And a PUT
request to the endpoint (to start the playback) fails unless the device is on the players list.Let me know what you folks think. The alternative I see are:
mopidy-librespot
integration for thisOn 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.
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.
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.
My 2 cents on this discussion, considering @BlackLight 's comments:
libspotify
for, at the very least, playback, until it no longer works, at which point we would have to switch to librespot
.
- Keep using
libspotify
for, at the very least, playback, until it no longer works, at which point we would have to switch tolibrespot
.
I guess this happend faster then expected. Is switching to librespot feasible by now?
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.
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.
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.
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.
https://github.com/dtcooper/raspotify
Installs librespot and has rpi binaries
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
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.
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.
@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.
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.
@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:
Tracks only play if I first open the Spotify client/app and select the Librespot player from the Spotify Connect panel. Unless I do that, the Librespot player doesn't even show under /me/player/devices
, and calls to /me/player/play
with the Librespot device_id
just fail with a 404. Not sure if this is tackled somehow in the Gst plugin.
Librespot's way of dispatching events is quite hackish to say the least. It basically allows an external script to be passed via --onevent
- no callbacks, DBus support etc. This means that, in order to synchronize the status of my app with playback changes performed by another client over Spotify Connect, I've had to resort to a workaround that involves an --onevent
script that pushes events on a Redis queue, and the main app consumes them from the queue. Definitely not a clean nor efficient solution.
When playback is started by another client over Spotify Connect, it can take a while (sometimes 10-15 seconds) before the audio streams are synchronized to Librespot.
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.
I just changed the title in the hopes that people stop creating new duplicate issues for the problem...
Will there be a patch for mopidy spotify? and are there any alternatives to mopidy that work with spotify and snapcast
and are there any alternatives to mopidy that work with spotify and snapcast
Depending on what you want to achieve, raspotify might do that.
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.
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 :)
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:
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).
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.
@BlackLight Thanks for telling me about these two amazing services. I will sure make use of them.
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.
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 :-)
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.
@kingosticks Could you push somewhere so we can test and maybe start packaging work for other distributions?
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.
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?
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.
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?