Skip to content

api: web audio player for library tracks - #56

Merged
vynulldev merged 2 commits into
vynulldev:mainfrom
kayrozen:web-player
Oct 9, 2026
Merged

vynulldev merged 2 commits into
vynulldev:mainfrom
kayrozen:web-player

Conversation

@kayrozen

@kayrozen kayrozen commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Adds browser playback of library tracks from the web UI.

api/audio.go — GET /api/tracks/{id}/audio streams the track file with
proper Content-Type and Range support. Formats every desktop browser decodes
natively and gaplessly (mp3/wav/flac/ogg) stream as-is; anything else (AIFF,
M4A/AAC) is transcoded to WAV through ffmpeg — the same decoder the analysis
uses — so the browser position lines up with the beat grid and cue times.

api/web/index.html — player bar in the library view: play/pause,
prev/next, seek, volume, elapsed/total, wired to the library table rows and
the now-playing state.

api/audio_test.go — host tests for the endpoint (content types, range
requests, transcode path).

Verified: go build ./... clean, go test ./api/... passes.

Note: this touches api/web/index.html, so it may conflict with #55 — happy
to rebase once that lands.

Add browser playback of library tracks from the web UI:

- api/audio.go: GET /api/tracks/{id}/audio streams the track file with
  proper Content-Type and Range support. Formats browsers decode natively
  and gaplessly (mp3/wav/flac/ogg) stream as-is; others (aiff, m4a) are
  transcoded to WAV through ffmpeg (same decoder as analysis) so browser
  position lines up with the beat grid and cue times.
- api/web/index.html: player bar (play/pause, prev/next, seek, volume,
  elapsed/total) wired to the library table and deck now-playing state.
- api/audio_test.go: host tests for the endpoint (content types, range
  requests, transcode path).
@kayrozen

kayrozen commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

I wanted a little player to test tracks on the fly. It's quick and dirty but willing to modify it to fit.

@vynulldev vynulldev added the enhancement New feature or request label Oct 8, 2026
@vynulldev
vynulldev self-requested a review October 8, 2026 18:41
@vynulldev

Copy link
Copy Markdown
Owner

This is a brilliant and solid feature PR! Thank you!

It builds clean, vet and gofmt are happy, the audio tests pass here, and the SPDX headers are in place. Happy to merge this as is.

Two small things, both optional and both fine as follow-ups rather than anything blocking:

  1. The transcoded WAV cache never evicts so it grows over time, and an edited file leaves its old entry behind. Not a problem for trying it out, but worth a cap or a sweep eventually.
  2. The transcode mutex is global, so it serialises transcodes of different tracks too, not just dedup the same one (it doesn't touch analysis or NFS, which have their own ffmpeg paths). Harmless and not a problem for the one-track-at-a-time player here; if preloading or multiple listeners ever transcode in parallel, a per-file lock would let them run concurrently.

Really glad to have you here. If you feel like tackling either follow-up that's fine, otherwise I'll note them and we can pick them up later. Thanks again!

Review feedback from the maintainer:

- The cache never evicted: edited files left their old size/mtime entries
  behind and the directory grew without bound. After a successful encode,
  sweep stale entries of the same source (hash matches, size/mtime don't)
  and evict oldest-mtime entries while the directory is over a 2 GiB cap.
- The global transcode mutex serialised transcodes of different tracks.
  Replace it with a per-source-file mutex, so the same track's probe +
  Range requests still dedup into one encode but distinct tracks run
  concurrently.
@kayrozen

kayrozen commented Oct 9, 2026

Copy link
Copy Markdown
Contributor Author

Both addressed in e60b132:

  • Cache eviction: after each successful encode, stale entries of the same source (same hash, different size/mtime — the edited-file leftovers) are removed, and the directory is swept down to a 2 GiB cap by evicting oldest-mtime entries. Sweep is best-effort and never fails the request.
  • Per-file locking: the global mutex is replaced by a per-source-file mutex, so a track's probe + Range requests still dedup into one encode while different tracks transcode concurrently.

go build ./... and go test ./api/... pass.

@vynulldev
vynulldev merged commit 09533b9 into vynulldev:main Oct 9, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants