All on by default, so a plain build is unchanged (verified: the default
dependency set for crabidy-server is byte-identical to before). Tailor a
smaller binary with --no-default-features --features …
(architecture/build-features.md).
Compile-time features draw dependency boundaries; the existing
crabidy-server.toml providers list keeps doing per-mount runtime
toggling. The compile-time set bounds the runtime one: a provider built
out cannot be enabled from the config, and naming it earns one startup
warning rather than silence.
- crabidy-server: tidal · youtube · fyyd · abs · soundcloud · jamendo ·
fs · opus · spectrum · web-ui, plus the all-providers group.
- fs is local files *and* persistent state (D5): the /fs mount, the
content store behind /crabidy and /orphans, bookmarks/captures, queue
persistence, and scan. Without it Capture/SaveQueue answer
Unimplemented and scan says which feature is missing — never a panic.
- opus drops symphonia + symphonia-adapter-libopus, and with them the
bundled libopus C build (no more cmake requirement). It also decides
whether scan indexes .opus at all, so scan never indexes what this
build cannot play. An Ogg-Opus file in an opus-less build reports the
missing feature and is skipped like any undecodable file.
- spectrum drops realfft and the FFT task; clients just never receive a
frame. cbd-tui gains notifications (notify-rust, a D-Bus stack).
- crabidy-server/cbd features print the compiled set, and startup logs
it, so a tailored binary is self-describing.
Not gated, deliberately: [auth]/argon2 (a build ignoring configured
hashes would run open — fail-open security hole), and hls.rs /
spectrum_tap.rs / windowed_http.rs (no dependency of their own, so
gating them buys cfg noise and nothing else).
devenv gains check-features: the curated matrix (defaults, nothing, each
provider alone, each axis dropped, both worked examples, the client
crates) all clippy-clean under -D warnings.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
New `soundclouddy` crate mounted at `/soundcloud`: search tracks and
playlists, resolve permalink URLs, and — with an optional OAuth token — the
user's likes and playlists (public browse/play needs only a client_id). Ran the
full dev-flow: architecture/soundcloud-provider.md, quality/soundcloud-provider.md,
plan/soundcloud.md, plan/summary.md.
- Provider logic over an `Sc` reqwest seam (faked in tests): creatable
`search`/`resolve` parents, canonical `track/<id>` and `playlist/<id>`
leaves, playlist hydration, download blessing — mirrors abs/fyyd.
- Auth: `client_id` from config or scraped from soundcloud.com (pure parsers,
unit-tested), re-scraped once on 401; scraped id persisted via `settings()`.
- Playback: a new `HlsStream` SourceStream in audio-player streams the m3u8's
mp3 segments in order as one continuous mp3; `open_source` routes `.m3u8` to
it, non-seekable so symphonia never end-seeks a length-less stream.
- Wired into crabidy-server the standard way (settings toggle, sc_owns/
sc_provider, non-fatal build block, root child, dispatch arms).
Verified offline: soundclouddy 19 tests, audio-player 14 (incl. HLS-parser),
crabidy-server 77+4 — all green; fmt/clippy/machete clean. The live client_id
scrape, real JSON shapes, and mp3-HLS play-to-EOS need real SoundCloud access
and are covered by tests/live.rs + #[ignore] gates (quality G7/G8/G14/G19).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
rodio decodes through symphonia 0.5, which ships no Opus decoder, so raw
.opus sources (audiobookshelf files, and opus from any provider) failed
Decoder::build(). Add OpusSource, a rodio Source that demuxes Ogg with
symphonia own Ogg reader and decodes with libopus (via
symphonia-adapter-libopus, registered into an explicit codec registry), and
route to it by content-sniffing OggS+OpusHead in the player -- the abs stream
URL has no file extension, so the extension hint is not enough. The bundled
libopus builds with cmake/ninja (added to devenv).
Verified end-to-end with ffmpeg mono/stereo opus fixtures including seeking;
11 audio-player tests pass, clippy/fmt clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The player always opened the system default output device. On a
Raspberry Pi that default is often HDMI, so playback ran but nothing
came out of the headphone jack or a USB/DAC -- "it plays but I hear no
sound".
Add an [audio] device option to crabidy-server.toml: a case-insensitive
substring of the output device name (a memorable fragment is enough).
The player engine opens the first matching device and falls back to the
system default with a warning if none matches. Absent config keeps the
system default, so existing setups are unchanged.
To discover the names, a new `crabidy-server audio-devices` subcommand
(also on `cbd`) lists the output devices and marks the one the current
config selects, using the same match the server applies at startup.
Plumbing: audio_player::output_device_names() enumerates via cpal;
Player::new(Option<String>) replaces the device-less construction
(Default = new(None)); Playback::new takes the device and serve() reads
it from settings. cpal's name() is deprecated in favor of description(),
but name() returns the ALSA-stable string users see in `aplay -l` and
match against, so it is kept behind a documented #[allow(deprecated)].
README documents the [audio] device option under the Pi/config section.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A row of frequency bars under the track progress, on by default and
toggleable with the client spectrum config option. Because the audio
plays on the server and clients may be remote, the spectrum is produced
server-side, not captured locally: audio-player taps its own output
into a lock-free ring on the audio thread (one store per sample, no
locks), crabidy-server runs a Hann + realfft over 2048 samples at 20fps,
folds it into log-spaced bars, and broadcasts them as a new SpectrumFrame
on the update stream. The task idles when nothing is playing or no
client is listening. The TUI renders block-glyph bars in the now-playing
pane; the web client renders the same bins as CSS bars.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
YouTube caps tokenless stream URLs at exactly their leading 1 MiB:
plain, open-ended, and oversized requests get 403, and fresh URLs
refuse offset starts, so playback died mid-first-minute. PO tokens
would lift the cap but the token-capable Innertube clients need
signature deciphering that is broken in rustypipe upstream (botguard
was built and tested — ineffective through the iOS client).
The player now streams every http(s) source through a windowed
SourceStream (bounded ~1 MiB ranges, 200-body fallback, eager
seek/reconnect so rejected windows fail typed instead of retrying
forever) and the capture downloader windows the same way. Stream URLs
come from a minimal yt-dlp sidecar again — metadata stays on the
pure-Rust rustypipe extractor — whose cipher-solved URLs stream whole
files at a throttled ~32 KB/s; a missing binary degrades to 1 MiB
streams with a warning. botguard_bin is wired through so streams flip
back to pure Rust when upstream deciphering recovers. Live-verified on
the exact track from the failure log.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- tonic 0.9 -> 0.14 (tonic-prost/tonic-prost-build split), prost 0.14
- ratatui 0.20 -> 0.30 (Frame no longer generic, Line instead of Spans),
crossterm 0.29
- rodio 0.17 -> 0.22: replace the custom symphonia decoder with rodio's
built-in decoder, seeking (try_seek) and position tracking (get_pos);
end-of-stream is now signalled via an EmptyCallback source with a
generation counter so a replaced track can never emit a stale EOS
- replace the vendored stream-download crate with the published
stream-download 0.24 (rustls), with a 30s open timeout
- reqwest 0.12->0.13 (rustls/webpki-roots/query features), base64 0.22
Engine API, rand 0.10, flume 0.12, thiserror 2, dirs 6, toml 1
- unify everything under [workspace.dependencies]; drop unused deps
(once_cell, serde_json in server; confique, secrecy in tidaldy)
- devenv: add protobuf (protoc) for prost-build
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>