control

package
v1.8.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 3, 2026 License: AGPL-3.0 Imports: 17 Imported by: 0

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

  • 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". A controller learns that id from its own login, not from this server — /whoami deliberately does NOT echo the user id, so a bystander can't read the credential and replay it. Convenience auth for a trusted LAN: the user copies no token; their own devices just connect. The user id is only semi-private, so this is LAN-trust grade, not a secret.
  • 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. CSRF-safe because the token lives in localStorage (not a cookie) and custom headers cannot be set cross-origin.
  • 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) and auth mode of a server before connecting.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Account

type Account struct {
	UserID string
	Name   string
	Offer  string
}

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

func NewClient(base, token, account string) *Client

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 added in v1.6.0

func (c *Client) CancelSleepTimer() (State, error)

CancelSleepTimer disarms the peer's sleep timer.

func (*Client) CycleRepeat

func (c *Client) CycleRepeat() (State, error)

func (*Client) EQ added in v1.7.0

func (c *Client) EQ() (EQState, error)

EQ returns the peer's equalizer snapshot (enabled, mono downmix, preamp, per-band gains, active preset and the preset/band lists).

func (*Client) Next

func (c *Client) Next() (State, error)

func (*Client) PlayPause

func (c *Client) PlayPause() (State, error)

PlayPause toggles play/pause on the peer.

func (*Client) PlayPlaylist

func (c *Client) PlayPlaylist(id string) (State, error)

func (*Client) PlayTrack

func (c *Client) PlayTrack(id string) (State, error)

func (*Client) Playlists

func (c *Client) Playlists() (json.RawMessage, error)

Playlists returns the raw playlists JSON from the server.

func (*Client) Prev

func (c *Client) Prev() (State, error)

func (*Client) Restart

func (c *Client) Restart() (State, error)

func (*Client) Search

func (c *Client) Search(q string) (json.RawMessage, error)

Search returns the raw search-results JSON from the server.

func (*Client) Seek

func (c *Client) Seek(ms int64) (State, error)

func (*Client) SetEQ added in v1.7.0

func (c *Client) SetEQ(params url.Values) (EQState, error)

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) SetRepeat added in v1.0.1

func (c *Client) SetRepeat(mode string) (State, error)

SetRepeat sets the repeat mode on the peer to mode ("off", "all", or "one").

func (*Client) SetShuffle added in v1.0.1

func (c *Client) SetShuffle(on bool) (State, error)

SetShuffle sets shuffle on (true) or off (false) on the peer.

func (*Client) SetSleepTimer added in v1.6.0

func (c *Client) SetSleepTimer(minutes int, endOfTrack bool) (State, error)

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

func (c *Client) SetVolume(v float64) (State, error)

func (*Client) Status

func (c *Client) Status() (State, error)

Status returns the current playback snapshot.

func (*Client) Stop

func (c *Client) Stop() (State, error)

func (*Client) ToggleShuffle

func (c *Client) ToggleShuffle() (State, error)

func (*Client) Whoami

func (c *Client) Whoami() (Whoami, error)

Whoami fetches the server's identity (name + auth mode).

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)
	SetSleepTimer    func(minutes int, endOfTrack bool) // arm the sleep timer
	CancelSleepTimer func()                             // disarm it
}

Commands are the mutating actions 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 added in v1.7.0

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 added in v1.7.0

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 added in v1.7.0

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 added in v1.7.0

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

func (s *Server) Addr() string

Addr returns the actual listen address (valid after Start).

func (*Server) Close

func (s *Server) Close()

Close stops the server.

func (*Server) DisablePairing added in v1.0.1

func (s *Server) DisablePairing()

DisablePairing clears the pairing code so no new phones can pair. Existing valid session tokens remain usable for their remaining TTL.

func (*Server) EnablePairing added in v1.0.1

func (s *Server) EnablePairing() string

EnablePairing mints a fresh 6-digit code, activates pairing, and returns the code. Safe to call multiple times; each call resets the code.

func (*Server) PairingActive added in v1.0.1

func (s *Server) PairingActive() bool

PairingActive reports whether pairing is currently enabled.

func (*Server) PairingCode added in v1.0.1

func (s *Server) PairingCode() string

PairingCode returns the current 6-digit code (empty when not active).

func (*Server) SetClientInfo

func (s *Server) SetClientInfo(client, device string)

SetClientInfo records the client/platform id + device label for /whoami.

func (*Server) SetEQ added in v1.7.0

func (s *Server) SetEQ(eq *EQ)

SetEQ wires the equalizer bridge (call before Start).

func (*Server) SetVersion

func (s *Server) SetVersion(v string)

SetVersion records the app version reported by /whoami.

func (*Server) Start

func (s *Server) Start() error

Start binds the port and serves in a background goroutine.

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

Whoami is the unauthenticated identity 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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL