Documentation
¶
Overview ¶
Package tui is the terminal interface: the result card printed after a recognition, the interactive explorer (audd browse), and the now-playing screen. Every screen also has a plain, non-interactive output so it works when piped.
Integration hooks, assigned where the stream store, results cache, and jobs store are wired in:
- NewFeed must return a StoreFeed backed by internal/streamstore and streams.RecentResults. The default only lists stations, so now-playing reports that stream results are not available.
- NewExplorerData must extend DefaultExplorerData with Recent (cache.Recent) and Jobs/JobItems (jobs.List and the job's results).
Index ¶
- Constants
- Variables
- func RenderResultCard(w io.Writer, a *app.App, r app.ResultView, details bool)
- func RunExplorer(ctx context.Context, a *app.App, tab string) error
- func RunNowPlaying(a *app.App, radioIDs []int, opts NowPlayingOptions) error
- func Tabs() []string
- func ViewFromJSON(raw json.RawMessage) (v app.ResultView, trackLength time.Duration, ok bool)
- func ViewJSON(v app.ResultView) map[string]any
- type ExplorerData
- type Feed
- type FeedFuncs
- type Health
- type JobItem
- type JobRow
- type NowPlayingLine
- type NowPlayingOptions
- type Play
- func (p Play) Ago(now time.Time) time.Duration
- func (p Play) Current(now time.Time) bool
- func (p Play) Elapsed(now time.Time) time.Duration
- func (p Play) Ended() (t time.Time, ok bool)
- func (p Play) JSON() map[string]any
- func (p Play) Played() time.Duration
- func (p Play) State(now time.Time) PlayState
- func (p Play) Timing(now time.Time) map[string]any
- type PlayState
- type PlayStore
- type RecentItem
- type Station
- type StoreFeed
Constants ¶
const HeartbeatWindow = 2 * time.Minute
HeartbeatWindow is how old the newest stored play may be, while the recorder is not running, before readers also ask the recent-results endpoint. The recorder writes a heartbeat every 30 seconds.
Variables ¶
var NewExplorerData = DefaultExplorerData
NewExplorerData builds the explorer's sources for an invocation. The default covers streams (NewFeed), stream management (API), and usage (account); the results cache and the jobs store are added where those packages are wired in.
var NewFeed = func(a *app.App) (Feed, error) { return FeedFuncs{StationsFunc: func(ctx context.Context) ([]Station, error) { return APIStations(ctx, a) }}, nil }
NewFeed builds the Feed for an invocation. The default lists streams with the API but has no plays; the integration replaces it with a StoreFeed over the local stream store and the recent-results endpoint (see the package comment).
Functions ¶
func RenderResultCard ¶
RenderResultCard prints a recognition result for people: cover art next to title, artist, album · label · year, and the song link. details adds ISRC, UPC, score, timecode, and every metadata provider's link. Art is drawn only on a terminal, with color on, and not when r.NoArt is set or AUDD_NO_ART / AUDD_ART=none turn it off.
func RunExplorer ¶
RunExplorer opens the interactive explorer on a tab: recent, jobs, streams, or usage ("jobs/<id>" opens one job; "streams/<id>,<id>" shows only those streams). Without a terminal it prints the tab's data as JSON instead.
func RunNowPlaying ¶
func RunNowPlaying(a *app.App, radioIDs []int, opts NowPlayingOptions) error
RunNowPlaying shows what is playing on the given streams (all streams when radioIDs is empty). On a terminal it opens the full-screen view; piped, it prints a JSONL line per song change; with Once it prints the current songs and exits.
func ViewFromJSON ¶
func ViewFromJSON(raw json.RawMessage) (v app.ResultView, trackLength time.Duration, ok bool)
ViewFromJSON flattens one AudD song object (a recognition result, a stream callback song, or a cached result) into a ResultView. Parsing is lenient: missing or wrong-typed fields are left empty. It also returns the track length when Apple Music (or Spotify or Deezer) metadata carries it. ok is false when raw is not an object or has neither artist nor title.
Types ¶
type ExplorerData ¶
type ExplorerData struct {
Recent func(ctx context.Context, limit int) ([]RecentItem, error)
Jobs func(ctx context.Context) ([]JobRow, error)
JobItems func(ctx context.Context, id string) ([]JobItem, error)
Feed Feed
Usage func(ctx context.Context, days int) (*account.Usage, error)
// Stream management from the Streams tab.
AddStream func(ctx context.Context, url string, radioID int) error
RemoveStream func(ctx context.Context, radioID int) error
SetStreamURL func(ctx context.Context, radioID int, url string) error
}
ExplorerData supplies the explorer's tabs. A nil source shows a short note in its tab instead of data.
func DefaultExplorerData ¶
func DefaultExplorerData(a *app.App) (ExplorerData, error)
DefaultExplorerData is the default NewExplorerData.
type Feed ¶
type Feed interface {
Stations(ctx context.Context) ([]Station, error)
Plays(ctx context.Context, radioID int, limit int) ([]Play, error)
}
Feed supplies stream data to now-playing and the explorer's Streams tab. Stations lists the account's streams with their latest health; Plays returns the recent plays of one stream, newest first. Plays reads the local stream store (kept complete by the background recorder) and falls back to the recent-results endpoint (the last ~30 results per stream).
type FeedFuncs ¶
type FeedFuncs struct {
StationsFunc func(ctx context.Context) ([]Station, error)
PlaysFunc func(ctx context.Context, radioID int, limit int) ([]Play, error)
}
FeedFuncs adapts two functions to a Feed.
type Health ¶
type Health struct {
Code int `json:"code"`
Message string `json:"message"`
At time.Time `json:"at"`
}
Health is a stream health notification (650: can't connect, 651: no music).
type JobItem ¶
type JobItem struct {
Index int `json:"index"`
Input string `json:"input"`
State string `json:"state"` // pending, done, failed
Result json.RawMessage `json:"result,omitempty"`
Err string `json:"error,omitempty"`
}
JobItem is one input of a batch job.
type JobRow ¶
type JobRow struct {
ID string `json:"id"`
Command []string `json:"command"`
Created time.Time `json:"created"`
Total int `json:"total"`
Done int `json:"done"`
Failed int `json:"failed"`
Status string `json:"status"`
}
JobRow is a batch job.
type NowPlayingLine ¶
type NowPlayingLine struct {
app.ResultView
RadioID int
URL string
State string
Playing bool
At time.Time
Elapsed time.Duration
Played time.Duration
Ended time.Time
Ago time.Duration
Length time.Duration
}
NowPlayingLine is the data a --format template sees: the song fields ({{.Artist}}, {{.Title}}, {{.Album}}, {{.Label}}, {{.ReleaseDate}}, {{.SongLink}}, {{.ISRC}}, {{.UPC}}), the stream ({{.RadioID}}, {{.URL}}), and the timing: {{.State}} (playing, just_played, or last_recognized), {{.Playing}}, {{.At}} (when the song started), {{.Elapsed}} (since it started, while it plays), {{.Played}} and {{.Ended}} (for results sent when the song ended), {{.Ago}} (since it ended, or since it started when the end is not known), and {{.Length}} (when the track length is known).
type NowPlayingOptions ¶
type NowPlayingOptions struct {
NoArt, Notify bool
// Once prints the current song of each station and exits.
Once bool
// Format is a text/template over NowPlayingLine, printed per song
// change (or once with Once), for status bars.
Format string
// Timeout stops the run after this long (0: run until interrupted).
Timeout time.Duration
// Interval is how often stream results are re-read (default 5 s).
Interval time.Duration
// Context cancels the run (Ctrl-C). Defaults to context.Background().
Context context.Context
}
NowPlayingOptions configure audd now-playing.
type Play ¶
type Play struct {
RadioID int
// At is when AudD detected the start of the song.
At time.Time
// PlayLength is how long the song had played when AudD reported it, in
// seconds (from the callback's play_length).
PlayLength int
// TrackLength is the full song length, when metadata includes it.
TrackLength time.Duration
app.ResultView
// Raw is the original song object from the API, when known.
Raw json.RawMessage
}
Play is one song recognized on a stream.
func PlayFromRaw ¶
PlayFromRaw builds a Play from a stored or fetched song object.
func (Play) Ago ¶
Ago is the time since the song was last heard: since it ended when the result says so, else since it started.
func (Play) Ended ¶
Ended reports when the song ended, for results delivered at the end of the song (they carry play_length). ok is false otherwise.
func (Play) State ¶
State is what the result says about the song now. A result with play_length arrived when the song ended, so the song is over. A result without it arrived when the song started: the song plays for its track length when that is known (provider metadata), else for up to 10 minutes.
func (Play) Timing ¶
Timing is the machine form of State: the state with the times that go with it. elapsed_seconds (since the song started) is set only while it plays; played_seconds and ended_at only for results delivered when the song ended; ago_seconds (since it ended, or since it started when the end is not known) only when it is not playing; length_seconds when the track length is known.
type PlayState ¶
type PlayState string
PlayState says what a stream result tells about the song now.
const ( // StatePlaying: the result arrived when the song started (a stream // added with --start) and the song is probably still on. StatePlaying PlayState = "playing" // StateJustPlayed: the result arrived when the song ended (the // default) a few minutes ago at most. StateJustPlayed PlayState = "just_played" // StateLastRecognized: the newest result is older than that. StateLastRecognized PlayState = "last_recognized" )
type PlayStore ¶
type PlayStore interface {
// Plays returns up to limit plays of a stream, newest first.
Plays(radioID int, limit int) ([]Play, error)
// AddPlay saves a play; duplicates (same stream and timestamp) are ignored.
AddPlay(p Play) error
}
PlayStore is the part of the local stream store the screens use.
type RecentItem ¶
type RecentItem struct {
Source string `json:"source"` // file path or URL
At time.Time `json:"at"`
Result json.RawMessage `json:"result"` // the API result (object, or array for enterprise)
}
RecentItem is one result recognized through the CLI (results cache and batch jobs).
type Station ¶
type Station struct {
RadioID int `json:"radio_id"`
URL string `json:"url,omitempty"`
Running bool `json:"stream_running"`
Health *Health `json:"health,omitempty"` // latest health notification, if any
// StatusUnknown is set when the stream list could not be read, so
// whether the stream runs is not known.
StatusUnknown bool `json:"-"`
}
Station is one monitored stream.
func APIStations ¶
APIStations lists the account's streams with getStreams, healing a rejected login token like every other API call.
func (Station) HealthText ¶
HealthText describes a station's health for people.
type StoreFeed ¶
type StoreFeed struct {
StationsFunc func(ctx context.Context) ([]Station, error)
Store PlayStore
// Recent fetches the recent-results endpoint for a stream.
Recent func(ctx context.Context, radioID int) ([]Play, error)
// RecorderRunning reports whether a recorder keeps a stream current
// (it has written a heartbeat for the stream within HeartbeatWindow).
RecorderRunning func(radioID int) bool
Now func() time.Time
// contains filtered or unexported fields
}
StoreFeed reads plays from the local stream store first. It asks the recent-results endpoint (the last ~30 results of a stream) only when the store has nothing for the stream, or when no recorder keeps the stream current and the newest stored play is older than HeartbeatWindow; what it fetches is written into the store before it is shown.