package web import ( "net/http" "strings" "git.noroutes.net/NoRoutes/vibetunes/internal/auth" "git.noroutes.net/NoRoutes/vibetunes/internal/database" "git.noroutes.net/NoRoutes/vibetunes/internal/streaming" ) // handleStream serves a song's audio bytes directly (no transcoding in // Phase 1). Authorization happens here, before any file is opened: the // caller must be a signed-in, non-disabled user (enforced by the // RequireUser middleware wrapping this route), and the resolved path is // always re-validated against its music folder root. func (h *Web) handleStream(w http.ResponseWriter, r *http.Request) { user := auth.UserFromContext(r.Context()) songID := r.PathValue("id") song, err := database.GetSong(h.DB, songID) if err != nil { http.NotFound(w, r) return } mediaFile, err := database.GetMediaFile(h.DB, song.MediaFileID) if err != nil { http.NotFound(w, r) return } folder, err := database.GetMusicFolder(h.DB, mediaFile.MusicFolderID) if err != nil { http.NotFound(w, r) return } absPath, err := streaming.ResolvePath(folder.Path, mediaFile.RelativePath) if err != nil { http.Error(w, "403 Forbidden", http.StatusForbidden) return } // Best-effort playback tracking: only record once per playback (on // the request for the start of the file, not every range chunk a // browser issues while seeking/buffering). rangeHeader := r.Header.Get("Range") if rangeHeader == "" || strings.HasPrefix(rangeHeader, "bytes=0-") { _ = database.SetNowPlaying(h.DB, user.ID, song.ID, "web", 0) _ = database.RecordPlayback(h.DB, user.ID, song.ID, 0) } if err := streaming.ServeFile(w, r, absPath, mediaFile.RelativePath); err != nil { http.Error(w, "could not read media file", http.StatusInternalServerError) return } }