Documentation
¶
Overview ¶
Package control exposes playback control + status over a small HTTP/JSON API. It is the shared foundation for remote control (one OpenDeezer client driving another) and the MCP server (an AI agent driving playback). A frontend wires it like the MPRIS bridge: provide a status snapshot func + a set of command callbacks, plus the Deezer client for read-only browse (search/playlists).
Auth has three modes, picked by Config. Credentials are accepted via request HEADERS only (never the query string, which leaks into logs/history), except that GET /events alone accepts ?session= for the browser EventSource API, which cannot set X-OpenDeezer-Session:
- Token: a bearer token in "X-OpenDeezer-Token". Strongest.
- Same-account: no token, but a controller must prove it is logged into the SAME Deezer account by sending its OWN Deezer user id in "X-OpenDeezer-Account". This is accepted directly from loopback clients (backward compat) and from LAN peers (for token-less same-account Connect and the LAN-trust tradeoff); the plaintext id is accepted on non-loopback binds. Pairing via short-TTL single-use 6-digit code may still be used to obtain a per-device session token (X-OpenDeezer-Session) instead.
- Session (web remote): a phone pairs with a 6-digit code minted at enable time; on success it receives a short-lived session token sent as X-OpenDeezer-Session. The code is now single-use + time-limited; failed verifies have lockout. Per-device ids allow individual revocation.
- None: open (only safe bound to localhost).
Mutating endpoints require POST and reject requests carrying a browser Origin header, so a web page the user happens to visit can't drive playback (CSRF). The exception is requests that also carry a valid X-OpenDeezer-Session token: those come from our own SPA (same origin in the browser), so they are allowed. GET /whoami is unauthenticated so a controller can discover the account NAME (not id), auth mode and supported command families before connecting.
Index ¶
- type Account
- type Client
- func (c *Client) CancelSleepTimer() (State, error)
- func (c *Client) CycleRepeat() (State, error)
- func (c *Client) EQ() (EQState, error)
- func (c *Client) Events(ctx context.Context) (<-chan State, error)
- func (c *Client) EventsTyped(ctx context.Context) (<-chan Event, error)
- func (c *Client) HistoryRecent(n int) (json.RawMessage, error)
- func (c *Client) Next() (State, error)
- func (c *Client) PlayAlbum(id string) (State, error)
- func (c *Client) PlayMixArtist(id string) (State, error)
- func (c *Client) PlayMixTrack(id string) (State, error)
- func (c *Client) PlayPause() (State, error)
- func (c *Client) PlayPlaylist(id string) (State, error)
- func (c *Client) PlayTrack(id string) (State, error)
- func (c *Client) Playlists() (json.RawMessage, error)
- func (c *Client) Prev() (State, error)
- func (c *Client) QueueAdd(id string, next bool) (State, error)
- func (c *Client) QueueJump(index int) (State, error)
- func (c *Client) QueueMove(from, to int) (State, error)
- func (c *Client) QueueRemove(index int) (State, error)
- func (c *Client) Restart() (State, error)
- func (c *Client) Search(q string) (json.RawMessage, error)
- func (c *Client) Seek(ms int64) (State, error)
- func (c *Client) SetEQ(params url.Values) (EQState, error)
- func (c *Client) SetQueue(tracksJSON string, index int) (State, error)
- func (c *Client) SetRepeat(mode string) (State, error)
- func (c *Client) SetShuffle(on bool) (State, error)
- func (c *Client) SetSleepTimer(minutes int, endOfTrack bool) (State, error)
- func (c *Client) SetVolume(v float64) (State, error)
- func (c *Client) Status() (State, error)
- func (c *Client) Stop() (State, error)
- func (c *Client) ToggleShuffle() (State, error)
- func (c *Client) Whoami() (Whoami, error)
- type Commands
- type Config
- type EQ
- type EQController
- type EQState
- type Event
- type EventType
- type FinishedEvent
- type Server
- func (s *Server) Addr() string
- func (s *Server) Close()
- func (s *Server) DisablePairing()
- func (s *Server) EnablePairing() string
- func (s *Server) NotifyFinished(trackID string)
- func (s *Server) NotifyStateChanged()
- func (s *Server) PairingActive() bool
- func (s *Server) PairingCode() string
- func (s *Server) RevokeSession(idOrToken string)
- func (s *Server) SetClientInfo(client, device string)
- func (s *Server) SetEQ(eq *EQ)
- func (s *Server) SetVersion(v string)
- func (s *Server) Start() error
- type State
- type Track
- type Whoami
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Account ¶
Account is the controlled client's Deezer identity, supplied by a snapshot provider so the HTTP goroutine never reads the deezer.Client's login fields directly (those are written by Login on another goroutine).
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client talks to a control Server over HTTP. It is the shared driver for the MCP server and the TUI's remote-play feature: both point it at another OpenDeezer client's control API and issue the same commands a local user would.
func NewClient ¶
NewClient builds a control client. base is the server's URL; token/account are the credentials (send whichever the server requires; empty ones are omitted).
func (*Client) CancelSleepTimer ¶
CancelSleepTimer disarms the peer's sleep timer.
func (*Client) CycleRepeat ¶
func (*Client) EQ ¶
EQ returns the peer's equalizer snapshot (enabled, mono downmix, preamp, per-band gains, active preset and the preset/band lists).
func (*Client) Events ¶
Events is the backward-compatible state-only view of EventsTyped. Finished events are ignored; state snapshots retain the historical latest-wins buffering behaviour for slow callers.
func (*Client) EventsTyped ¶
EventsTyped subscribes to all recognized peer events. Events retain stream order and include both complete State snapshots and natural-finish edges. Callers should drain the channel promptly; backpressure affects only this connection. The channel closes when ctx is cancelled or the peer disconnects.
func (*Client) HistoryRecent ¶
func (c *Client) HistoryRecent(n int) (json.RawMessage, error)
HistoryRecent returns the peer's recent-listening history as raw JSON.
func (*Client) Playlists ¶
func (c *Client) Playlists() (json.RawMessage, error)
Playlists returns the raw playlists JSON from the server.
func (*Client) QueueAdd ¶
QueueAdd appends a track to the peer's queue, or inserts it immediately after the current track when next is true.
func (*Client) Search ¶
func (c *Client) Search(q string) (json.RawMessage, error)
Search returns the raw search-results JSON from the server.
func (*Client) SetEQ ¶
SetEQ applies a partial equalizer update. 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 server applies whichever are present (at least one is required).
func (*Client) SetQueue ¶
SetQueue atomically replaces the peer's host-owned queue. tracksJSON must be a bridge.Track JSON array. index identifies its current row, or is -1 when clearing the queue with an empty array.
func (*Client) SetRepeat ¶
SetRepeat sets the repeat mode on the peer to mode ("off", "all", or "one").
func (*Client) SetShuffle ¶
SetShuffle sets shuffle on (true) or off (false) on the peer.
func (*Client) SetSleepTimer ¶
SetSleepTimer arms the peer's sleep timer: pause after `minutes` (with a fade-out), or when the current track ends if endOfTrack is true.
func (*Client) ToggleShuffle ¶
type Commands ¶
type Commands struct {
PlayPause func()
Next func()
Prev func()
Stop func()
Restart func() // seek to 0
CycleRepeat func()
ToggleShuffle func()
SetRepeat func(mode string) // mode: "off"|"all"|"one" (SET variant)
SetShuffle func(on bool) // on: true/false (SET variant)
Seek func(ms int64)
SetVolume func(v float64)
PlayTrack func(id string)
PlayPlaylist func(id string)
// Optional extended-control callbacks return an error for a well-formed
// request that cannot be applied to the current queue/player state. The HTTP
// handlers expose those failures as 409 Conflict; nil means unsupported.
QueueAdd func(id string, next bool) error
SetQueue func(tracksJSON string, index int) error
QueueJump func(index int) error
QueueRemove func(index int) error
QueueMove func(from, to int) error
PlayAlbum func(id string) error
PlayMixTrack func(id string) error
PlayMixArtist func(id string) error
HistoryRecent func(n int) (json.RawMessage, error)
SetSleepTimer func(minutes int, endOfTrack bool) // arm the sleep timer
CancelSleepTimer func() // disarm it
}
Commands are the control callbacks a controller exposes (each may be nil).
type Config ¶
type Config struct {
Addr string // host:port ("127.0.0.1:7654" localhost, ":7654" LAN)
Token string // bearer token; "" disables token auth
SameAccountOnly bool // when Token=="", require a matching Deezer account id
WebRemote bool // allow LAN bind with session (pairing) as the sole auth
}
Config configures the control server.
type EQ ¶
type EQ struct {
State func() EQState
SetEnabled func(on bool)
SetMono func(on bool)
SetPreamp func(db float64)
SetPreset func(name string) error
SetBand func(band int, db float64) error
}
EQ is the optional equalizer bridge; when nil the /eq endpoints 404. Hosts wire the funcs to the audio player (each may be nil individually).
func PlayerEQ ¶
func PlayerEQ(get func() EQController, presets []string) *EQ
PlayerEQ builds the /eq bridge from a live player getter (get may return nil while the engine is starting) and the core's preset-name list.
type EQController ¶
type EQController interface {
EQEnabled() bool
MonoDownmix() bool
EQPreampDB() float64
EQGains() []float64
EQPreset() string
EQBands() []float64
SetEQEnabled(on bool)
SetMonoDownmix(on bool)
SetEQPreamp(db float64)
SetEQPreset(name string) error
SetEQGain(band int, db float64) error
}
EQController is the player subset the /eq endpoint drives; *audio.Player satisfies it (declared here so this package needn't import the engine).
type EQState ¶
type EQState struct {
Enabled bool `json:"enabled"`
Mono bool `json:"mono"`
PreampDB float64 `json:"preampDb"`
GainsDB []float64 `json:"gainsDb"`
Preset string `json:"preset"`
Bands []float64 `json:"bands"`
Presets []string `json:"presets"`
}
EQState is the equalizer snapshot returned by GET /eq (same wire shape as corelib's DZEQJSON / odmobile's EQJSON, so every controller renders one UI).
type Event ¶
type Event struct {
Type EventType
State *State
Finished *FinishedEvent
}
Event is a tagged item from EventsTyped. Exactly one payload pointer is set for the event types currently defined by the control protocol.
type FinishedEvent ¶
type FinishedEvent struct {
TrackID string `json:"trackId"`
}
FinishedEvent is the payload of an SSE "finished" event. Hosts emit it for natural end-of-track only; decode/stream failures and explicit stops are not finishes.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server serves the control API.
func New ¶
func New(cfg Config, status func() State, account func() Account, cmds Commands, client *deezer.Client) *Server
New builds a control server from cfg. status + account are snapshot providers (called from the HTTP goroutine, so they must be race-free reads); client supplies the browse endpoints (search/playlists).
func (*Server) DisablePairing ¶
func (s *Server) DisablePairing()
DisablePairing clears the pairing code so no new phones can pair and revokes all existing session tokens (individual sessions can be revoked via their per-device id). It also disables the web remote SPA.
func (*Server) EnablePairing ¶
EnablePairing mints a fresh 6-digit code, activates pairing, and returns the code. Safe to call multiple times; each call resets the code. The code is single-use and short-TTL (5 minutes).
func (*Server) NotifyFinished ¶
NotifyFinished publishes a natural end-of-track edge to current /events subscribers. It is non-blocking so it is safe to call directly from a player's onFinish callback. Hosts that do not wire this method retain the previous behaviour: controllers can infer finishes from State transitions.
func (*Server) NotifyStateChanged ¶
func (s *Server) NotifyStateChanged()
NotifyStateChanged asks the event loop to publish a fresh snapshot. Calls are deliberately coalesced: the payload is state, not an edge-triggered event, so one pending notification is enough to deliver the newest snapshot.
func (*Server) PairingActive ¶
PairingActive reports whether pairing is currently enabled.
func (*Server) PairingCode ¶
PairingCode returns the current 6-digit code (empty when not active).
func (*Server) RevokeSession ¶
RevokeSession revokes the session by its public identifier (returned as "id" from successful /pair) or by its token. Used for per-device revocation and by account-switch / DisablePairing logic.
func (*Server) SetClientInfo ¶
SetClientInfo records the client/platform id + device label for /whoami.
func (*Server) SetVersion ¶
SetVersion records the app version reported by /whoami.
type State ¶
type State struct {
State string `json:"state"` // playing | paused | stopped | loading | error
Track *Track `json:"track,omitempty"`
PositionMS int64 `json:"positionMs"`
DurationMS int64 `json:"durationMs"`
Volume float64 `json:"volume"` // 0..1
Repeat string `json:"repeat"` // off | all | one
Shuffle bool `json:"shuffle"`
Format string `json:"format,omitempty"`
Queue []Track `json:"queue,omitempty"`
// Sleep timer (0/false when not armed).
SleepActive bool `json:"sleepActive,omitempty"`
SleepEndOfTrack bool `json:"sleepEndOfTrack,omitempty"`
SleepRemainingMS int64 `json:"sleepRemainingMs,omitempty"`
}
State is the playback snapshot returned by GET /status.
type Track ¶
type Track struct {
ID string `json:"id"`
Title string `json:"title"`
Artist string `json:"artist"`
ArtistID string `json:"artistId,omitempty"`
Album string `json:"album"`
Explicit bool `json:"explicit"`
DurationMS int64 `json:"durationMs"`
ArtworkURL string `json:"artworkUrl,omitempty"`
}
Track is a now-playing / queue entry in the API.
type Whoami ¶
type Whoami struct {
Name string `json:"name"`
Offer string `json:"offer,omitempty"`
Auth string `json:"auth"` // token | account | session | none
Version string `json:"version,omitempty"` // OpenDeezer version
Client string `json:"client,omitempty"` // client/platform id (tui, macos, gnome…)
Device string `json:"device,omitempty"` // human device label ("OpenDeezer TUI")
Commands []string `json:"commands"` // supported command and event capabilities
}
Whoami is the unauthenticated identity and command-capability discovery response returned by GET /whoami. It carries the account display NAME (for the controller to recognise its own device) but never the user id: in same-account mode that id IS the credential, so echoing it here would let any bystander read and replay it.