snapcast icon indicating copy to clipboard operation
snapcast copied to clipboard

Add librespot-java stream source

Open markferry opened this issue 2 years ago • 12 comments

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?

markferry avatar Oct 18 '21 10:10 markferry

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".

efung1232 avatar Nov 15 '21 19:11 efung1232

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

badaix avatar Jul 19 '22 18:07 badaix

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.

excitare avatar Jun 11 '23 14:06 excitare

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.

manfreddz avatar Sep 28 '23 10:09 manfreddz

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

dsheets avatar Nov 29 '23 11:11 dsheets

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.

dsheets avatar Nov 29 '23 14:11 dsheets

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

ChrisIossa avatar Mar 22 '24 04:03 ChrisIossa

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

badaix avatar Mar 26 '24 08:03 badaix

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.

ChrisIossa avatar Mar 26 '24 14:03 ChrisIossa

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

badaix avatar Mar 26 '24 19:03 badaix

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)
)

ChrisIossa avatar Mar 27 '24 22:03 ChrisIossa

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()

badaix avatar Mar 28 '24 09:03 badaix