badaix / snapcast

Synchronous multiroom audio player
GNU General Public License v3.0
6k stars 447 forks source link

Add librespot-java stream source #928

Open markferry opened 2 years ago

markferry commented 2 years ago

librespot-java provides an API for local control of the spotify client whereas librespot does not.

I'd like to see librespot-java considered as an (autodetected?) alternative for librespot:// or as an additional librespot-java:// stream source.

Counter-argument that librespot-java isn't as active as librespot.

Another counter-argument: just use process://. (What advantage is there internally for snapcast having specific stream sources instead of just process://? Is it the metadata formats?)

For reference spocon packages librespot-java for Debian, etc.

Thoughts?

efung1232 commented 2 years ago

I would like to see the librespot-java get supported. I have just used librespot-api (it is within the librespot-java package) to replace librespot. The advantage of librespot-java is to have the cover art image and metadata available. With librespot-api, "curl -X POST http://localhost:24879/player/next , prev, pause, resume, or etc, can be sent to the spotify host app". I set up librespot-java to send output to the pipe such that I can use equaliser such as alsaequal or eqfa4p in addition to the default sound card with aplay. The aplay command line is "aplay -D eqfa4p -r raw -f cd < myLibrespot-java-pipe.raw".

badaix commented 2 years ago

It seems that there is some bigger refactoring going on with librespot (new-api branch), as you can see in this discussion, they also mention that the java implementation might be ahead in some regards:

The "new Spotify API" means moving large parts of the Spotify protocol from Mercury to HTTP. A lot of this was reverse engineered before by @devgianlu of librespot-java. It was long overdue that we started implementing it too, not in the least because new features like the upcoming Spotify HiFi depend on it.

For testing purposes I've replaced librespot on my server with the java librespot-api and added a glue script to retrieve metadata and control Spotify. You can find the meta_librespot-java.py script in the develop branch. Currently I'm using the process stream and librespot-api is wrapped in a shell script librespot-api.sh:

#!/bin/bash

cd "$(dirname "$0")"
java -jar /home/pi/Develop/librespot-java/librespot-api-1.6.2.jar --logLevel="OFF" --player.output="MIXER" --player.enableNormalisation=false --player.preferredAudioQuality="VERY_HIGH"

logging seems to go to stdout, so I had to disable it.

The stream is setup as follows:

stream = process:////home/pi/Develop/librespot-java/librespot-api.sh?name=Spotify&sampleformat=44100:16:2&controlscript=/home/pi/meta_librespot-java.py

Feel free to test this setup and give feedback

excitare commented 1 year ago

Just for reference if someone else has an issue in the future:
The setting --player.output should be STDOUT, not MIXER unless I missed something.

Otherwise it works perfectly. librespot-java seems to be more responsive regarding Spotify Connect, has much better queue compatibility and also does playback reporting.

manfreddz commented 10 months ago

sed -i 's/<Console name="console" target="SYSTEM_OUT">/<Console name="console" target="SYSTEM_ERR">/' api/src/main/resources/log4j2.xml and sed -i 's/<Console name="console" target="SYSTEM_OUT">/<Console name="console" target="SYSTEM_ERR">/' lib/src/main/resources/log4j2.xml before building librespot-java will get you logging to stderr instead. Not sure if both are necessary. Then you can add log_stderr=true to your source-row in snapserver.conf.

dsheets commented 8 months ago

Regarding @manfreddz's tip, both log4j2.xml changes are necessary.

dsheets commented 8 months ago

You might also run into a difference in the volume control mappings between librespot-org/librespot and librespot-org/librespot-java. See patch at https://github.com/librespot-org/librespot-java/issues/373#issuecomment-1831964719 for a quick and dirty (i.e. no config) implementation of something like librespot's logarithmic volume control but for librespot-java.

ChrisIossa commented 4 months ago

Does meta_librespot-java.py still work? I can't get it to work properly with the new version of Snapweb

badaix commented 4 months ago

@ChrisIossa what exactly is not working for you, with which versions (Snapserver, Snapweb, Librespot)? Unfortunately, my Spotify premium account has expired.

ChrisIossa commented 4 months ago

I was actually having trouble getting the control script working at all. Neither the metadata nor the playback controls were showing up in the recent version of Snapweb. After doing some digging I found out it was because the script and Librespot were starting at the same time (when Snapserver comes up) and since Librespot java takes some time to spin up, the script was trying and failing to connect to the websocket (which wasn't open yet).

Adding a time.sleep delay to the script alleviated this, but I'm not super happy with that fix since I consider it hacky. I'm going to try have it wait for the websocket to open. Once I do, I'll make a PR with my changes. I actually already have some fixes for the MPD control script that I should make a PR for once I clean it up a bit.

badaix commented 4 months ago

I already fixed the same issue in meta_mopidy.py (42678620c672ebb5814f85a1f458afb47e5e3fa0), and applied the same change now to meta_librespot-java.py: be083d2e8e38b0b7abae34adc598a9867caea681

Can you please try if this version fixes the probem? It simply continuously retries to open the websocket to librespot: https://github.com/badaix/snapcast/blob/develop/server/etc/plug-ins/meta_librespot-java.py

ChrisIossa commented 4 months ago

So the change to the websocket loop fixes the errors for when librespot-java is restarted. Thank you for that! However that script doesn't work out of the box. I had to make changes to the WebSocketApp constructor for it to work.

self.websocket = websocket.WebSocketApp(
    url=f"ws://{self._params['librespot-host']}:{self._params['librespot-port']}/events",
    on_message=lambda ws, msg: self.on_ws_message(ws, msg),
    on_error=lambda ws, error: self.on_ws_error(ws, error),
    on_open=lambda ws: self.on_ws_open(ws),
    on_close=lambda ws, close_status_code, close_msg: self.on_ws_close(ws, close_status_code, close_msg)
)
badaix commented 4 months ago

Why do you need these lambdas? The constructor is the same as before, except that the url is now a named parameter. As I understood the old version was working for you (at least when starting delayed). The really same constructor is used in meta_mopidy.py and it's working without a flaw on my server.

Edit: May it be related to the version of the websocket-client library? If merged now the check for the required version from meta_mopidy.py to meta_librespot-java.py:

        wsversion = websocket.__version__.split(".")
        if int(wsversion[0]) == 0 and int(wsversion[1]) < 58:
            logger.error(
                f"websocket-client version 0.58.0 or higher required, installed: {websocket.__version__}, exiting."
            )
            exit()