Documentation
¶
Overview ¶
Package connect implements OpenDeezer Connect: LAN discovery and remote control of OpenDeezer devices, in both directions.
OpenDeezer Connect is symmetric — this package exposes both sides:
- out — discover and control other devices: Discover + RemoteClient
- in — be discoverable and controllable: Host (and the lower-level Advertise if you manage your own control server)
Transport: IPv4 UDP multicast (group 239.255.42.99, port 7655) with a directed-broadcast fallback so it works on networks that filter multicast. Only LAN/loopback sources are answered; no credentials are exchanged during discovery.
Out: discover devices ¶
devices, err := connect.Discover(2*time.Second, 0)
for _, d := range devices {
fmt.Printf("%s at %s (client: %s)\n", d.Name, d.Addr, d.Client)
}
Out: drive a device ¶
rc := connect.NewRemoteClient(devices[0].Addr, token, "")
st, _ := rc.PlayPause()
fmt.Println("state:", st.State)
In: be a controllable device ¶
Host ties a control endpoint together with LAN advertising, so this process becomes discoverable and accepts the same commands a RemoteClient sends:
host := connect.NewHost(
connect.HostConfig{
Control: connect.Config{Addr: ":7654", SameAccountOnly: true},
Name: "My Player", Client: "myapp", Version: "1.0",
},
func() connect.State { return snapshot() },
func() connect.Account { return connect.Account{UserID: uid, Name: name} },
connect.Commands{PlayPause: player.TogglePause, Stop: player.Stop},
dzClient, // browse routes; nil to disable
)
host.Start()
defer host.Close()
In: advertise only (custom control server) ¶
resp, _ := connect.Advertise(func() connect.AdvertiseInfo {
return connect.AdvertiseInfo{Name: "My Player", Client: "myapp", Version: "1.0"}
}, controlPort)
defer resp.Close()
Index ¶
- Variables
- type Account
- type AdvertiseInfo
- type Commands
- type Config
- type Device
- type EQ
- type EQState
- type Event
- type Host
- type HostConfig
- type RemoteClient
- func (rc *RemoteClient) CancelSleepTimer() (State, error)
- func (rc *RemoteClient) CycleRepeat() (State, error)
- func (rc *RemoteClient) EQ() (EQState, error)
- func (rc *RemoteClient) Events(ctx context.Context) (<-chan State, error)
- func (rc *RemoteClient) EventsTyped(ctx context.Context) (<-chan Event, error)
- func (rc *RemoteClient) HistoryRecent(n int) (json.RawMessage, error)
- func (rc *RemoteClient) Next() (State, error)
- func (rc *RemoteClient) PlayAlbum(id string) (State, error)
- func (rc *RemoteClient) PlayMixArtist(id string) (State, error)
- func (rc *RemoteClient) PlayMixTrack(id string) (State, error)
- func (rc *RemoteClient) PlayPause() (State, error)
- func (rc *RemoteClient) PlayPlaylist(id string) (State, error)
- func (rc *RemoteClient) PlayTrack(id string) (State, error)
- func (rc *RemoteClient) Playlists() (json.RawMessage, error)
- func (rc *RemoteClient) Prev() (State, error)
- func (rc *RemoteClient) QueueAdd(id string, next bool) (State, error)
- func (rc *RemoteClient) QueueJump(index int) (State, error)
- func (rc *RemoteClient) QueueMove(from, to int) (State, error)
- func (rc *RemoteClient) QueueRemove(index int) (State, error)
- func (rc *RemoteClient) Restart() (State, error)
- func (rc *RemoteClient) Search(q string) (json.RawMessage, error)
- func (rc *RemoteClient) SeekMS(ms int64) (State, error)
- func (rc *RemoteClient) SetEQ(params url.Values) (EQState, error)
- func (rc *RemoteClient) SetQueue(tracksJSON string, index int) (State, error)
- func (rc *RemoteClient) SetRepeat(mode string) (State, error)
- func (rc *RemoteClient) SetShuffle(on bool) (State, error)
- func (rc *RemoteClient) SetSleepTimer(minutes int, endOfTrack bool) (State, error)
- func (rc *RemoteClient) SetVolume(v float64) (State, error)
- func (rc *RemoteClient) Status() (State, error)
- func (rc *RemoteClient) Stop() (State, error)
- func (rc *RemoteClient) ToggleShuffle() (State, error)
- func (rc *RemoteClient) Whoami() (Whoami, error)
- type Responder
- type State
- type Track
- type Whoami
Constants ¶
This section is empty.
Variables ¶
var ErrAdvertise = errors.New("connect: control endpoint is serving but LAN advertising failed")
ErrAdvertise is wrapped into the error returned by Host.Start when the control endpoint bound successfully but LAN advertising failed. The endpoint is left serving (reachable by address); callers must still call Host.Close.
Functions ¶
This section is empty.
Types ¶
type Account ¶
type Account = internalcontrol.Account
Account is this device's Deezer identity snapshot, provided to NewHost. It is the same type as control.Account.
type AdvertiseInfo ¶
type AdvertiseInfo = internaldiscovery.Info
AdvertiseInfo is the identity advertised to probers. The function you pass to Advertise returns one of these on each probe so changes (e.g. a re-login updating the display name) are reflected immediately.
type Commands ¶
type Commands = internalcontrol.Commands
Commands are the playback actions an inbound controller can dispatch to this device via a Host. It is the same type as control.Commands.
type Config ¶
type Config = internalcontrol.Config
Config configures the control endpoint a Host exposes (the inbound side). It is the same type as control.Config.
type Device ¶
type Device = internaldiscovery.Device
Device is a discovered OpenDeezer instance.
- Name — the account's display name
- Addr — control API address (host:port) to pass to NewRemoteClient
- Client — client/platform id (e.g. "tui", "macos", "gnome")
- Version — OpenDeezer version
func Discover ¶
Discover multicasts a discovery probe and returns all OpenDeezer devices that reply within timeout.
selfPort is this device's own control port. Replies arriving from a local interface with that port are filtered out so you never list yourself. Pass 0 if you are not running a control server.
Optional staticPeers ("host" or "host:port") are additionally probed via unicast, so known devices answer even on networks that filter multicast/broadcast (e.g. Tailscale/VPN meshes).
type EQ ¶
type EQ = internalcontrol.EQ
EQ is the equalizer bridge a Host can serve at /eq — wire it via Host.Server().SetEQ before Start (see control.PlayerEQ for building one from a player). It is the same type as control.EQ.
type EQState ¶
type EQState = internalcontrol.EQState
EQState is a device's equalizer snapshot, returned by RemoteClient.EQ and RemoteClient.SetEQ. It is the same type as control.EQState.
type Event ¶
type Event = internalcontrol.Event
Event is a typed control event (a "state" snapshot or a "finished" event).
type Host ¶
type Host struct {
// contains filtered or unexported fields
}
Host is the inbound side of OpenDeezer Connect: a control endpoint other devices can drive, advertised on the LAN so Discover finds it. It is the mirror image of RemoteClient (which drives a host) and pairs with Advertise/Discover.
Construct one with NewHost, call Host.Start to bind and advertise, and Host.Close to stop. Host is safe for concurrent use once started.
func NewHost ¶
func NewHost( cfg HostConfig, status func() State, account func() Account, cmds Commands, dz *sdkdeezer.Client, ) *Host
NewHost builds a Connect host.
- cfg — control bind/auth + advertised identity
- status — playback snapshot provider (race-free reads)
- account — logged-in identity provider; its Name is preferred for the advertised name. It is also the same-account auth credential source: with Control.SameAccountOnly set, account must be non-nil or Host.Start fails (every request would 401). Pass nil only when SameAccountOnly is off, to advertise cfg.Name.
- cmds — the actions remote controllers can dispatch (same command set a RemoteClient sends)
- dz — Deezer client for the endpoint's browse routes; nil to disable
func (*Host) Addr ¶
Addr returns the control endpoint's listen address (valid after Host.Start).
func (*Host) Close ¶
func (h *Host) Close()
Close stops advertising and shuts down the control endpoint.
func (*Host) Server ¶
func (h *Host) Server() *sdkcontrol.Server
Server returns the underlying control server so callers can enable web-remote pairing (sdkcontrol.Server.EnablePairing), read the listen address, or override the device label.
func (*Host) Start ¶
Start binds the control endpoint and begins advertising it on the LAN via OpenDeezer Connect (UDP port 7655). The advertised control port is taken from the endpoint's actual listen address.
Advertising is only useful when the control endpoint binds a LAN-reachable (non-loopback) address; a loopback bind is advertised but reachable only on this machine.
When advertising fails, Start leaves the control endpoint serving (reachable by address) and returns an error wrapping ErrAdvertise; the caller must still call Host.Close to release it. Any other error means nothing is running.
type HostConfig ¶
type HostConfig struct {
// Control is the control-endpoint configuration (listen address and auth
// mode). For a LAN-reachable device, bind a non-loopback address such as
// ":7654" and set Token or SameAccountOnly.
Control Config
// Name is the advertised display name. When an account snapshot is provided
// to [NewHost] and reports a non-empty name, that name is used instead so a
// re-login is reflected automatically.
Name string
// Client is the advertised client/platform id (e.g. "myapp").
Client string
// Device is the human device label reported by [RemoteClient.Whoami]
// (e.g. "Kitchen Speaker"). Defaults to Client when empty.
Device string
// Version is the advertised OpenDeezer/app version.
Version string
}
HostConfig configures a Host: how the control endpoint binds and authenticates, plus the identity advertised over LAN discovery.
type RemoteClient ¶
type RemoteClient struct {
// contains filtered or unexported fields
}
RemoteClient drives a discovered OpenDeezer device via its HTTP/JSON control API. All mutation methods return the device's post-command playback snapshot.
Thread-safe.
func NewRemoteClient ¶
func NewRemoteClient(addr, token, accountID string) *RemoteClient
NewRemoteClient builds a remote-control client for the device at addr (host:port as returned by [Device.Addr]). Provide token or accountID depending on the device's auth mode (see RemoteClient.Whoami):
- token auth → token="<bearer>", accountID=""
- account auth → token="", accountID="<your Deezer user id>"
- none → token="", accountID=""
A device reporting "session" auth (WebRemote mode) is driveable only from its embedded browser SPA, which authenticates with a paired session token sent in the X-OpenDeezer-Session header (stored in the browser's localStorage, not a cookie); RemoteClient has no way to supply that credential and cannot control such a device.
func (*RemoteClient) CancelSleepTimer ¶
func (rc *RemoteClient) CancelSleepTimer() (State, error)
CancelSleepTimer disarms the device's sleep timer.
func (*RemoteClient) CycleRepeat ¶
func (rc *RemoteClient) CycleRepeat() (State, error)
CycleRepeat advances the device's repeat mode one step (off → all → one → off).
func (*RemoteClient) EQ ¶
func (rc *RemoteClient) EQ() (EQState, error)
EQ returns the device's equalizer snapshot (enabled, mono downmix, preamp, per-band gains, active preset and the preset/band lists). Errors when the device serves no equalizer bridge (its /eq endpoints are 404).
func (*RemoteClient) Events ¶
func (rc *RemoteClient) Events(ctx context.Context) (<-chan State, error)
Events subscribes to the device's playback state stream (GET /events, Server-Sent Events). Every subscriber receives an initial State snapshot immediately, then a snapshot on each wire-visible change — no polling. The returned channel is closed when ctx is cancelled or the connection to the device drops; there is no automatic reconnect (call Events again). Snapshots are buffered and coalesced: a slow consumer may skip intermediate snapshots but always observes the newest one. It works over LAN like every other method — a discovered device streams its state across the network.
func (*RemoteClient) EventsTyped ¶
func (rc *RemoteClient) EventsTyped(ctx context.Context) (<-chan Event, error)
EventsTyped subscribes to the device's typed event stream (GET /events): "state" snapshots plus explicit "finished" events. The channel closes on ctx cancel or when the stream drops (no auto-reconnect).
func (*RemoteClient) HistoryRecent ¶
func (rc *RemoteClient) HistoryRecent(n int) (json.RawMessage, error)
HistoryRecent returns the device's recent-listening history as raw JSON, most recent first (GET /history/recent). n caps the number of entries (1..500). Errors when the device exposes no history.
func (*RemoteClient) Next ¶
func (rc *RemoteClient) Next() (State, error)
Next skips to the next track.
func (*RemoteClient) PlayAlbum ¶
func (rc *RemoteClient) PlayAlbum(id string) (State, error)
PlayAlbum instructs the device to play the album with the given Deezer id from its first track (replacing the queue).
func (*RemoteClient) PlayMixArtist ¶
func (rc *RemoteClient) PlayMixArtist(id string) (State, error)
PlayMixArtist starts a Deezer mix (radio) seeded by the artist with the given id.
func (*RemoteClient) PlayMixTrack ¶
func (rc *RemoteClient) PlayMixTrack(id string) (State, error)
PlayMixTrack starts a Deezer mix (radio) seeded by the track with the given id.
func (*RemoteClient) PlayPause ¶
func (rc *RemoteClient) PlayPause() (State, error)
PlayPause toggles play/pause on the device.
func (*RemoteClient) PlayPlaylist ¶
func (rc *RemoteClient) PlayPlaylist(id string) (State, error)
PlayPlaylist instructs the device to play the playlist with the given id.
func (*RemoteClient) PlayTrack ¶
func (rc *RemoteClient) PlayTrack(id string) (State, error)
PlayTrack instructs the device to play the track with the given Deezer id.
func (*RemoteClient) Playlists ¶
func (rc *RemoteClient) Playlists() (json.RawMessage, error)
Playlists returns the device account's raw playlists JSON (GET /playlists). Errors when the device exposes no browse endpoints.
func (*RemoteClient) Prev ¶
func (rc *RemoteClient) Prev() (State, error)
Prev jumps to the previous track.
func (*RemoteClient) QueueAdd ¶
func (rc *RemoteClient) QueueAdd(id string, next bool) (State, error)
QueueAdd appends the track with the given Deezer id to the device's queue, or inserts it right after the current track when next is true.
func (*RemoteClient) QueueJump ¶
func (rc *RemoteClient) QueueJump(index int) (State, error)
QueueJump jumps playback to the queue entry at index (0-based).
func (*RemoteClient) QueueMove ¶
func (rc *RemoteClient) QueueMove(from, to int) (State, error)
QueueMove moves the queue entry at position from to position to (both 0-based).
func (*RemoteClient) QueueRemove ¶
func (rc *RemoteClient) QueueRemove(index int) (State, error)
QueueRemove removes the queue entry at index (0-based).
func (*RemoteClient) Restart ¶
func (rc *RemoteClient) Restart() (State, error)
Restart seeks to position 0 in the current track.
func (*RemoteClient) Search ¶
func (rc *RemoteClient) Search(q string) (json.RawMessage, error)
Search returns the device's raw search-results JSON for query q (GET /search). Errors when the device exposes no browse endpoints.
func (*RemoteClient) SeekMS ¶
func (rc *RemoteClient) SeekMS(ms int64) (State, error)
SeekMS seeks to ms milliseconds from the start of the current track.
func (*RemoteClient) SetEQ ¶
func (rc *RemoteClient) SetEQ(params url.Values) (EQState, error)
SetEQ applies a partial equalizer update on the device. params carries any of the POST /eq query params — on=0|1, mono=0|1, preset=name, band=N&db=X, preamp=dB — and the device applies whichever are present (at least one is required).
func (*RemoteClient) SetQueue ¶
func (rc *RemoteClient) SetQueue(tracksJSON string, index int) (State, error)
SetQueue pushes a whole queue to the device (POST /queue/set): tracksJSON is the bridge.Track JSON array and index the cursor (-1 for empty). Groundwork for host-owned casting. Errors when the device exposes no SetQueue.
func (*RemoteClient) SetRepeat ¶
func (rc *RemoteClient) SetRepeat(mode string) (State, error)
SetRepeat sets the repeat mode: "off", "all", or "one".
func (*RemoteClient) SetShuffle ¶
func (rc *RemoteClient) SetShuffle(on bool) (State, error)
SetShuffle enables (true) or disables (false) shuffle on the device.
func (*RemoteClient) SetSleepTimer ¶
func (rc *RemoteClient) SetSleepTimer(minutes int, endOfTrack bool) (State, error)
SetSleepTimer arms the device's sleep timer: playback fades out and pauses after minutes, or when the current track ends if endOfTrack is true (minutes is then ignored).
func (*RemoteClient) SetVolume ¶
func (rc *RemoteClient) SetVolume(v float64) (State, error)
SetVolume sets the volume on the device (0.0 = silent, 1.0 = full).
func (*RemoteClient) Status ¶
func (rc *RemoteClient) Status() (State, error)
Status returns the device's current playback snapshot.
func (*RemoteClient) ToggleShuffle ¶
func (rc *RemoteClient) ToggleShuffle() (State, error)
ToggleShuffle flips shuffle on/off on the device.
func (*RemoteClient) Whoami ¶
func (rc *RemoteClient) Whoami() (Whoami, error)
Whoami fetches the device's identity and auth mode. This endpoint is unauthenticated and can be called before supplying credentials.
type Responder ¶
type Responder = internaldiscovery.Responder
Responder is a running OpenDeezer Connect discovery responder. Stop it with Close.
func Advertise ¶
func Advertise(info func() AdvertiseInfo, controlPort int) (*Responder, error)
Advertise starts an OpenDeezer Connect responder on UDP port 7655. info is called on each incoming probe to get the current display name and version. controlPort is the TCP port of the local control server that controllers should connect to.
Call Close on the returned Responder to stop advertising.
type State ¶
type State = internalcontrol.State
State is the playback snapshot returned by RemoteClient.Status and all mutation methods.
type Whoami ¶
type Whoami = internalcontrol.Whoami
Whoami holds a device's identity and the auth mode it requires.
- Name — account display name (not the user id)
- Offer — plan name (e.g. "Deezer Premium")
- Auth — "token" | "account" | "session" | "none"
- Client — client/platform id
- Device — human device label