player

package
v0.9.0 Latest Latest
Warning

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

Go to latest
Published: Aug 16, 2026 License: GPL-3.0 Imports: 28 Imported by: 0

Documentation

Index

Constants

View Source
const (
	SampleRate = 44100
	Channels   = 2
)
View Source
const CdnUrlQuarantineDuration = 15 * time.Minute
View Source
const MaxStateVolume = 65535

Variables

View Source
var ErrPlayerClosed = errors.New("player is closed")

Functions

func GetFormatBitrate added in v0.3.0

func GetFormatBitrate(format metadatapb.AudioFile_Format) int

func GetFormatCodec added in v0.9.0

func GetFormatCodec(format metadatapb.AudioFile_Format) string

GetFormatCodec returns the codec name of a format, for reporting through the API. Note that a format may be named here without being decodable.

func NarrationLoudness added in v0.9.0

func NarrationLoudness(metadata map[string]string, prefix string) (loudnessDb, truePeakDb float32, ok bool)

NarrationLoudness reads the loudness and true peak a track's narration metadata declares. Reporting absence matters: taking a missing value as zero would mean normalising against 0 LUFS and attenuating the clip to nothing.

Types

type AudioFormat added in v0.5.1

type AudioFormat int
const (
	AudioFormatUnknown AudioFormat = iota
	AudioFormatFLAC
	AudioFormatOGGVorbis
	AudioFormatMP3
	AudioFormatAAC
)

func GetAudioFileFormatAudioFormat added in v0.5.1

func GetAudioFileFormatAudioFormat(format metadatapb.AudioFile_Format) AudioFormat

GetAudioFileFormatAudioFormat maps metadatapb.AudioFile_Format to AudioFormat

type Event

type Event struct {
	Type EventType
}

type EventManager added in v0.3.0

type EventManager interface {
	PreStreamLoadNew(playbackId []byte, spotId librespot.SpotifyId, mediaPosition int64)
	PostStreamResolveAudioFile(playbackId []byte, targetBitrate int32, media *librespot.Media, file *metadata.AudioFile)
	PostStreamRequestAudioKey(playbackId []byte)
	PostStreamResolveStorage(playbackId []byte)
	PostStreamInitHttpChunkReader(playbackId []byte, reader *audio.HttpChunkedReader)

	OnPrimaryStreamUnload(stream *Stream, pos int64)
	PostPrimaryStreamLoad(stream *Stream, paused bool)

	OnPlayerPlay(stream *Stream, ctxUri string, shuffle bool, playOrigin *connectpb.PlayOrigin, track *connectpb.ProvidedTrack, pos int64)
	OnPlayerResume(stream *Stream, pos int64)
	OnPlayerPause(stream *Stream, ctxUri string, shuffle bool, playOrigin *connectpb.PlayOrigin, track *connectpb.ProvidedTrack, pos int64)
	OnPlayerSeek(stream *Stream, oldPos, newPos int64)
	OnPlayerSkipForward(stream *Stream, pos int64, skipTo bool)
	OnPlayerSkipBackward(stream *Stream, pos int64)
	OnPlayerEnd(stream *Stream, pos int64)

	Close()
}

type EventType

type EventType int
const (
	EventTypePlay EventType = iota
	EventTypeResume
	EventTypePause
	EventTypeStop
	EventTypeNotPlaying
)

type NarratedSource added in v0.9.0

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

NarratedSource plays a track with the DJ talking around it: an introduction before it and a closing remark after it, either of which may be absent.

Playback is sequential rather than mixed, matching the ms_narration_overlapping of 0 the official client reports. Position stays the track's throughout, so it reads as not yet started while the DJ introduces it.

func NewNarratedSource added in v0.9.0

func NewNarratedSource(log librespot.Logger, intro, main, outro librespot.AudioSource) *NarratedSource

NewNarratedSource wraps a track with its narration. Either clip may be nil.

func (*NarratedSource) Close added in v0.9.0

func (s *NarratedSource) Close() error

func (*NarratedSource) NoCrossfade added in v0.9.0

func (s *NarratedSource) NoCrossfade() bool

NoCrossfade keeps a closing remark out of the crossfade reserve, which would otherwise spend the whole of it fading out. A source with only an introduction ends in ordinary music and still crossfades.

func (*NarratedSource) PositionMs added in v0.9.0

func (s *NarratedSource) PositionMs() int64

func (*NarratedSource) Read added in v0.9.0

func (s *NarratedSource) Read(p []float32) (int, error)

func (*NarratedSource) SetPositionMs added in v0.9.0

func (s *NarratedSource) SetPositionMs(pos int64) error

SetPositionMs seeks the track, abandoning an introduction still playing: a listener who scrubs wants the music. The closing remark still follows, since it belongs to the end of the track.

type NarrationSource added in v0.9.0

type NarrationSource struct {
	*mp3.Decoder
}

NarrationSource plays a DJ narration clip: MP3 fetched from a CDN with a plain unauthenticated GET, with no Spotify id, audio key or PlayPlay involved.

The whole clip is buffered. It is small, and the decoder needs to seek.

func NewNarrationSource added in v0.9.0

func NewNarrationSource(ctx context.Context, log librespot.Logger, client *http.Client,
	url string, loudnessDb, truePeakDb, pregain float32) (*NarrationSource, error)

NewNarrationSource fetches a synthesized narration clip and decodes it.

The url from Spclient.NarrationUrl is already signed, so it is fetched without the Authorization header the rest of the client sends. The levels come from the track's narration.*.loudness and narration.*.true_peak metadata.

type Options added in v0.2.0

type Options struct {
	Spclient *spclient.Spclient
	AudioKey *audio.KeyProvider
	Events   EventManager

	Log librespot.Logger

	// Cache, when non-nil, is used to store and retrieve encrypted audio files
	// on disk, avoiding a CDN download when a track is played again.
	Cache *cache.Cache

	// FlacEnabled specifies if FLAC files should be preferred when available.
	// When setting this to true, it is assumed that the PlayPlay plugin is provided.
	FlacEnabled bool

	// NormalisationEnabled specifies if the volume should be normalised according
	// to Spotify parameters. Only track normalization is supported.
	NormalisationEnabled bool
	// NormalisationUseAlbumGain specifies whether album gain instead of track gain
	// should be used for normalisation
	NormalisationUseAlbumGain bool
	// NormalisationPregain specifies the pre-gain to apply when normalising the volume
	// in dB. Use negative values to avoid clipping.
	NormalisationPregain float32

	// CrossfadeDuration specifies for how long tracks should overlap during
	// a track change. Zero disables crossfading.
	CrossfadeDuration time.Duration

	// CountryCode specifies the country code to use for media restrictions.
	CountryCode *string

	// AudioBackend specifies the audio backend to use (alsa, pulseaudio, etc).
	AudioBackend string
	// AudioBackendRuntimeSocket specifies a prefixed with protocol (e.g. `unix:` or `tcp:`) path
	// to a runtime socket of audio backend.
	//
	// This feature is support only for the pulseaudio backend.
	AudioBackendRuntimeSocket string
	// AudioDevice specifies the audio device name.
	//
	// This feature is support only for the alsa and pulseaudio backend.
	AudioDevice string
	// MixerDevice specifies the audio mixer name.
	//
	// This feature is support only for the alsa backend.
	MixerDevice string
	// MixerControlName specifies the mixer control name.
	//
	// This only works in combination with Mixer.
	MixerControlName string
	// AudioBufferTime is the buffer time in microseconds.
	//
	// This is only supported on the alsa backend.
	AudioBufferTime int
	// AudioPeriodCount is the number of periods to request.
	//
	// This is only supported on the alsa backend.
	AudioPeriodCount int

	// ExternalVolume specifies, if the volume is controlled outside the app.
	//
	// This is only supported on the alsa and pipe backends.
	// The PulseAudio backend always uses external volume.
	ExternalVolume bool

	// VolumeUpdate is a channel on which volume updates will be sent back to
	// Spotify. This must be a buffered channel.
	VolumeUpdate chan float32

	// AudioOutputPipe is the path to the output pipe.
	//
	// This is only supported on the pipe backend.
	AudioOutputPipe string

	// AudioOutputPipeFormat is the format of the output pipe.
	// Available formats are: "s16le", "s32le", "f32le". Default is "s16le".
	//
	// This is only supported on the pipe backend.
	AudioOutputPipeFormat string

	// AudioOutputPipeWaitForReader makes the pipe backend wait for a reader to
	// appear when opening the FIFO, instead of failing if none is present at the
	// time playback starts. This is useful for readers (e.g. snapcast with
	// dryout) that only connect to the FIFO when data is expected.
	//
	// This is only supported on the pipe backend.
	AudioOutputPipeWaitForReader bool
}

type Player

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

func NewPlayer

func NewPlayer(opts *Options) (*Player, error)

func (*Player) Close

func (p *Player) Close()

Close stops the player. It is safe to call more than once and safe to call while other goroutines are issuing commands: those get ErrPlayerClosed.

func (*Player) HasBeenPlayingFor

func (p *Player) HasBeenPlayingFor() time.Duration

func (*Player) NewStream

func (p *Player) NewStream(ctx context.Context, client *http.Client, spotId librespot.SpotifyId, bitrate int, mediaPosition int64) (*Stream, error)

func (*Player) Pause

func (p *Player) Pause() error

func (*Player) Play

func (p *Player) Play() error

func (*Player) PositionMs

func (p *Player) PositionMs() int64

PositionMs returns the current playback position, or zero if the player has been closed.

func (*Player) Receive

func (p *Player) Receive() <-chan Event

func (*Player) ReopenOutput added in v0.8.0

func (p *Player) ReopenOutput(device string) error

ReopenOutput reopens the audio output on the given device without touching the Spotify session, preserving the current playback position, paused state and volume. If playback is currently active it switches devices live (with a brief audio gap); if nothing is playing it just records the device for the next output open. It returns an error if the new device fails to open, in which case playback is left stopped.

func (*Player) SeekMs

func (p *Player) SeekMs(pos int64) error

func (*Player) SetPrimaryStream

func (p *Player) SetPrimaryStream(source librespot.AudioSource, paused, drop bool) error

func (*Player) SetSecondaryStream

func (p *Player) SetSecondaryStream(source librespot.AudioSource)

func (*Player) SetVolume

func (p *Player) SetVolume(val uint32)

func (*Player) Stop

func (p *Player) Stop()

type Stream

type Stream struct {
	PlaybackId []byte

	// RequestedId is the id this stream was created for, before any relinking
	// to an alternative track. Callers keep referring to the stream by the id
	// they requested, which may differ from the media's own id.
	RequestedId librespot.SpotifyId

	Source librespot.AudioSource
	Media  *librespot.Media
	File   *metadatapb.AudioFile

	// SampleRate of the decoded audio, in Hz.
	SampleRate int32

	// BitDepth of the source audio, in bits per sample. Only lossless formats
	// have one: the lossy codecs decode to floats with no meaningful source
	// depth, and report zero.
	BitDepth int32
}

func (*Stream) Is

func (s *Stream) Is(id librespot.SpotifyId) bool

type SwitchingAudioSource

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

func NewSwitchingAudioSource

func NewSwitchingAudioSource(crossfadeSamples int) *SwitchingAudioSource

NewSwitchingAudioSource creates an audio source that switches seamlessly between a primary and a prefetched secondary source.

If crossfadeSamples is greater than zero, track transitions overlap by up to that many samples (interleaved, so frames times channels) with an equal-power crossfade. A value of zero disables crossfading and preserves gapless behavior.

func (*SwitchingAudioSource) Close

func (s *SwitchingAudioSource) Close() error

func (*SwitchingAudioSource) Done

func (s *SwitchingAudioSource) Done() <-chan struct{}

func (*SwitchingAudioSource) PositionMs

func (s *SwitchingAudioSource) PositionMs() int64

func (*SwitchingAudioSource) Read

func (s *SwitchingAudioSource) Read(p []float32) (n int, err error)

func (*SwitchingAudioSource) SetPositionMs

func (s *SwitchingAudioSource) SetPositionMs(pos int64) error

func (*SwitchingAudioSource) SetPrimary

func (s *SwitchingAudioSource) SetPrimary(source librespot.AudioSource)

func (*SwitchingAudioSource) SetSecondary

func (s *SwitchingAudioSource) SetSecondary(source librespot.AudioSource)

Jump to

Keyboard shortcuts

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