feat(subsonic): stream/download/getCoverArt/scrobble (#296) #9
Reference in New Issue
Block a user
Delete Branch "dev"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Implements Subsonic spec §6 streaming lifecycle so a third-party client can actually play music against the library tree shipped in #295. Closes Fable #296.
Endpoints
stream— serves track bytes viahttp.ServeContent, which handlesRange,If-Modified-Since, andETagfor free.Accept-Ranges: bytesset explicitly so clients probe-then-seek.download— same byte path withContent-Disposition: attachment; filename="..."so browsers save.getCoverArt— resolves id → album (trying the id directly, then as a track → album_id). Prefersalbums.cover_art_pathwhen set (scanner will populate in a later milestone); falls back to a sidecar image next to the first track's file. Supported names in priority order:cover.{jpg,jpeg,png}→folder.{jpg,jpeg,png}. Returns Subsonic 70 when no art found.scrobble—submission=falserecords a now-playing hint into an in-memorynowPlayingMapkeyed by user id.submission=trueand missing-submission are no-ops; M2 will add real play/skip/seek/complete event ingestion.No transcoding in v1
Per agreed scope,
maxBitRateandformatparams are intentionally ignored. Original bytes are served as-is. Real ffmpeg pipeline is M6. Documented inline in the handler.Wire-level side effect
coverArtIDnow returns the album UUID unconditionally (previously empty string whencover_art_pathwas nil). Browse responses advertise a coverArt id on every album/song — worst case clients get a Subsonic 70 when no sidecar exists. Net effect: scanner can skip cover extraction during M1 and clients still display art when the library has sidecar files.Test plan
go build ./...cleango vet ./...cleangolangci-lint run ./internal/subsonic/...cleango test -race ./...greenfindSidecarCoverpriority (cover.* wins over folder.*),imageContentTypemapping,nowPlayingMapwrite/read round-triphttp.ServeContent, which is well-tested. End-to-end client verification happens in the next task (Fable #297 — M1 milestone gate with Feishin/Symfonium).