mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-18 08:36:13 +00:00
fix(preset): wrap LOCAL_INTERNET_RADIO stream URL in Orion location
The speaker's BMX module calls GET on the stored preset location and expects a BmxPlaybackResponse JSON from the AfterTouch Orion endpoint. Storing a bare stream URL (e.g. http://davefmradio.no-ip.org:8000/stream) causes BMX to receive raw ICY audio, which it cannot parse; playback silently stays on the previous source and no error is surfaced. Add --service-url / SOUNDTOUCH_SERVICE_URL to `preset set`. When set alongside --source LOCAL_INTERNET_RADIO and a raw HTTP(S) location, the CLI wraps the stream URL in the Orion station endpoint: <service-url>/core02/svc-bmx-adapter-orion/prod/orion/station ?data=<base64({"name":"…","imageUrl":"…","streamUrl":"…"})> Without --service-url the command still works but prints a clear warning explaining why the saved preset is likely to not play, rather than saving a silently broken location. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
d70e336e52
commit
e54738d367
@@ -1,7 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/gesellix/bose-soundtouch/pkg/models"
|
||||
@@ -85,6 +88,7 @@ type presetParams struct {
|
||||
name string
|
||||
itemType string
|
||||
artwork string
|
||||
serviceURL string
|
||||
}
|
||||
|
||||
// extractPresetParams extracts parameters from CLI context
|
||||
@@ -97,9 +101,38 @@ func extractPresetParams(c *cli.Context) *presetParams {
|
||||
name: c.String("name"),
|
||||
itemType: c.String("type"),
|
||||
artwork: c.String("artwork"),
|
||||
serviceURL: strings.TrimRight(c.String("service-url"), "/"),
|
||||
}
|
||||
}
|
||||
|
||||
// buildOrionLocation wraps a raw stream URL in the AfterTouch Orion station
|
||||
// endpoint that the speaker's BMX module expects when playing LOCAL_INTERNET_RADIO
|
||||
// content. The speaker calls GET on the stored preset location and expects a
|
||||
// BmxPlaybackResponse JSON — not raw audio bytes — which is why direct stream
|
||||
// URLs silently fail to start playback.
|
||||
func buildOrionLocation(serviceURL, name, imageURL, streamURL string) string {
|
||||
payload := struct {
|
||||
Name string `json:"name"`
|
||||
ImageURL string `json:"imageUrl"`
|
||||
StreamURL string `json:"streamUrl"`
|
||||
}{
|
||||
Name: name,
|
||||
ImageURL: imageURL,
|
||||
StreamURL: streamURL,
|
||||
}
|
||||
|
||||
data, _ := json.Marshal(payload)
|
||||
encoded := url.QueryEscape(base64.StdEncoding.EncodeToString(data))
|
||||
|
||||
return serviceURL + "/core02/svc-bmx-adapter-orion/prod/orion/station?data=" + encoded
|
||||
}
|
||||
|
||||
// isOrionLocation reports whether location is already an Orion station URL so
|
||||
// we don't double-wrap it.
|
||||
func isOrionLocation(location string) bool {
|
||||
return strings.Contains(location, "/core02/svc-bmx-adapter-orion/")
|
||||
}
|
||||
|
||||
// resolveLocationAndMetadata resolves location and fetches metadata if needed
|
||||
func resolveLocationAndMetadata(params *presetParams) error {
|
||||
originalLocation := params.location
|
||||
@@ -108,6 +141,26 @@ func resolveLocationAndMetadata(params *presetParams) error {
|
||||
params.source = resolvedSource
|
||||
params.location = resolvedLocation
|
||||
|
||||
// For LOCAL_INTERNET_RADIO with a raw stream URL, the speaker's BMX module
|
||||
// calls GET on the preset location expecting a BmxPlaybackResponse JSON (the
|
||||
// Orion station format). A direct stream URL returns raw audio, which BMX
|
||||
// cannot parse, so playback silently stays on the previous source.
|
||||
// Wrap the stream URL in the Orion endpoint when --service-url is provided.
|
||||
if params.source == "LOCAL_INTERNET_RADIO" &&
|
||||
params.serviceURL != "" &&
|
||||
!isOrionLocation(params.location) &&
|
||||
(strings.HasPrefix(params.location, "http://") || strings.HasPrefix(params.location, "https://")) {
|
||||
params.location = buildOrionLocation(params.serviceURL, params.name, params.artwork, resolvedLocation)
|
||||
fmt.Printf(" Wrapped stream URL in Orion location for LOCAL_INTERNET_RADIO\n")
|
||||
} else if params.source == "LOCAL_INTERNET_RADIO" &&
|
||||
params.serviceURL == "" &&
|
||||
!isOrionLocation(params.location) &&
|
||||
(strings.HasPrefix(params.location, "http://") || strings.HasPrefix(params.location, "https://")) {
|
||||
fmt.Printf(" ⚠️ --service-url not set: storing raw stream URL as location.\n")
|
||||
fmt.Printf(" The speaker's BMX module expects an Orion station URL, not raw audio.\n")
|
||||
fmt.Printf(" Re-run with --service-url <https://your-aftertouch-host> to fix this.\n")
|
||||
}
|
||||
|
||||
// If metadata (name or artwork) is missing, try to fetch it
|
||||
if params.name == "" || params.artwork == "" {
|
||||
var (
|
||||
|
||||
@@ -385,6 +385,11 @@ func main() {
|
||||
Name: "artwork",
|
||||
Usage: "Artwork URL",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "service-url",
|
||||
Usage: "AfterTouch service HTTPS URL (e.g. https://soundtouch.local). Required for LOCAL_INTERNET_RADIO: the speaker's BMX module calls GET on the preset location and expects an Orion JSON response, not raw audio. When provided, the stream URL is automatically wrapped in the Orion station endpoint.",
|
||||
EnvVars: []string{"SOUNDTOUCH_SERVICE_URL"},
|
||||
},
|
||||
},
|
||||
Before: RequireHost,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user