mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-24 14:47:23 +00:00
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>
90 lines
3.2 KiB
Go
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)
|
|
}
|