streams

package
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Oct 9, 2026 License: MIT Imports: 31 Imported by: 0

Documentation

Overview

Package streams reads AudD stream results (live longpoll events and the recent-results endpoint), records them into the local stream store, runs the background recorder, and relays plays to a local callback handler.

Index

Constants

View Source
const BackgroundChildFlag = "background-child"

BackgroundChildFlag is the hidden flag `audd streams record` gets when it runs as the background recorder.

View Source
const EmptyCallbackURL = "https://audd.tech/empty/"

EmptyCallbackURL is a placeholder callback URL that accepts and discards callbacks. Longpoll only delivers events when some callback URL is set.

View Source
const RecorderNote = "Recording stream results in the background. See audd streams recorder status."

RecorderNote is shown the first time audd starts the background recorder for a profile, by any command or screen.

Variables

View Source
var ErrNotRunning = errors.New("no stream recorder is running")

ErrNotRunning is returned by Stop when no recorder runs for the profile.

View Source
var HTTPClient = &http.Client{Timeout: 30 * time.Second}

HTTPClient is used for the recent-results endpoint and for relaying plays to --forward-to handlers. It honors HTTPS_PROXY and NO_PROXY.

View Source
var LongpollHTTPClient = &http.Client{}

LongpollHTTPClient is used for longpoll requests, which carry no API token. It has no overall timeout: each poll sets its own deadline.

View Source
var RecentResultsURL = "https://api.audd.io/lastSong/getChannelById/"

RecentResultsURL is the recent-results endpoint: the last ~30 results per stream, keyed by longpoll category. It needs no API token.

Functions

func Backfill

func Backfill(ctx context.Context, a *app.App, store *streamstore.Store, radioIDs []int, freshWithin time.Duration) error

Backfill stores the recent results of the account's streams (or only the given ones) once, without longpolling, and records the account's stream list. Streams a recorder covered within freshWithin are skipped (0: none are). Commands that read the store use it when it is not current.

func CallbackMissing

func CallbackMissing(ctx context.Context, c *audd.Client) (bool, error)

CallbackMissing reports whether the account has no callback URL (AudD error 19 from getCallbackUrl), which stops longpoll from delivering events. Other errors come back as the SDK returned them.

func Do

func Do[T any](ctx context.Context, a *app.App, call func(c *audd.Client) (T, error)) (T, error)

Do runs a stream API call through api.Do, so a rejected token fetched by audd login is healed and retried once, and errors get the same codes, exit codes, and token hints as every other command (see MapError).

func EnsureBackground

func EnsureBackground(a *app.App) (started bool, err error)

EnsureBackground starts the background recorder for the active profile unless it is running, turned off (streams.background_recorder false or AUDD_NO_BACKGROUND_RECORDER set), or there is no API token. started reports whether this call started it. It is assigned to app.EnsureRecorder. AUDD_NO_BACKGROUND_RECORDER is intentionally supported but left out of the user docs: tests (internal/e2e) and throwaway environments such as CI use it so that only an explicit `audd streams recorder start` starts a recorder.

func EnsureCallbackURL

func EnsureCallbackURL(ctx context.Context, a *app.App) error

EnsureCallbackURL makes sure longpoll can deliver events: when the account has no callback URL it offers to set EmptyCallbackURL, and sets it only after confirmation (or --yes). It never changes an existing callback URL.

func EnsureRecorder

func EnsureRecorder(a *app.App) (note string, err error)

EnsureRecorder starts the background recorder when it should run (through app.EnsureRecorder) and returns the note to show: RecorderNote when this call started the recorder and the profile has not seen the note before, else "". err is the start error, which callers treat as a note at most.

func Executable

func Executable() (string, error)

Executable is the path services and the background recorder run: the audd on PATH when it is this binary (so package-manager upgrades keep working), else this binary.

func FormatTimestamp

func FormatTimestamp(t time.Time) string

FormatTimestamp writes a time the way AudD stream results do.

func HealthCallbackBody

func HealthCallbackBody(h streamstore.HealthEvent) []byte

HealthCallbackBody builds the callback JSON for a health event.

func HealthFromNotification

func HealthFromNotification(n audd.StreamCallbackNotification, radioID int, now time.Time) streamstore.HealthEvent

HealthFromNotification converts a stream notification into a HealthEvent.

func InstallService

func InstallService(profile string) (path string, err error)

InstallService writes the service file for this OS and returns its path. It does not activate it; ServiceFor(profile).Activate is the command that does.

func IsNoCallback

func IsNoCallback(err error) bool

IsNoCallback reports whether err is AudD error 19 meaning the account has no callback URL.

func LogPath

func LogPath(profile string) string

LogPath is the background recorder's log file.

func MapError

func MapError(a *app.App, err error) error

MapError turns an audd-go error into an *output.Error the way api.MapError does (with a hint that fits where the token came from), plus the streams docs link and a stream-monitoring hint when it is not enabled. Other errors pass through with any token in their text redacted (Go's connection errors include the request URL, which carries the token on some API calls).

func OpenLog

func OpenLog(profile string) (*os.File, error)

OpenLog opens the profile's recorder log for appending.

func PIDPath

func PIDPath(profile string) string

PIDPath is the file holding the running recorder's PID and start time.

func ParseRecentResults

func ParseRecentResults(body []byte, now time.Time) ([]streamstore.Play, error)

ParseRecentResults parses a recent-results response body.

func ParseTimestamp

func ParseTimestamp(s string) (t time.Time, ok bool)

ParseTimestamp reads a stream-result timestamp: "YYYY-MM-DD hh:mm:ss" in AudD's UTC+3, RFC 3339, or Unix seconds/milliseconds. ok is false when the value is not a time.

func PlayCallbackBody

func PlayCallbackBody(p streamstore.Play) []byte

PlayCallbackBody builds the callback JSON for a play (used for plays that came from the recent-results endpoint rather than a live event).

func PlayFromMatch

func PlayFromMatch(m audd.StreamCallbackMatch, now time.Time) streamstore.Play

PlayFromMatch converts a longpoll/callback recognition into a Play. now is used when the event carries no usable timestamp.

func RecentResults

func RecentResults(ctx context.Context, radioID int, category string) ([]streamstore.Play, error)

RecentResults returns a stream's recent results, oldest first. The endpoint is undocumented, so the response is read leniently: unknown fields are ignored, wrong-typed values degrade to empty, and entries without an artist or title are skipped. RadioID is left at 0 unless the entry carries one; callers set it. Failures are network or server errors (exit 5, retryable) that name the stream.

func RestartBackground

func RestartBackground(a *app.App) (restarted bool, err error)

RestartBackground restarts the profile's background recorder, if one runs, so it reads the API token again (after audd token rotate, say). A recorder run in the foreground with audd streams record is left alone; it picks up a stored token change within five minutes.

func SetSpawnForTesting

func SetSpawnForTesting(f func(exe string, args, env []string, logPath string) (pid int, err error)) (restore func())

SetSpawnForTesting replaces how the background recorder process is started and returns a function that restores the default.

func SetTerminateForTesting

func SetTerminateForTesting(f func(pid int) error) (restore func())

SetTerminateForTesting replaces how a recorder process is asked to exit and returns a function that restores the default.

func StartBackground

func StartBackground(a *app.App) (started bool, err error)

StartBackground starts the background recorder for the active profile even when automatic starting is turned off. started is false when one is already running.

func Status

func Status(profile string) (running bool, pid int, since time.Time, err error)

Status reports whether a recorder runs for the profile.

func Stop

func Stop(profile string) error

Stop asks the profile's recorder to exit and waits up to 10 seconds.

func StopBackground

func StopBackground(profile string) (stopped bool, err error)

StopBackground stops the profile's background recorder, if one runs (after audd logout, say, when it used the token from the sign-in). A recorder run in the foreground with audd streams record is left alone. stopped reports whether one was stopped.

func StopPath

func StopPath(profile string) string

StopPath is the file `audd streams recorder stop` writes to ask a running recorder to shut down. Windows has no SIGTERM for detached processes, so this file is how every platform asks for a clean exit.

func WithStopFile

func WithStopFile(ctx context.Context, profile string) (context.Context, context.CancelFunc)

WithStopFile returns a context that is cancelled when the profile's stop file appears. It removes any stale stop file first, and removes the file again when the returned cancel function runs.

Types

type Forwarder

type Forwarder struct {
	URL    string
	Client *http.Client // defaults to HTTPClient
}

Forwarder relays stream events to a local handler as callback-shaped POSTs, the same JSON AudD sends to a callback URL.

func (*Forwarder) Post

func (f *Forwarder) Post(ctx context.Context, body []byte) error

Post sends one callback body.

type Lock

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

Lock is the single-recorder-per-profile lock.

func Acquire

func Acquire(profile string) (*Lock, error)

Acquire takes the recorder lock for a profile and writes the PID file. It fails with recorder_running when another recorder holds it.

func (*Lock) Check

func (l *Lock) Check() error

Check reports why this recorder no longer owns the profile: its lock file or PID file was removed or replaced (by hand, or by cleaning the data directory). Without them, status cannot see the recorder, stop cannot stop it, and a second recorder could start, so a recorder that gets an error here should exit. It returns nil while both are in place.

func (*Lock) MarkBackground

func (l *Lock) MarkBackground() error

MarkBackground records in the PID file that this is the background recorder, which RestartBackground may restart.

func (*Lock) Release

func (l *Lock) Release()

Release removes the PID file and releases the lock.

type Recorder

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

Recorder longpolls an account's streams and stores every play and health event in the stream store.

func NewRecorder

func NewRecorder(a *app.App, store *streamstore.Store, opts RecorderOptions) *Recorder

NewRecorder returns a recorder that writes into store.

func (*Recorder) Run

func (r *Recorder) Run(ctx context.Context) error

Run records until ctx is cancelled. It backfills each stream from the recent-results endpoint, then longpolls every stream, re-reads the stream list and the API token every 5 minutes, writes a heartbeat every 30 seconds for each connected stream, and reconnects with backoff after errors. When AudD refuses the stream list (a rejected token, no stream monitoring on the account), recording is paused, the problem is noted in the store's last_error, and each rescan tries again. A rejected token fetched by audd login is replaced with the account's current one first. Run returns nil when ctx ends, or an error when there is no API token or AudD rejects a token given with --token or AUDD_API_TOKEN (it cannot change while this process runs; the problem is noted all the same).

type RecorderOptions

type RecorderOptions struct {
	// RadioIDs limits recording to these streams; empty means every stream
	// on the account, including ones added later.
	RadioIDs []int
	// ForwardTo, when set, receives each live play and health event as a
	// callback-shaped POST.
	ForwardTo string
	// OnPlay is called once for every play this recorder sees after its
	// start-up backfill: live plays, and plays a reconnect backfill finds.
	// A play is reported even when another recorder writing the same store
	// saved it first. It may be called from several goroutines at once.
	OnPlay func(streamstore.Play)
	// OnHealth is called for every health event.
	OnHealth func(streamstore.HealthEvent)
	// Logf receives connection problems and other notes (default: discard).
	Logf func(format string, args ...any)
	// Owned, when set, is checked with every heartbeat: an error means
	// this recorder no longer owns its profile (see Lock.Check), and Run
	// stops, logging why.
	Owned func() error
}

RecorderOptions configure a Recorder.

type Service

type Service struct {
	Path     string // where the file goes
	Content  string // file contents (UTF-8; Windows task XML is written as UTF-16)
	Activate string // the command that turns it on
	// StartsNow reports whether Activate also starts the recorder right
	// away (systemd and launchd); a Windows scheduled task first runs at
	// the next logon.
	StartsNow bool
}

Service describes the login service file for a profile's recorder.

func ServiceFor

func ServiceFor(profile string) (Service, error)

ServiceFor returns the service definition for this OS: a systemd user unit on Linux and other Unix systems, a launchd agent on macOS, and a Task Scheduler task on Windows. Each runs `audd streams record` at login.

Directories

Path Synopsis
Package streamstest is a fake AudD stream API for tests: stream management methods, longpoll, and the recent-results endpoint.
Package streamstest is a fake AudD stream API for tests: stream management methods, longpoll, and the recent-results endpoint.

Jump to

Keyboard shortcuts

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