Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API for Zeroconf connection to other devices on the network #352

Open
pezzy-o opened this issue May 7, 2021 · 37 comments
Open

API for Zeroconf connection to other devices on the network #352

pezzy-o opened this issue May 7, 2021 · 37 comments
Labels
enhancement New feature or request

Comments

@pezzy-o
Copy link

pezzy-o commented May 7, 2021

Problem:
Spotify Web API only allows me to connect to devices which are specifically registered to my account, rather than those devices which advertise themselves over mdns on my local network.

Possible solution:
I'd like to be able to use an API in order to programmatically connect to any Spotify devices on my local network, using Zeroconf. I can see that you've done the hard yards to be able to connect the librespot-java player via Zeroconf: would it be possible to onboard other players using a similar method, and incorporate this into the API?

Background / use case:
I have created an application in Spotify, and I can use it for Spotify Web API calls, including connecting to those devices which are registered to my Spotify account. I use Sonos speakers, which seem to rely more on Zeroconf connections for devices on the local network, so when I use the Spotify Web API to list available devices using the Spotify Web API, unless I've recently connected to the Sonos device specifically through the Spotify app, the Sonos devices do not appear in the list of available devices. The Sonos devices still show up on the Spotify Connect app (they're still advertised and over mdns), just not in the Web API, presumably because they aren't registered specifically to my account, and need to be available to other users on my local network.

@pezzy-o pezzy-o added the enhancement New feature or request label May 7, 2021
@devgianlu
Copy link
Member

So you'd like an API endpoint to list devides available on your network and one to connect to them?

@pezzy-o
Copy link
Author

pezzy-o commented May 7, 2021

Basically yes - that was just a long-winded way of asking, so I could explain the use case.

@devgianlu
Copy link
Member

First part should be relatively easy: just need to make a mDNS probe and get the results. For the second one we need to craft the zeroconf blob which ultimately is a lot of wrapping around stored credentials data.

I'll start by modifying zeroconf-java to allow probing.

devgianlu added a commit that referenced this issue May 7, 2021
@pezzy-o
Copy link
Author

pezzy-o commented May 8, 2021

Awesome - thanks for getting started so quickly! This might be me, but I'm getting an authorisation failure (401) when I try to compile (mvn clean package):
Downloading from github: https://maven.pkg.github.com/devgianlu/*/xyz/gianlu/zeroconf/zeroconf/1.2.1/zeroconf-1.2.1.pom
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary for librespot-java 1.5.6-SNAPSHOT:
[INFO]
[INFO] librespot-java ..................................... SUCCESS [ 0.150 s]
[INFO] librespot-java sink API ............................ SUCCESS [ 2.256 s]
[INFO] librespot-java default sink ........................ SUCCESS [ 0.584 s]
[INFO] librespot-java decoder API ......................... SUCCESS [ 0.385 s]
[INFO] librespot-java lib ................................. FAILURE [ 1.125 s]
[INFO] librespot-java DACP interface ...................... SKIPPED
[INFO] librespot-java player .............................. SKIPPED
[INFO] librespot-java api ................................. SKIPPED
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5.275 s
[INFO] Finished at: 2021-05-08T20:10:59+10:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal on project librespot-lib: Could not resolve dependencies for project xyz.gianlu.librespot:librespot-lib:jar:1.5.6-SNAPSHOT: Failed to collect dependencies at xyz.gianlu.zeroconf:zeroconf:jar:1.2.1: Failed to read artifact descriptor for xyz.gianlu.zeroconf:zeroconf:jar:1.2.1: Could not transfer artifact xyz.gianlu.zeroconf:zeroconf:pom:1.2.1 from/to github (https://maven.pkg.github.com/devgianlu/*): Authentication failed for https://maven.pkg.github.com/devgianlu/*/xyz/gianlu/zeroconf/zeroconf/1.2.1/zeroconf-1.2.1.pom 401 Unauthorized -> [Help 1]

@devgianlu
Copy link
Member

Yep, I know. The CI is also failing, there's something wrong with the CI on zeroconf-java because I had to switch away from Travis and it broke. Hadn't time to fix it yet.

As a workaround you can either clone zeroconf-java and do mvn clean install or download the JAR and install it manually.

@pezzy-o
Copy link
Author

pezzy-o commented May 9, 2021

mvn clean install is giving me the same result. I'm not sure how I would modify the compiled JAR file: I took a look inside the archive and couldn't immediately find the same filenames as were modified in the commit, but I'm not overly familiar with java, maven etc. so might be best for me to just wait until the CI is fixed and I can compile it again. Thanks @devgianlu

@devgianlu
Copy link
Member

@benturnberg You should be good now.

@pezzy-o
Copy link
Author

pezzy-o commented May 10, 2021

Great - I can compile again 🤘

Using the librespot-api-1.6.1-SNAPSHOT.jar, I get the following result when running a POST to http://localhost:24879/discovery/list:

2021-05-10 16:26:25,521 TRACE selector:564 - Selected on sun.nio.ch.WindowsSelectorImpl@4166658f
2021-05-10 16:26:25,521 TRACE selector:583 - Selected key channel=java.nio.channels.SocketChannel[connected local=/[0:0:0:0:0:0:0:1]:24879 remote=/[0:0:0:0:0:0:0:1]:59437], selector=sun.nio.ch.WindowsSelectorImpl@4166658f, interestOps=1, readyOps=1 for java.nio.channels.SocketChannel[connected local=/[0:0:0:0:0:0:0:1]:24879 remote=/[0:0:0:0:0:0:0:1]:59437]
2021-05-10 16:26:25,521 TRACE selector:169 - Calling handleReady key 1 for java.nio.channels.SocketChannel[connected local=/[0:0:0:0:0:0:0:1]:24879 remote=/[0:0:0:0:0:0:0:1]:59437]
2021-05-10 16:26:25,522 TRACE listener:91 - Invoking listener io.undertow.server.protocol.http.HttpReadListener@22e2f427 on channel org.xnio.conduits.ConduitStreamSourceChannel@f641445
2021-05-10 16:26:25,522 TRACE transfer-encoding:119 - No content, starting next request
2021-05-10 16:26:25,522 TRACE selector:539 - Beginning select on sun.nio.ch.WindowsSelectorImpl@4166658f (with timeout)
2021-05-10 16:26:25,522 TRACE nio:550 - Select, queue is empty
2021-05-10 16:26:26,274 TRACE selector:564 - Selected on sun.nio.ch.WindowsSelectorImpl@4166658f
2021-05-10 16:26:26,275 TRACE selector:539 - Beginning select on sun.nio.ch.WindowsSelectorImpl@4166658f (with timeout)
2021-05-10 16:26:26,275 TRACE nio:550 - Select, queue is empty
2021-05-10 16:26:26,274 TRACE HttpServerExchange:1866 - Starting to write response for HttpServerExchange{ POST /discovery/list}
2021-05-10 16:26:26,277 TRACE safe-close:151 - Closing resource io.undertow.server.HttpServerExchange$DefaultBlockingHttpExchange@6da75879
2021-05-10 16:26:26,277 TRACE safe-close:151 - Closing resource io.undertow.server.HttpServerExchange$DefaultBlockingHttpExchange@6da75879
2021-05-10 16:26:27,002 TRACE selector:564 - Selected on sun.nio.ch.WindowsSelectorImpl@4166658f
2021-05-10 16:26:27,002 TRACE nio:611 - Running task io.undertow.util.DateUtils$2@4c001e57
2021-05-10 16:26:27,002 TRACE selector:539 - Beginning select on sun.nio.ch.WindowsSelectorImpl@4166658f (with timeout)
2021-05-10 16:26:27,002 TRACE nio:550 - Select, queue is empty
2021-05-10 16:26:30,906 TRACE selector:564 - Selected on sun.nio.ch.WindowsSelectorImpl@4166658f
2021-05-10 16:26:30,906 TRACE nio:611 - Running task io.undertow.server.protocol.ParseTimeoutUpdater@6b5bf646
2021-05-10 16:26:30,907 TRACE selector:539 - Beginning select on sun.nio.ch.WindowsSelectorImpl@4166658f (with timeout)
2021-05-10 16:26:30,907 TRACE nio:550 - Select, queue is empty

@devgianlu
Copy link
Member

You should get the list of devices in the response body.

@pezzy-o
Copy link
Author

pezzy-o commented May 10, 2021

Sorry, should have included that - the response body is:

{
    "value":  [

              ],
    "Count":  0
}

@pezzy-o
Copy link
Author

pezzy-o commented May 11, 2021

Just to add to this, the API does work for me when invoking /player/next for example... On the other hand, it also returns a blank response when (while connected) I invoke /web-api/v1/me/player/devices.

@devgianlu
Copy link
Member

I think the problem goes back to zeroconf-java not being able to discover your librespot-java client.

@pezzy-o
Copy link
Author

pezzy-o commented May 11, 2021

Anything I can check on my side? I tried disabling AV and firewall just as a sense-check, still the same though.

@devgianlu
Copy link
Member

Turns out there was a bug in zeroconf-java. Should be fixed now.

@pezzy-o
Copy link
Author

pezzy-o commented May 11, 2021

Ok that's good. Now it can see itself, but it's not seeing any other players advertised on the local network:

{  
    "value":  [    
                  {
                      "name":  "librespot-java",
                      "target":  "librespot-java",
                      "port":  57351
                  }
              ],
    "Count":  1
} 

@pezzy-o
Copy link
Author

pezzy-o commented May 11, 2021

... or at least, it's not returning them in the body of the response

@devgianlu
Copy link
Member

After more bug fixing it should be finally good.

@pezzy-o
Copy link
Author

pezzy-o commented May 12, 2021

It's still only recognising itself.

@devgianlu
Copy link
Member

What do you see if you run dns-sd -B _spotify-connect local?

@pezzy-o
Copy link
Author

pezzy-o commented May 12, 2021

The devices are there:

Timestamp     A/R Flags if Domain                    Service Type              Instance Name
16:03:30.999  Add     2 20 local.                    _spotify-connect._tcp.    librespot-java
16:03:31.162  Add     2 20 local.                    _spotify-connect._tcp.    sonos48A6B8XXXXX1
16:03:31.208  Add     2 20 local.                    _spotify-connect._tcp.    sonos48A6B8XXXXX2

@pezzy-o
Copy link
Author

pezzy-o commented Jun 4, 2021

Hey @devgianlu let me know if there's anything else I can do to troubleshoot. Cheers

@devgianlu
Copy link
Member

@benturnberg I just need to find the time to debug zeroconf-java with a Spotify Connect enabled device (likely my TV).

@pezzy-o
Copy link
Author

pezzy-o commented Jun 5, 2021

Thanks @devgianlu , I appreciate it. Finding the time can certainly be difficult!

@sbrunk
Copy link

sbrunk commented Jun 20, 2021

Hi,
I just stumbled upon librespot-java (which looks amazing!) and then this thread when trying to use it. I'm building an NFC remote control to enable the kids to play Spotify albums/playlists on a Sonos speaker by scanning a card with an NFC tag.

Like @benturnberg I can see the speakers with dns-sd:

dns-sd -B _spotify-connect local
Browsing for _spotify-connect._tcp.local
DATE: ---Sun 20 Jun 2021---
13:39:23.219  ...STARTING...
Timestamp     A/R    Flags  if Domain               Service Type         Instance Name
13:39:23.220  Add        3   6 local.               _spotify-connect._tcp. sonosB8E9374XXXXX
13:39:23.220  Add        3   6 local.               _spotify-connect._tcp. sonos7828CAXXXXX2
13:39:23.220  Add        3   6 local.               _spotify-connect._tcp. sonos7828CAXXXXXA
13:39:23.220  Add        3   6 local.               _spotify-connect._tcp. sonos7828CAXXXXX0
13:39:23.220  Add        3   6 local.               _spotify-connect._tcp. librespot-java
13:39:23.220  Add        2   6 local.               _spotify-connect._tcp. Kodi (mediacenter)

But when running the API server (via Coursier)

cs launch -r https://oss.sonatype.org/content/repositories/snapshots xyz.gianlu.librespot:librespot-api:1.6.1-SNAPSHOT -M xyz.gianlu.librespot.api.Main

and then calling the discovery endpoint, I can see only the other Spotify connect devices but not the Sonos speakers:

curl -s -X POST http://localhost:24879/discovery/list | jq
[
  {
    "name": "librespot-java",
    "target": "Sorens-MacBook-Pro",
    "port": 34515
  },
  {
    "name": "Kodi (mediacenter)",
    "target": "mediacenter.local",
    "port": 46233
  }
]

I also ran the discoverer manually (basically the code below from the DiscoveryHandler just to check and got the same result (Sonos speakers not showing up).

Zeroconf zeroconf = new Zeroconf()
.setUseIpv4(true)
.setUseIpv6(false);
try {
zeroconf.addAllNetworkInterfaces();
} catch (IOException ex) {
LOGGER.error("Failed adding network interfaces for Zeroconf.", ex);
}
discoverer = zeroconf.discover(ZeroconfServer.SERVICE, "tcp", ".local");

If there's anything I can do to help debugging the issue, I'd be happy to.

@sbrunk
Copy link

sbrunk commented Jun 20, 2021

Wow that was fast @devgianlu. While looking into zeroconf-java, I just saw that you had released 1.3.1. So I tried it and now the speakers show up on the discovery enpoint!

@devgianlu
Copy link
Member

devgianlu commented Jun 20, 2021

Turns out there was a bug in the code I hadn't written myself. It was working with zeroconf-java only because the packet creation is unoptimized while official clients are.

I was writing the code for the connection, but it can be done in two ways:

  • supplying the username and password to the request
  • connecting with the current user of librespot-java

Let me know if you intended to use it differently.

devgianlu added a commit that referenced this issue Jun 20, 2021
@sbrunk
Copy link

sbrunk commented Jun 20, 2021

I think connecting with the current user of librespot-java would be the best fit for my use-case.

@pezzy-o
Copy link
Author

pezzy-o commented Jun 20, 2021

Current user would work with my use case too. Thanks @devgianlu

@sbrunk
Copy link

sbrunk commented Sep 30, 2021

@devgianlu now that discovery works is there anything I can do to help with the connect part?

@devgianlu
Copy link
Member

I don't really have time to develop this feature. What needs to be done is writing the encryption code for the blob having the one for decryption.

@dsheets
Copy link

dsheets commented Jan 30, 2022

Has anyone made any progress on this?

I have started the patchset to encrypt the blob and make the addUser request to the HTTP server advertised by zeroconf. I thought I'd check in before I do too much more work on the feature. My use case is basically the same as @sbrunk's.

@sbrunk
Copy link

sbrunk commented Feb 1, 2022

@dsheets I haven't looked into this further yet.

@thlucas1
Copy link

Any progress on this? I am trying to make the same addUser request in Python 3. Thanks.

@floodwayprintco
Copy link

Just wanted to add myself in here. I have been trying to get my Spocon setup to show up on the network for things like HomeAssistant and have been struggling. I think this is a similar issue?

I am trying to do the mDNS port forwarding and Google led me here.

Is there any way to wake up Spocon/librespot and show it on Spotify Connect?

@thlucas1
Copy link

thlucas1 commented Oct 10, 2024

Not sure on that, as I am not familiar with “Spocon”. Or is that short for Spotify Connect?

I have had success with getting the integration to work with the spotifyd and Spotify Connect Addon, both of which use librespot under the covers. You just have to configure librespot in discovery mode, and create a credentials.json file.

Checkout the SpotifyPlus Device Configuation Options for librespot wiki doc for more details.

@thlucas1
Copy link

@floodwayprintco
I did some more research on SpoCon, and found the SpoCon web-site and associated configuration docs. I think you can adjust your /opt/spocon/config.toml configuration settings to use the following options for librespot:

# Spotify Connect device name and info
deviceName = "My-Spocon"
deviceType = "COMPUTER" 
preferredLocale = "en"

[auth] # use zeroconf authentication (no username, password, blob)
strategy = "ZEROCONF"

[zeroconf] # listen on all interfaces, and bind to specific port 8600
listenPort = 8600
listenAll = true

[cache] # enable cache path for librespot auth credentials
enabled = true
dir = "./cache/"
doCleanUp = true

That should force SpoCon to advertise itself via zeroconf / mdns on port 8600. I just picked 8600 out of the blue, you can choose whatever port you want. I like a dedicated (vs random) port, in case you need to open firewalls. If you do open firewalls, open activity for 8600 TCP and 5353 UDP (mdns advertise).

I would also avoid spaces in the deviceName parameter, as some Spotify Clients have issues with handling spaces in the name.

You should then be able to start the SpoCon service; after it is started, do the following:

  1. Open the Spotify Desktop client from a machine on the same network as you ran this, ensuring no proxy is in use that may interfere with zeroconf. Note that the Spotify Mobile client does not find devices immediately like the Spotify Desktop client does. It is suggested that you use the Spotify Desktop client for this step.

  2. Use the Spotify client "Connect to a Device" menu to select the device name that you specified for the deviceName config value (My-Spocon in this example).

At this point, librespot should have generated a credentials.json file in the cache path that you specified for the dir config value (./cache/credentials.json in this example).

Please refer to the SpotifyPlus librespot Credentials File topic for further instructions on how to utilize the librespot credentials with the SpotifyPlus integration.

Hope it helps - let me know either way, as you are the first person I have heard of that utilizes SpoCon. I have never used it myself, but based on the docs that I read it should work fine. If it does work, then I will add this to my SpotifyPlus wiki documentation.

@thlucas1
Copy link

@floodwayprintco
Apologies - I just realized that this thread is for the librespot-java process, and not SpotifyPlus integration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

6 participants