Go: - GET /audio/library?q= — Funkwhale tracks proxy (503 until FUNKWHALE_BASE set) - GET /audio/library/:trackId/listen — audio stream proxy - FUNKWHALE_BASE config key added (env var) Flutter: - AudioLibraryScreen: Device tab (file_picker) + Library tab (Funkwhale) - VideoStitchingService.stitchVideos(): audioOverlayPath + audioVolume params — second FFmpeg pass: amix with configurable volume, falls back if mix fails - EnhancedQuipRecorderScreen: music button, audio chip + volume slider, wired to stitcher Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
79 lines
2.2 KiB
Go
79 lines
2.2 KiB
Go
package handlers
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// AudioHandler proxies Funkwhale audio library requests so the Flutter app
|
|
// doesn't need CORS credentials or direct Funkwhale access.
|
|
type AudioHandler struct {
|
|
funkwhaleBase string // e.g. "http://localhost:5001" — empty = not yet deployed
|
|
}
|
|
|
|
func NewAudioHandler(funkwhaleBase string) *AudioHandler {
|
|
return &AudioHandler{funkwhaleBase: funkwhaleBase}
|
|
}
|
|
|
|
// SearchAudioLibrary proxies GET /audio/library?q=&page= to Funkwhale /api/v1/tracks/
|
|
func (h *AudioHandler) SearchAudioLibrary(c *gin.Context) {
|
|
if h.funkwhaleBase == "" {
|
|
c.JSON(http.StatusServiceUnavailable, gin.H{
|
|
"error": "audio library not yet configured — Funkwhale deployment pending",
|
|
"tracks": []any{},
|
|
"count": 0,
|
|
})
|
|
return
|
|
}
|
|
|
|
q := url.QueryEscape(c.DefaultQuery("q", ""))
|
|
page := url.QueryEscape(c.DefaultQuery("page", "1"))
|
|
|
|
target := fmt.Sprintf("%s/api/v1/tracks/?q=%s&page=%s&playable=true", h.funkwhaleBase, q, page)
|
|
resp, err := http.Get(target) //nolint:gosec
|
|
if err != nil {
|
|
c.JSON(http.StatusBadGateway, gin.H{"error": "audio library unavailable"})
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, _ := io.ReadAll(resp.Body)
|
|
c.Data(resp.StatusCode, "application/json", body)
|
|
}
|
|
|
|
// GetAudioTrackListen proxies the audio stream for a track.
|
|
// Flutter uses this URL in ffmpeg_kit as the audio input.
|
|
func (h *AudioHandler) GetAudioTrackListen(c *gin.Context) {
|
|
if h.funkwhaleBase == "" {
|
|
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "audio library not yet configured"})
|
|
return
|
|
}
|
|
|
|
trackID := c.Param("trackId")
|
|
target := fmt.Sprintf("%s/api/v1/listen/%s/", h.funkwhaleBase, url.PathEscape(trackID))
|
|
|
|
req, err := http.NewRequestWithContext(c.Request.Context(), http.MethodGet, target, nil)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create request"})
|
|
return
|
|
}
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadGateway, gin.H{"error": "audio stream unavailable"})
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
contentType := resp.Header.Get("Content-Type")
|
|
if contentType == "" {
|
|
contentType = "audio/mpeg"
|
|
}
|
|
c.DataFromReader(resp.StatusCode, resp.ContentLength, contentType, resp.Body, nil)
|
|
}
|