go_librespot

package module
v0.0.0-...-77678e4 Latest Latest
Warning

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

Go to latest
Published: Jun 24, 2026 License: GPL-3.0 Imports: 21 Imported by: 0

README

go-librespot

GitHub release GitHub branch check runs Go Report Card GitHub License

Yet another open-source Spotify Connect compatible client, written in Go.

go-librespot gives you the freedom to have a Spotify Connect device wherever you want.

Getting Started

Using prebuilt binary

To get started you can download prebuilt binaries for the latest release.

Development prebuilt binaries are also available as GitHub Actions artifacts.

Using Docker

A lightweight Docker image for go-librespot is available on the GitHub Container Registry.

An example Docker Compose configuration for PulseAudio is available here.

Using Brew

You can also install go-librespot using Brew on macOS and Linux (thanks @kriive):

brew install go-librespot
Building from source

To build from source the following prerequisites are necessary:

  • Go 1.25 or higher
  • Libraries: libogg, libvorbis, flac, libasound2

To install Go, download it from the Go website.

To install the required libraries on Debian-based systems (Debian, Ubuntu, Raspbian), use:

sudo apt-get install libogg-dev libvorbis-dev libflac-dev libasound2-dev

Once prerequisites are installed you can clone the repository and run the daemon with:

go run ./cmd/daemon

Details about cross-compiling go-librespot are described here (thank you @felixstorm).

Configuration

The default directory for configuration files is ~/.config/go-librespot. On macOS devices, this is ~/Library/Application Support/go-librespot. You can change this directory with the -config_dir flag. The configuration directory contains:

  • config.yml: The main configuration (does not exist by default)
  • state.json: The player state and credentials
  • lockfile: A lockfile to prevent running multiple instances on the same configuration

The full configuration schema is available here, only the main options are detailed below.

Zeroconf Mode and mDNS Backend Selection

Zeroconf mode enables mDNS auto discovery, allowing Spotify clients inside the same network to connect to go-librespot. This is also known as Spotify Connect.

Backend selection:
go-librespot supports two different backends for mDNS service registration:

  • builtin: (default) Uses the built-in mDNS responder provided by go-librespot itself.
  • avahi: Uses the system's avahi-daemon (via D-Bus) for mDNS service registration.

You can configure which backend to use via the zeroconf_backend setting in your configuration file:

zeroconf_backend: avahi   # Options: "builtin" (default), "avahi"

Or via the command line:

go-librespot -c zeroconf_backend=avahi
Which backend should I use?
  • Use avahi if you want to integrate with an existing Avahi daemon, e.g. on embedded systems, to avoid port conflicts, or to centralize mDNS advertisements with system service management (e.g., using systemd).
    • Compatible with Avahi 0.6.x and later (tested with 0.7 and 0.8).
  • Use builtin if you do not have Avahi running and want go-librespot to manage its own mDNS advertisements (no extra dependencies required).
Example minimal Zeroconf configuration
zeroconf_enabled: true # Whether to keep the device discoverable at all times, even if authenticated via other means
zeroconf_port: 0       # The port to use for Zeroconf, 0 for random
zeroconf_backend: avahi
credentials:
  type: zeroconf
  zeroconf:
    persist_credentials: false # Whether to persist zeroconf user credentials even after disconnecting

If persist_credentials is true, after connecting to the device for the first time credentials will be stored locally and you can switch to interactive mode without having to authenticate manually.

If zeroconf_interfaces_to_advertise is provided, you can limit interfaces that will be advertised. For example, if you have Docker installed on your host, you may want to disable advertising to its bridge interface, or you may want to disable interfaces that will not be reachable.

Interactive mode

This mode allows you to associate your account with the device and make it discoverable even outside the network. It requires some manual steps to complete the authentication:

  1. Configure interactive mode

    zeroconf_enabled: false # Whether to keep the device discoverable at all times
    credentials:
      type: interactive
    
  2. Start the daemon to begin the authentication flow

  3. Open the authorization link in your browser and wait to be redirected

  4. If go-librespot is running on the same device as your browser, authentication should complete successfully

  5. (optional) If go-librespot is running on another device, you'll need to copy the URL from your browser and call it from the device, for example:

    curl http://127.0.0.1:36842/login?code=xxxxxxxx
    
API server

Optionally, an API server can be started to control and monitor the player. To enable this feature, add the following to your configuration:

server:
  enabled: true
  address: localhost # Which address to bind to
  port: 3678 # The server port
  allow_origin: '' # Value for the Access-Control-Allow-Origin header
  cert_file: '' # Path to certificate file for TLS
  key_file: '' # Path to key file for TLS
  image_size: 'default' # Album art image size (default, small, large, xlarge)

For detailed API documentation see here.

Volume synchronization

Various configurations for volume control are available:

  1. No external volume without mixer: Spotify volume is controlled independently of the device volume, output samples are multiplied with the volume
  2. No external volume with mixer: Spotify volume is synchronized with the device volume and vice versa, output samples are not volume dependant, Spotify volume changes are applied to the ALSA mixer and vice versa
  3. External volume without mixer: Spotify volume is not synchronized with the device volume, output samples are not volume dependant
  4. External volume with mixer: Device volume is synchronized with Spotify volume, output samples are not volume dependant, volume changes are not applied to the ALSA mixer
Audio normalization

go-librespot implements audio normalization according to Spotify's standards, which targets -14 dB LUFS (Loudness Units relative to Full Scale) based on the ITU-R BS.1770 standard. Source

Normalization can be configured with the following options:

normalisation_disabled: false # Whether to disable normalization (default: false)
normalisation_use_album_gain: false # Whether to use album gain instead of track gain (default: false)
normalisation_pregain: 0 # Pregain in dB to apply before normalization (default: 0)

The pregain is applied on Spotify's -14 dB LUFS target. Spotify suggests the following presets for pregain:

  • Loud: -11 dB LUFS, apply a pregain of +3 dB
  • Normal: -14 dB LUFS, apply a pregain of 0 dB
  • Quiet: -19 dB LUFS, apply a pregain of -5 dB
Additional configuration

The following options are also available:

log_level: info # Log level configuration (trace, debug, info, warn, error)
log_disable_timestamp: false # Whether to disable timestamps in log output
device_id: '' # Spotify device ID (auto-generated)
device_name: '' # Spotify device name
device_type: computer # Spotify device type (icon)
audio_backend: alsa # Audio backend to use (alsa, pipe, pulseaudio)
audio_device: default # ALSA audio device to use for playback
mixer_device: '' # ALSA mixer device for volume synchronization 
mixer_control_name: Master # ALSA mixer control name for volume synchronization
audio_buffer_time: 500000 # Audio buffer time in microseconds, ALSA only
audio_period_count: 4 # Number of periods to request, ALSA only
audio_output_pipe: '' # Path to a named pipe for audio output
audio_output_pipe_format: s16le # Audio output pipe format (s16le, s32le, f32le)
bitrate: 160 # Playback bitrate (96, 160, 320)
volume_steps: 100 # Volume steps count
initial_volume: 100 # Initial volume in steps (not applied to the mixer device)
ignore_last_volume: false # Whether to ignore the last saved volume and always use initial_volume
external_volume: false # Whether volume is controlled externally 
disable_autoplay: false # Whether autoplay of more songs should be disabled

Make sure to check here for the full list of options.

Embedding

To embed go-librespot in your own application (e.g. a TUI or custom client) instead of using the daemon, see docs/embedding.md. It covers session creation (including from a config directory and single OAuth flow), configurable track context size, logger adapters, Web API 429 retry, and image/product helpers.

Development

Protobuf definitions are managed through Buf. To recompile, execute:

buf generate

or using Go:

go generate ./...

Documentation

Index

Constants

View Source
const MaxStateVolume uint32 = 65535

MaxStateVolume is the maximum volume value used in DeviceInfo and PlayerState. Duplicated from player package to avoid circular imports.

View Source
const SpotifyVersionCode = 127700358

Variables

View Source
var (
	ErrMediaRestricted    = errors.New("media is restricted")
	ErrNoSupportedFormats = errors.New("no supported formats")
)
View Source
var ClientId = []byte{0x65, 0xb7, 0x8, 0x7, 0x3f, 0xc0, 0x48, 0xe, 0xa9, 0x2a, 0x7, 0x72, 0x33, 0xca, 0x87, 0xbd}
View Source
var ClientIdHex = hex.EncodeToString(ClientId)
View Source
var UriRegexp = regexp.MustCompile("^spotify:([a-z]+):([0-9a-zA-Z]{21,22})$")

Functions

func BuildPutStateRequest

func BuildPutStateRequest(opts PutStateOpts, reason connectpb.PutStateReason) *connectpb.PutStateRequest

BuildPutStateRequest constructs a PutStateRequest for the given reason. For BECAME_INACTIVE, callers should use Spclient.PutConnectStateInactive directly.

func ContextTrackToProvidedTrack

func ContextTrackToProvidedTrack(typ SpotifyIdType, track *connectpb.ContextTrack) *connectpb.ProvidedTrack

func DefaultDeviceInfo

func DefaultDeviceInfo(opts DeviceInfoOpts) *connectpb.DeviceInfo

DefaultDeviceInfo creates a DeviceInfo with standard capabilities for a Spotify Connect device. Only consumer-specific values need to be provided via opts.

func GetBestImageIdForSize

func GetBestImageIdForSize(images []*metadatapb.Image, size string) []byte

func GetCpuFamily

func GetCpuFamily() spotifypb.CpuFamily

func GetOS

func GetOS() spotifypb.Os

func GetPlatform

func GetPlatform() spotifypb.Platform

func GetPlatformSpecificData

func GetPlatformSpecificData() *clienttokenpb.PlatformSpecificData

func GidToBase62

func GidToBase62(id []byte) string

func NewPlayerState

func NewPlayerState() *connectpb.PlayerState

NewPlayerState creates a fresh PlayerState with sensible defaults.

func NormalizeSpotifyId

func NormalizeSpotifyId(uriOrId string) string

func ObfuscateUsername

func ObfuscateUsername(username string) string

func PlayOrigin

func PlayOrigin(state *connectpb.PlayerState) string

PlayOrigin returns the FeatureIdentifier from a PlayerState's PlayOrigin.

func SetPaused

func SetPaused(state *connectpb.PlayerState, paused bool)

SetPaused sets the IsPaused flag and PlaybackSpeed on a PlayerState. PlaybackSpeed must be 0 when paused, or Spotify Android will have subtle bugs.

func SpotifyLikeClientVersion

func SpotifyLikeClientVersion() string

func SystemInfoString

func SystemInfoString() string

func TrackPosition

func TrackPosition(state *connectpb.PlayerState, durationMs int64) int64

TrackPosition computes the current playback position in milliseconds from the PlayerState's timestamp and position fields. It handles paused/playback state, stale timestamps, and negative values. If durationMs > 0, the result is clamped to not exceed the duration.

func UpdateTimestamp

func UpdateTimestamp(state *connectpb.PlayerState, durationMs int64)

UpdateTimestamp refreshes the PlayerState's timestamp and position fields to reflect how much playback has advanced since the last update. If durationMs > 0, the position is clamped to not exceed the duration.

func UserAgent

func UserAgent() string

func VersionNumberString

func VersionNumberString() string

func VersionString

func VersionString() string

Types

type AppState

type AppState struct {
	sync.Mutex

	DeviceId     string          `json:"device_id"`
	EventManager json.RawMessage `json:"event_manager"`
	Credentials  struct {
		Username string `json:"username"`
		Data     []byte `json:"data"`
	} `json:"credentials"`
	LastVolume *uint32 `json:"last_volume"`
	// contains filtered or unexported fields
}

func (*AppState) Read

func (s *AppState) Read(configDir string) error

func (*AppState) SetLogger

func (s *AppState) SetLogger(log Logger)

func (*AppState) Write

func (s *AppState) Write() error

type AudioSource

type AudioSource interface {
	io.Closer

	// SetPositionMs sets the new position in samples
	SetPositionMs(int64) error

	// PositionMs gets the position in samples
	PositionMs() int64

	// Read reads 32bit little endian floats from the stream
	Read([]float32) (int, error)
}

type DeviceInfoOpts

type DeviceInfoOpts struct {
	DeviceName      string
	DeviceId        string
	DeviceType      devicespb.DeviceType
	ClientId        string
	VolumeSteps     uint32
	ZeroconfEnabled bool
}

DeviceInfoOpts holds consumer-specific values for DefaultDeviceInfo.

type Float32Reader

type Float32Reader interface {
	// Read reads 32bit little endian floats from the stream
	// until EOF or ErrDrainReader is returned.
	Read([]float32) (n int, err error)
}

type GetAddressFunc

type GetAddressFunc func(ctx context.Context) string

GetAddressFunc is a function that everytime it is called returns a different address for that type of endpoint.

type GetLogin5TokenFunc

type GetLogin5TokenFunc func(ctx context.Context, force bool) (string, error)

GetLogin5TokenFunc is a function that everytime it is called returns a valid login5 access token.

type Logger

type Logger interface {
	Tracef(format string, args ...any)
	Debugf(format string, args ...any)
	Infof(format string, args ...any)
	Warnf(format string, args ...any)
	Errorf(format string, args ...any)

	Trace(args ...any)
	Debug(args ...any)
	Info(args ...any)
	Warn(args ...any)
	Error(args ...any)

	WithField(key string, value any) Logger
	WithError(err error) Logger
}

type LogrusAdapter

type LogrusAdapter struct {
	Log *logrus.Entry
}

func (LogrusAdapter) Debug

func (l LogrusAdapter) Debug(args ...any)

func (LogrusAdapter) Debugf

func (l LogrusAdapter) Debugf(format string, args ...any)

func (LogrusAdapter) Error

func (l LogrusAdapter) Error(args ...any)

func (LogrusAdapter) Errorf

func (l LogrusAdapter) Errorf(format string, args ...any)

func (LogrusAdapter) Info

func (l LogrusAdapter) Info(args ...any)

func (LogrusAdapter) Infof

func (l LogrusAdapter) Infof(format string, args ...any)

func (LogrusAdapter) Trace

func (l LogrusAdapter) Trace(args ...any)

func (LogrusAdapter) Tracef

func (l LogrusAdapter) Tracef(format string, args ...any)

func (LogrusAdapter) Warn

func (l LogrusAdapter) Warn(args ...any)

func (LogrusAdapter) Warnf

func (l LogrusAdapter) Warnf(format string, args ...any)

func (LogrusAdapter) WithError

func (l LogrusAdapter) WithError(err error) Logger

func (LogrusAdapter) WithField

func (l LogrusAdapter) WithField(key string, value any) Logger

type LogrusAdapterWithStderrAuth

type LogrusAdapterWithStderrAuth struct {
	Log          *logrus.Entry
	StderrPrefix string
	AuthKeywords []string
}

func (LogrusAdapterWithStderrAuth) Debug

func (l LogrusAdapterWithStderrAuth) Debug(args ...any)

func (LogrusAdapterWithStderrAuth) Debugf

func (l LogrusAdapterWithStderrAuth) Debugf(format string, args ...any)

func (LogrusAdapterWithStderrAuth) Error

func (l LogrusAdapterWithStderrAuth) Error(args ...any)

func (LogrusAdapterWithStderrAuth) Errorf

func (l LogrusAdapterWithStderrAuth) Errorf(format string, args ...any)

func (LogrusAdapterWithStderrAuth) Info

func (l LogrusAdapterWithStderrAuth) Info(args ...any)

func (LogrusAdapterWithStderrAuth) Infof

func (l LogrusAdapterWithStderrAuth) Infof(format string, args ...any)

func (LogrusAdapterWithStderrAuth) Trace

func (l LogrusAdapterWithStderrAuth) Trace(args ...any)

func (LogrusAdapterWithStderrAuth) Tracef

func (l LogrusAdapterWithStderrAuth) Tracef(format string, args ...any)

func (LogrusAdapterWithStderrAuth) Warn

func (l LogrusAdapterWithStderrAuth) Warn(args ...any)

func (LogrusAdapterWithStderrAuth) Warnf

func (l LogrusAdapterWithStderrAuth) Warnf(format string, args ...any)

func (LogrusAdapterWithStderrAuth) WithError

func (l LogrusAdapterWithStderrAuth) WithError(err error) Logger

func (LogrusAdapterWithStderrAuth) WithField

func (l LogrusAdapterWithStderrAuth) WithField(key string, value any) Logger

type Media

type Media struct {
	// contains filtered or unexported fields
}

func NewMediaFromEpisode

func NewMediaFromEpisode(episode *metadatapb.Episode) *Media

func NewMediaFromTrack

func NewMediaFromTrack(track *metadatapb.Track) *Media

func (Media) Duration

func (te Media) Duration() int32

func (Media) Episode

func (te Media) Episode() *metadatapb.Episode

func (Media) Id

func (te Media) Id() SpotifyId

func (Media) IsEpisode

func (te Media) IsEpisode() bool

func (Media) IsTrack

func (te Media) IsTrack() bool

func (Media) Name

func (te Media) Name() string

func (Media) Restriction

func (te Media) Restriction() []*metadatapb.Restriction

func (Media) Track

func (te Media) Track() *metadatapb.Track

type NullLogger

type NullLogger struct{}

func (*NullLogger) Debug

func (l *NullLogger) Debug(...any)

func (*NullLogger) Debugf

func (l *NullLogger) Debugf(string, ...any)

func (*NullLogger) Error

func (l *NullLogger) Error(...any)

func (*NullLogger) Errorf

func (l *NullLogger) Errorf(string, ...any)

func (*NullLogger) Info

func (l *NullLogger) Info(...any)

func (*NullLogger) Infof

func (l *NullLogger) Infof(string, ...any)

func (*NullLogger) Trace

func (l *NullLogger) Trace(...any)

func (*NullLogger) Tracef

func (l *NullLogger) Tracef(string, ...any)

func (*NullLogger) Warn

func (l *NullLogger) Warn(...any)

func (*NullLogger) Warnf

func (l *NullLogger) Warnf(string, ...any)

func (*NullLogger) WithError

func (l *NullLogger) WithError(error) Logger

func (*NullLogger) WithField

func (l *NullLogger) WithField(string, any) Logger

type PageResolver

type PageResolver[T any] interface {
	Page(ctx context.Context, idx int) ([]T, error)
}

type PutStateOpts

type PutStateOpts struct {
	Device                    *connectpb.DeviceInfo
	PlayerState               *connectpb.PlayerState
	Active                    bool
	ActiveSince               time.Time
	LastCommandMsgId          uint32
	LastCommandSentByDeviceId string
	HasBeenPlayingForMs       uint64
}

PutStateOpts holds the values needed to build a PutStateRequest.

type SizedReadAtSeeker

type SizedReadAtSeeker interface {
	io.ReadSeeker
	io.ReaderAt

	Size() int64
}

type SpotifyId

type SpotifyId struct {
	// contains filtered or unexported fields
}

func SpotifyIdFromBase62

func SpotifyIdFromBase62(typ SpotifyIdType, id string) (*SpotifyId, error)

func SpotifyIdFromGid

func SpotifyIdFromGid(typ SpotifyIdType, id []byte) SpotifyId

func SpotifyIdFromUri

func SpotifyIdFromUri(uri string) (_ *SpotifyId, err error)

func (SpotifyId) Base62

func (id SpotifyId) Base62() string

func (SpotifyId) Hex

func (id SpotifyId) Hex() string

func (SpotifyId) Id

func (id SpotifyId) Id() []byte

func (SpotifyId) String

func (id SpotifyId) String() string

func (SpotifyId) Type

func (id SpotifyId) Type() SpotifyIdType

func (SpotifyId) Uri

func (id SpotifyId) Uri() string

type SpotifyIdType

type SpotifyIdType string
const (
	SpotifyIdTypeTrack    SpotifyIdType = "track"
	SpotifyIdTypeEpisode  SpotifyIdType = "episode"
	SpotifyIdTypePlaylist SpotifyIdType = "playlist"
)

func InferSpotifyIdTypeFromContextUri

func InferSpotifyIdTypeFromContextUri(uri string) SpotifyIdType

Jump to

Keyboard shortcuts

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