Documentation
¶
Overview ¶
Package player provides the audio engine for MP3 playback with a 10-band parametric EQ, volume control, and sample capture for visualization.
Index ¶
- Variables
- func DeviceSampleRate() int
- func InstallYTDLP() error
- func PrepareAudioDevice(device string) func()
- func SetYTDLCookiesFrom(browser string)
- func SwitchAudioDevice(deviceName string) error
- func YTDLPAvailable() bool
- func YtdlpInstallHint() string
- type AudioDevice
- type Engine
- type Player
- func (p *Player) CancelSeekYTDL()
- func (p *Player) ClearPreload()
- func (p *Player) Close()
- func (p *Player) Drained() bool
- func (p *Player) Duration() time.Duration
- func (p *Player) EQBands() [10]float64
- func (p *Player) GaplessAdvanced() bool
- func (p *Player) HasPreload() bool
- func (p *Player) IsPaused() bool
- func (p *Player) IsPlaying() bool
- func (p *Player) IsStreamSeek() bool
- func (p *Player) IsYTDLSeek() bool
- func (p *Player) Mono() bool
- func (p *Player) Play(path string, knownDuration time.Duration) error
- func (p *Player) PlayYTDL(pageURL string, knownDuration time.Duration) error
- func (p *Player) Position() time.Duration
- func (p *Player) PositionAndDuration() (time.Duration, time.Duration)
- func (p *Player) Preload(path string, knownDuration time.Duration) error
- func (p *Player) PreloadYTDL(pageURL string, knownDuration time.Duration) error
- func (p *Player) RegisterBufferedURLMatcher(match func(string) bool)
- func (p *Player) RegisterStreamMetadataResolver(r StreamMetadataResolver)
- func (p *Player) RegisterStreamerFactory(scheme string, f StreamerFactory)
- func (p *Player) SampleRate() int
- func (p *Player) SamplesInto(dst []float64) int
- func (p *Player) Seek(d time.Duration) error
- func (p *Player) SeekYTDL(d time.Duration) error
- func (p *Player) Seekable() bool
- func (p *Player) SetEQBand(band int, dB float64)
- func (p *Player) SetSpeed(ratio float64)
- func (p *Player) SetVolume(db float64)
- func (p *Player) SetVolumeMin(db float64)
- func (p *Player) Speed() float64
- func (p *Player) Stop()
- func (p *Player) StreamBytes() (downloaded, total int64)
- func (p *Player) StreamErr() error
- func (p *Player) StreamTitle() string
- func (p *Player) ToggleMono()
- func (p *Player) TogglePause()
- func (p *Player) Volume() float64
- func (p *Player) VolumeMin() float64
- type Quality
- type StreamMetadataResolver
- type StreamerFactory
Constants ¶
This section is empty.
Variables ¶
var SupportedExts = map[string]bool{ ".mp3": true, ".wav": true, ".flac": true, ".ogg": true, ".m4a": true, ".aac": true, ".aacp": true, ".m4b": true, ".alac": true, ".wma": true, ".opus": true, ".webm": true, }
SupportedExts is the set of file extensions the player can decode.
Functions ¶
func DeviceSampleRate ¶
func DeviceSampleRate() int
DeviceSampleRate returns 0 on platforms where device detection is not implemented. Callers should fall back to a sensible default (e.g. 44100).
func InstallYTDLP ¶
func InstallYTDLP() error
InstallYTDLP attempts to install yt-dlp using the system package manager. Returns nil on success. The caller should re-check YTDLPAvailable() after.
func PrepareAudioDevice ¶
func PrepareAudioDevice(device string) func()
PrepareAudioDevice sets PIPEWIRE_NODE so the PipeWire ALSA plugin routes this process's audio to the named device. Must be called before player.New(). Returns a cleanup that restores the env.
func SetYTDLCookiesFrom ¶
func SetYTDLCookiesFrom(browser string)
SetYTDLCookiesFrom configures yt-dlp to use cookies from the given browser for YouTube Music playback (e.g. "chrome", "firefox", "brave").
func SwitchAudioDevice ¶
SwitchAudioDevice moves cliamp's audio stream to a different sink. Falls back to changing the system default if the stream can't be found.
func YTDLPAvailable ¶
func YTDLPAvailable() bool
YTDLPAvailable reports whether yt-dlp is installed and on PATH.
func YtdlpInstallHint ¶
func YtdlpInstallHint() string
YtdlpInstallHint returns a platform-specific install command suggestion.
Types ¶
type AudioDevice ¶
type AudioDevice struct {
Index int
Name string // internal identifier (sink name, UID, or device ID)
Description string // human-readable label
Active bool // true when this is the current default
}
AudioDevice represents an available audio output device (sink/endpoint).
func ListAudioDevices ¶
func ListAudioDevices() ([]AudioDevice, error)
ListAudioDevices returns available output sinks via pactl. Works on PulseAudio and PipeWire (via pipewire-pulse).
type Engine ¶
type Engine interface {
// Playback control
Play(path string, knownDuration time.Duration) error
PlayYTDL(pageURL string, knownDuration time.Duration) error
Preload(path string, knownDuration time.Duration) error
PreloadYTDL(pageURL string, knownDuration time.Duration) error
ClearPreload()
Stop()
Close()
TogglePause()
// Seeking
Seek(d time.Duration) error
SeekYTDL(d time.Duration) error
CancelSeekYTDL()
// State queries
IsPlaying() bool
IsPaused() bool
Drained() bool
HasPreload() bool
Seekable() bool
IsStreamSeek() bool
IsYTDLSeek() bool
GaplessAdvanced() bool
// Position and duration
Position() time.Duration
Duration() time.Duration
PositionAndDuration() (time.Duration, time.Duration)
// Volume
SetVolumeMin(db float64)
VolumeMin() float64
SetVolume(db float64)
Volume() float64
// Speed
SetSpeed(ratio float64)
Speed() float64
// Mono
ToggleMono()
Mono() bool
// EQ
SetEQBand(band int, dB float64)
EQBands() [10]float64
// Stream info
StreamErr() error
StreamTitle() string
StreamBytes() (downloaded, total int64)
// Audio samples for visualizer
SamplesInto(dst []float64) int
SampleRate() int
}
Engine is the interface used by the TUI model to control audio playback. It is satisfied by *Player and can be replaced with a mock for testing.
type Player ¶
type Player struct {
// contains filtered or unexported fields
}
Player is the audio engine managing the playback pipeline:
[Gapless] -> [10x Biquad EQ] -> [Volume] -> [Tap] -> [Ctrl] -> speaker
↑
├─ current: [Decode A] → [Resample A]
└─ next: [Decode B] → [Resample B] (preloaded)
func (*Player) CancelSeekYTDL ¶
func (p *Player) CancelSeekYTDL()
CancelSeekYTDL increments the seek generation, causing any in-flight SeekYTDL to discard its result instead of swapping streams.
func (*Player) ClearPreload ¶
func (p *Player) ClearPreload()
ClearPreload discards the preloaded next track (e.g., when shuffle/repeat changes). Speaker is locked to ensure no in-flight gapless transition can reference the pipeline we're about to close.
func (*Player) Close ¶
func (p *Player) Close()
Close fully stops the speaker and cleans up all resources.
func (*Player) Drained ¶
Drained returns true if the current track ended with no preloaded next track.
func (*Player) Duration ¶
Duration returns the total duration of the current track. For seekable local files it is derived from the decoder's sample count. For HTTP streams where the decoder reports Len()==0, the metadata hint stored at pipeline build time (knownDuration) is returned instead.
func (*Player) GaplessAdvanced ¶
GaplessAdvanced returns true (once) when a gapless transition happened.
func (*Player) HasPreload ¶
HasPreload returns true if a next track is already queued for gapless transition.
func (*Player) IsPlaying ¶
IsPlaying returns true if a track is loaded and playing (possibly paused).
func (*Player) IsStreamSeek ¶
IsStreamSeek reports whether the current track uses HTTP seek-by-reconnect. When true, Seek() releases the speaker lock during the HTTP request, so callers should dispatch seeks asynchronously to avoid blocking the UI.
func (*Player) IsYTDLSeek ¶
IsYTDLSeek reports whether the current track uses yt-dlp seek-by-restart.
func (*Player) Play ¶
Play opens and starts playing an audio file. On the first call it builds the long-lived EQ → volume → tap → ctrl chain and starts the speaker. Subsequent calls swap only the track source via the gapless streamer. knownDuration is the metadata duration (use 0 if unknown); it is used as a fallback when the decoder cannot determine the length (e.g. HTTP streams).
func (*Player) PlayYTDL ¶
PlayYTDL starts playing a yt-dlp page URL via a piped yt-dlp | ffmpeg chain. Playback starts as soon as the first PCM samples arrive (~1-3s). Not seekable.
func (*Player) Position ¶
Position returns the current playback position. For ranged HTTP streams (seek-by-reconnect), streamOffset is added to the decoder's sample-based position so the reported time is absolute within the track, not relative to the reconnect point.
func (*Player) PositionAndDuration ¶
PositionAndDuration returns both position and duration under a single speaker lock, avoiding two separate lock acquisitions per tick.
func (*Player) Preload ¶
Preload builds a pipeline for the next track and queues it for gapless transition. knownDuration is the metadata duration (use 0 if unknown).
func (*Player) PreloadYTDL ¶
PreloadYTDL builds a yt-dlp pipe pipeline and queues it for gapless transition.
func (*Player) RegisterBufferedURLMatcher ¶
RegisterBufferedURLMatcher registers a function that identifies HTTP URLs requiring the buffered download + ffmpeg pipeline (e.g. Subsonic stream endpoints). This replaces hardcoded URL pattern checks.
func (*Player) RegisterStreamMetadataResolver ¶
func (p *Player) RegisterStreamMetadataResolver(r StreamMetadataResolver)
RegisterStreamMetadataResolver installs a resolver used to pull now-playing metadata for streams that lack inline ICY metadata. Pass nil to disable.
func (*Player) RegisterStreamerFactory ¶
func (p *Player) RegisterStreamerFactory(scheme string, f StreamerFactory)
RegisterStreamerFactory registers a factory for a custom URI scheme prefix (e.g., "spotify:"). When buildPipeline encounters a path starting with this prefix, it calls the factory to create the decoder instead of the normal file/HTTP pipeline.
func (*Player) SampleRate ¶
SampleRate returns the output sample rate in Hz.
func (*Player) SamplesInto ¶
SamplesInto copies the latest audio samples into dst, avoiding allocation. Returns the number of samples written.
func (*Player) Seek ¶
Seek moves the playback position by the given duration (positive or negative). For seekable local files, the decoder's Seek method is used directly. For HTTP streams with a known Content-Length and duration (seekableStream), seek is implemented by reconnecting with a Range: bytes=N- header and rebuilding the decoder at the computed byte offset. This is known as seek-by-reconnect. Returns nil immediately for non-seekable streams (e.g., Icecast radio). The speaker lock is acquired first (outer), then p.mu briefly to snapshot the current pipeline, ensuring consistent lock ordering with the audio thread. Clears the preloaded next pipeline to prevent a stale gapless transition.
func (*Player) SeekYTDL ¶
SeekYTDL seeks a yt-dlp stream by restarting the pipeline at the target position. Must NOT be called with the speaker lock held. If a newer seek is requested (via CancelSeekYTDL) while this one is building, the result is discarded.
func (*Player) Seekable ¶
Seekable reports whether the current track supports seeking. Returns true for local files (decoder-native seek) and for HTTP streams with a known Content-Length and duration (seek-by-reconnect).
func (*Player) SetSpeed ¶
SetSpeed sets the playback speed ratio, clamped to [0.25, 2.0]. 1.0 is normal speed, 2.0 is double speed, etc.
func (*Player) SetVolumeMin ¶
SetVolumeMin sets the minimum volume floor in dB, clamped to [-90, 0]. If the current volume is below the new floor it is immediately raised to match.
func (*Player) Stop ¶
func (p *Player) Stop()
Stop halts playback and releases resources. The speaker is suspended so the ALSA audio callback goroutine blocks (zero CPU) instead of streaming silence. Resume is called automatically on the next Play().
func (*Player) StreamBytes ¶
StreamBytes returns the bytes downloaded and total content length for the current HTTP stream. Returns (0, 0) for local files or when no counter exists.
func (*Player) StreamErr ¶
StreamErr returns the current streamer error, if any (e.g., connection drops).
func (*Player) StreamTitle ¶
StreamTitle returns the current ICY stream title (e.g., "Artist - Song"). Returns "" when no ICY metadata has been received.
func (*Player) ToggleMono ¶
func (p *Player) ToggleMono()
ToggleMono switches between stereo and mono (L+R downmix) output.
func (*Player) TogglePause ¶
func (p *Player) TogglePause()
TogglePause toggles between paused and playing states. When pausing, the speaker is suspended to save CPU; when unpausing it is resumed so the audio callback drains the queued samples.
type Quality ¶
type Quality struct {
SampleRate int // output sample rate in Hz (e.g. 44100, 48000)
BufferMs int // speaker buffer in milliseconds
ResampleQuality int // beep resample quality factor (1–4)
BitDepth int // PCM bit depth for FFmpeg output: 16 or 32 (32 = lossless)
}
Quality holds configurable audio output parameters.
type StreamMetadataResolver ¶
type StreamMetadataResolver func(streamURL string) (fetch func(ctx context.Context) (string, error), interval time.Duration, ok bool)
StreamMetadataResolver matches a stream URL to a now-playing fetcher for broadcasters that carry no inline ICY metadata (e.g. NTS, FIP) and instead publish the current track via a separate JSON API. It returns a fetch function, the poll interval, and ok=false when the URL is not recognized.
type StreamerFactory ¶
StreamerFactory creates a beep.StreamSeekCloser for a custom URI scheme (e.g., spotify:track:xxx). Returns the streamer, its format, the track duration, and any error.