feat(api): GET /api/tracks/{id}/stream with Range support

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-21 22:44:48 -04:00
parent 945b773cbd
commit 3656461b20
3 changed files with 214 additions and 0 deletions
+39
View File
@@ -147,3 +147,42 @@ func (h *handlers) handleGetCover(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", imageContentType(path))
http.ServeContent(w, r, filepath.Base(path), info.ModTime(), f)
}
// handleGetStream implements GET /api/tracks/{id}/stream. Opens the file on
// disk and delegates byte-serving to http.ServeContent, which handles Range,
// If-Modified-Since, and ETag based on the file's mod time.
func (h *handlers) handleGetStream(w http.ResponseWriter, r *http.Request) {
id, ok := parseUUID(chi.URLParam(r, "id"))
if !ok {
writeErr(w, http.StatusBadRequest, "bad_request", "invalid track id")
return
}
q := dbq.New(h.pool)
track, err := q.GetTrackByID(r.Context(), id)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
writeErr(w, http.StatusNotFound, "not_found", "track not found")
return
}
h.logger.Error("api: get track for stream failed", "err", err)
writeErr(w, http.StatusInternalServerError, "server_error", "server error")
return
}
f, err := os.Open(track.FilePath)
if err != nil {
// File vanished (scanner indexed it, filesystem lost it). Treat as
// 404 so clients can fall back to the next item in a queue.
writeErr(w, http.StatusNotFound, "not_found", "track file not found")
return
}
defer func() { _ = f.Close() }()
info, err := f.Stat()
if err != nil {
h.logger.Error("api: stat track failed", "err", err)
writeErr(w, http.StatusInternalServerError, "server_error", "server error")
return
}
w.Header().Set("Content-Type", audioContentType(track.FileFormat))
w.Header().Set("Accept-Ranges", "bytes")
http.ServeContent(w, r, filepath.Base(track.FilePath), info.ModTime(), f)
}