Files
Bose-SoundTouch/pkg/service/soundtouchweb/mount.go
T
Tobias GesellchenandClaude Sonnet 4.6 11a6515f4d feat(tunein): add section-grouped results and load-more pagination
TuneIn's profiles API caps initial results at ~10 per container (Stations,
Shows, etc.) and exposes a Pivots.More.Url cursor for the remainder. This
change wires that cursor through the stack so users can load additional
results without leaving the search view.

- tuneInSearchSection now extracts Pivots.More.Url as bmx_next when
  itemToken is present; absent for containers already at their limit
- TuneInSearchNext fetches the cursor URL, which returns a flat Items[]
  (not nested containers), and maps Station/Program/Topic items using
  the existing play/profile builders
- New GET /v1/search/next and /api/tunein/search/next endpoints with
  matching handlers in both service paths
- TuneInBrowser: flat items state replaced with per-section sections
  state; each section shows a header label and a Load more button when
  a cursor is available; browse/navigate mode is unaffected

Relates to #336.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 14:25:01 +02:00

90 lines
3.2 KiB
Go

package soundtouchweb
import (
"context"
"io/fs"
"net/http"
"time"
"github.com/gesellix/bose-soundtouch/pkg/discovery"
"github.com/go-chi/chi/v5"
)
// Mount registers all routes (static, WebSocket, REST) on r. The
// discovery service is reused by the POST /api/discover handler to
// trigger an on-demand sweep — pass the same instance you used for
// startup discovery so settings (interface, timeout) stay consistent.
func (app *WebApp) Mount(r chi.Router, discoveryService *discovery.UnifiedDiscoveryService) {
// Static assets (embedded in binary)
subFS, _ := fs.Sub(StaticFS, "static")
r.Get("/static/*", http.StripPrefix("/static", http.FileServer(http.FS(subFS))).ServeHTTP)
// WebSocket endpoint
r.Get("/ws", app.HandleWebSocket)
// API endpoints
r.Get("/api/devices", app.HandleAPIDevices)
r.Get("/api/device/{id}", app.HandleAPIDevice)
r.Get("/api/version", app.HandleAPIVersion)
r.Post("/api/discover", func(w http.ResponseWriter, r *http.Request) {
app.HandleAPIDiscover(w, r)
// Trigger discovery
//nolint:contextcheck // Context is created within goroutine
go func() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
app.BroadcastDiscoveryStatus("starting", app.DeviceCount())
app.DiscoverDevices(ctx, discoveryService)
app.BroadcastDiscoveryStatus("completed", app.DeviceCount())
app.BroadcastDeviceList()
}()
})
// Device control endpoints (GET for most actions, POST for volume/bass)
r.Get("/api/control/{id}/{action}", app.HandleAPIControl)
r.Post("/api/control/{id}/{action}", app.HandleAPIControl)
// TuneIn browse, search, and playback
r.Get("/api/tunein/search", app.HandleTuneInSearch)
r.Get("/api/tunein/search/next", app.HandleTuneInSearchNext)
r.Get("/api/tunein/navigate", app.HandleTuneInNavigate)
r.Get("/api/tunein/navigate/*", app.HandleTuneInNavigate)
r.Post("/api/tunein/play/{id}", app.HandlePlayTuneIn)
// Enhanced device control endpoints
r.Post("/api/device-key/{id}/{key}", app.HandleDeviceKey)
r.Post("/api/device-volume/{id}/{volume}", app.HandleDirectVolumeControl)
r.Post("/api/device-power/{id}", app.HandleDevicePower)
r.Get("/api/device-power-status/{id}", app.HandleDevicePowerStatus)
r.Get("/api/device-recents/{id}", app.HandleDeviceRecents)
r.Post("/api/device-play/{id}", app.HandleDevicePlay)
r.Get("/api/zone/{id}", app.HandleGetZone)
r.Post("/api/zone/{id}/add/{slaveId}", app.HandleZoneAdd)
r.Post("/api/zone/{id}/remove/{slaveId}", app.HandleZoneRemove)
r.Post("/api/zone/{id}/dissolve", app.HandleZoneDissolve)
r.Post("/api/zone/{id}/leave", app.HandleZoneLeave)
r.Get("/api/device-ws/{id}", app.HandleDeviceWebSocket)
// RadioBrowser search
r.Get("/api/radiobrowser/search", app.HandleRadioBrowserSearch)
r.Post("/api/radiobrowser/play/{id}", app.HandlePlayRadioBrowser)
// SPA routes — serve index.html for client-side routing
r.Get("/", app.serveIndex)
r.Get("/devices", app.serveIndex)
r.Get("/device/*", app.serveIndex)
r.Get("/tunein", app.serveIndex)
r.Get("/radiobrowser", app.serveIndex)
}
func (app *WebApp) serveIndex(w http.ResponseWriter, _ *http.Request) {
data, _ := StaticFS.ReadFile("static/index.html")
w.Header().Set("Content-Type", "text/html")
_, _ = w.Write(data)
}