Jump to section
Packages
Four packages, one module. Mix and match: API-only tools skip
sdk/player; non-Go clients can call the HTTP
control API directly without importing anything.
| Package | Purpose |
|---|---|
github.com/Cycl0o0/OpenDeezer/sdk/deezer |
Deezer API client + track decode/download |
github.com/Cycl0o0/OpenDeezer/sdk/connect |
OpenDeezer Connect — LAN discovery, drive or host a device |
github.com/Cycl0o0/OpenDeezer/sdk/control |
Control server + client (HTTP/JSON API + phone web remote) |
github.com/Cycl0o0/OpenDeezer/sdk/player |
In-process audio playback (requires cgo) |
Full Go docs: pkg.go.dev/github.com/Cycl0o0/OpenDeezer/sdk
Authentication (ARL)
Deezer uses a long-lived cookie called the ARL
(Audio Reference Link) for authentication. Get yours from a browser
session: open deezer.com, press
F12,
go to Application → Cookies → arl.
import dz "github.com/Cycl0o0/OpenDeezer/sdk/deezer"
client := dz.New(os.Getenv("DEEZER_ARL"))
if err := client.Login(); err != nil {
// errors.Is(err, dz.ErrARLExpired) → ARL needs refreshing
log.Fatal(err)
}
acc := client.Account()
fmt.Printf("Hello %s (%s)\n", acc.Name, acc.Offer)
The ARL only leaves your machine as HTTPS requests to
deezer.com and media.deezer.com.
Treat it like a password — it grants full access to your account.
Browse
Search, global charts, your library, artist profiles and synced
lyrics — all from the same client.
// Search
results, _ := client.Search("Radiohead")
for _, t := range results.Tracks {
fmt.Println(t.Name, "—", t.ArtistLine())
}
// Charts (global top 50)
charts, _ := client.Charts("0")
// Favorites / playlists (requires login)
tracks, _ := client.Favorites()
playlists, _ := client.Playlists()
// Artist profile
page, _ := client.ArtistProfile("27") // Radiohead
fmt.Println(page.Artist.Name, page.Artist.NbFans, "fans")
// Lyrics
lyr, _ := client.Lyrics(trackID)
if lyr.IsSynced() {
for _, line := range lyr.Synced {
fmt.Printf("%d ms %s\n", line.TimeMS, line.Text)
}
}
Download & decode a track
PrepareStream resolves the CDN URL and
decryption key. DownloadTrack fetches,
Blowfish-decrypts (BF_CBC_STRIPE), and writes the audio bytes to any
io.Writer.
import (
"os"
dz "github.com/Cycl0o0/OpenDeezer/sdk/deezer"
)
client.SetQuality(dz.QualityHigh) // prefer MP3 320; falls back if not entitled
plan, err := client.PrepareStream(trackID)
if err != nil {
log.Fatal(err)
}
fmt.Println("Format:", dz.FormatLabel(plan.Format)) // "MP3 · 320 kbps"
f, _ := os.Create("track.mp3")
defer f.Close()
if err := dz.DownloadTrack(plan, f); err != nil {
log.Fatal(err)
}
Quality levels
| Constant | Format | Requires |
|---|---|---|
dz.QualityNormal |
MP3 128 kbps | Any account |
dz.QualityHigh |
MP3 320 kbps | Premium |
dz.QualityLossless |
FLAC | HiFi |
Deezer falls back to the highest quality the account is entitled to automatically.
Decrypt an in-memory buffer
plain, err := dz.DecryptBytes(trackID, encryptedBytes)
OpenDeezer Connect
LAN discovery and device handoff, like Spotify Connect. The SDK is symmetric: you can drive another device (out) or be driven by one (in). Both sides cover the same transport command set: play/pause, next, prev, stop, restart, seek, volume, repeat, shuffle, play-track, play-playlist.
| Direction | What | API |
|---|---|---|
| out | Discover + control other devices | connect.Discover, connect.RemoteClient |
| in | Be discoverable + controllable | connect.Host (or connect.Advertise for discovery only) |
Out — discover and drive a device
import "github.com/Cycl0o0/OpenDeezer/sdk/connect"
// Find devices (2-second probe window).
devices, _ := connect.Discover(2*time.Second, 0)
for _, d := range devices {
fmt.Printf("%s at %s\n", d.Name, d.Addr)
}
// Drive the first device (same-account auth — no token needed).
rc := connect.NewRemoteClient(devices[0].Addr, "", myUserID)
st, _ := rc.PlayPause()
fmt.Println("state:", st.State)
In — be a controllable device
connect.Host ties a control endpoint together
with LAN advertising. It accepts the same command set 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 currentState() },
func() connect.Account {
a := client.Account()
return connect.Account{UserID: a.UserID, Name: a.Name, Offer: a.Offer}
},
connect.Commands{
PlayPause: player.TogglePause,
Stop: player.Stop,
SetVolume: player.SetVolume,
PlayTrack: playByID,
},
client, // browse routes; nil to disable
)
host.Start()
defer host.Close()
// host.Server().EnablePairing() to also accept the phone web remote.
If you already run your own control server, advertise the discovery half alone:
resp, _ := connect.Advertise(func() connect.AdvertiseInfo {
return connect.AdvertiseInfo{Name: "My Player", Client: "myapp", Version: "1.0"}
}, controlPort)
defer resp.Close()
Auth modes for RemoteClient
Check Whoami.Auth on the target device to
know which credential to supply:
| Auth value | What to pass |
|---|---|
"token" |
Pass token="<bearer-token>" |
"account" |
Pass accountID="<your Deezer user id>" |
"session" |
Use control.NewClient and pair via GET /remote |
"none" |
Empty strings |
Remote control API
The control API is also symmetric:
control.Server hosts a controllable endpoint
(in), control.Client drives one
(out). Non-Go clients can hit the same HTTP endpoints
directly — this is the “and more”: any language,
any HTTP client.
Enable the server
export OPENDEEZER_CONTROL=1 # localhost only (127.0.0.1:7654)
export OPENDEEZER_CONTROL=:7654 # bind all interfaces (LAN remote)
# or:
echo 1 > ~/.config/opendeezer/control.txt
Control server (in)
Host a controllable endpoint that phones, AI agents, or other OpenDeezer clients can drive.
import "github.com/Cycl0o0/OpenDeezer/sdk/control"
srv := control.NewServer(
control.Config{
Addr: ":7654",
Token: "my-secret-token",
},
func() control.State { return currentState() },
func() control.Account {
a := client.Account()
return control.Account{UserID: a.UserID, Name: a.Name, Offer: a.Offer}
},
control.Commands{
PlayPause: player.TogglePause,
Next: queue.Next,
Stop: player.Stop,
Seek: player.SeekMS,
SetVolume: player.SetVolume,
},
client, // for GET /search and GET /playlists; nil to disable
)
srv.SetVersion("1.0")
srv.Start()
defer srv.Close()
Phone web remote
Enable pairing to let a phone control playback via a browser at
http://<host>:7654/remote.
Display the 6-digit code to the user; they enter it in the browser UI.
srv := control.NewServer(
control.Config{Addr: ":7654", WebRemote: true},
// ... same state/account/commands as above
)
srv.Start()
code := srv.EnablePairing() // display this 6-digit code to the user
fmt.Printf("Open http://192.168.1.X:7654/remote — code: %s\n", code)
Control client (out)
c := control.NewClient("http://192.168.1.5:7654", "my-secret-token", "")
st, _ := c.Status()
fmt.Println(st.State, st.Track.Title)
c.SetVolume(0.8)
c.SeekMS(30000)
c.PlayTrack("3135556")
HTTP endpoint reference
The API speaks plain HTTP/JSON, so any language
can use it. Reads are GET, mutations are
POST. Mutations reject a browser
Origin header (already the case for native
clients and curl).
| Method | Path | Action |
|---|---|---|
GET | /whoami | Account name + auth mode (unauthenticated) |
GET | /status | Playback snapshot: state, track, position, volume, queue |
GET | /playlists | User’s playlist list |
GET | /search?q= | Search tracks |
POST | /playpause | Toggle play / pause |
POST | /next | Skip to next track |
POST | /prev | Previous track |
POST | /stop | Stop playback |
POST | /restart | Restart current track from the beginning |
POST | /seek?ms= | Seek to position in milliseconds |
POST | /volume?v= | Set volume (0.0–1.0) |
POST | /repeat | Cycle repeat mode |
POST | /shuffle | Toggle shuffle |
POST | /play/track?id= | Play a track by Deezer track id |
POST | /play/playlist?id= | Play a playlist by id |
GET | /remote | Phone web remote UI (requires WebRemote: true) |
POST | /pair?code= | Pair a phone web remote session with the 6-digit code |
Auth
Credentials go in request headers only:
• X-OpenDeezer-Token: <token> — strongest; set via OPENDEEZER_CONTROL_TOKEN.
• X-OpenDeezer-Account: <deezer-user-id> — same-account (LAN-trust); default when bound to a non-loopback address with no token.
• No header — open on localhost only. A LAN bind always requires one of the two — the server refuses to start unauthenticated on a non-loopback address.
curl examples
# Playback status (same-account auth)
curl http://192.168.1.5:7654/status \
-H "X-OpenDeezer-Account: <your_user_id>"
# Toggle play/pause (token auth)
curl -X POST http://192.168.1.5:7654/playpause \
-H "X-OpenDeezer-Token: my-secret-token"
# Seek to 30 seconds
curl -X POST "http://192.168.1.5:7654/seek?ms=30000" \
-H "X-OpenDeezer-Token: my-secret-token"
# Set volume to 80%
curl -X POST "http://192.168.1.5:7654/volume?v=0.8" \
-H "X-OpenDeezer-Token: my-secret-token"
# Play a track by id
curl -X POST "http://192.168.1.5:7654/play/track?id=3135556" \
-H "X-OpenDeezer-Token: my-secret-token"
# Search
curl "http://192.168.1.5:7654/search?q=daft+punk" \
-H "X-OpenDeezer-Token: my-secret-token"
In-process playback
The sdk/player package wraps the audio engine
(miniaudio via malgo, or oto, selected by build tag). It requires cgo.
Omit this import if you only need API access, search, or
download/decrypt — the other three packages are pure Go.
import (
"github.com/Cycl0o0/OpenDeezer/sdk/player"
dz "github.com/Cycl0o0/OpenDeezer/sdk/deezer"
)
p, _ := player.NewPlayer()
defer p.Close()
p.SetReplayGain(true)
p.SetVolume(0.9)
// Play a track.
plan, _ := client.PrepareStream(trackID)
p.Play(plan, track.DurationMS)
// Advance queue when track ends.
p.SetOnFinish(func() { /* load next */ })
// Gapless: preload the next track before this one ends.
nextPlan, _ := client.PrepareStream(nextTrackID)
p.Preload(nextPlan, nextTrack.DurationMS)
Examples
Runnable examples live in
examples/
in the repo. Each one is a standalone main package.
| Directory | Direction | What it shows |
|---|---|---|
examples/search |
— | Login + search + print results |
examples/download |
— | Login → PrepareStream → DownloadTrack → file |
examples/connect |
out | Discover LAN devices + send PlayPause |
examples/host |
in | Be a discoverable, controllable Connect device |
examples/remote-server |
in | Host a control server + poll it via the client |
DEEZER_ARL=<your_arl> go run ./examples/search "Daft Punk"
DEEZER_ARL=<your_arl> go run ./examples/download 3135556
DEEZER_ARL=<your_arl> go run ./examples/connect
DEEZER_ARL=<your_arl> go run ./examples/host
DEEZER_ARL=<your_arl> go run ./examples/remote-server