mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-09-07 15:07:17 +00:00
fix(player): treat LOCAL_INTERNET_RADIO as a provider source too
Confirmed on real hardware: selecting it bare produces the byte-identical stub RADIO_BROWSER produced, with the source name echoed as the item name, empty type and location, isPresetable false, and PlayStatus empty, while the previously playing stream carries on. Nothing about the speaker's audio changes; only /now_playing does. Its fallback browser is Play URL, which is the page in this app that emits that source: HandlePlayURL builds a ContentItem with Source "LOCAL_INTERNET_RADIO". ALEXA is advertised READY as well but stays unlisted: whether a bare select resumes anything for it is still unverified. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
21af353472
commit
1e7dae35aa
@@ -67,6 +67,7 @@ render(h(Sources, {
|
||||
nowPlayingRevision: 1,
|
||||
sources: { SourceItem: [
|
||||
{ Source: 'RADIO_BROWSER', SourceAccount: '', DisplayName: 'RadioBrowser', Status: 'READY' },
|
||||
{ Source: 'LOCAL_INTERNET_RADIO', SourceAccount: '', DisplayName: 'Local Radio', Status: 'READY' },
|
||||
] },
|
||||
nowPlaying: { Source: 'SPOTIFY', SourceAccount: 'someone' },
|
||||
},
|
||||
@@ -573,7 +574,7 @@ func TestProviderSourceResumesMostRecentStation(t *testing.T) {
|
||||
if err := chromedp.Run(ctx,
|
||||
chromedp.Navigate(server.URL+"/fixture"),
|
||||
chromedp.WaitVisible(`.source-btn`, chromedp.ByQuery),
|
||||
chromedp.Click(`.source-btn`, chromedp.ByQuery),
|
||||
chromedp.Click(`.source-btn:nth-child(1)`, chromedp.ByQuery),
|
||||
chromedp.Poll(`document.querySelector('.source-command-status').textContent === 'Source selected'`, nil),
|
||||
chromedp.Evaluate(`window.navigated`, &navigated),
|
||||
); err != nil {
|
||||
@@ -624,7 +625,7 @@ func TestProviderSourceWithoutRecentsNavigatesInstead(t *testing.T) {
|
||||
if err := chromedp.Run(ctx,
|
||||
chromedp.Navigate(server.URL+"/fixture"),
|
||||
chromedp.WaitVisible(`.source-btn`, chromedp.ByQuery),
|
||||
chromedp.Click(`.source-btn`, chromedp.ByQuery),
|
||||
chromedp.Click(`.source-btn:nth-child(1)`, chromedp.ByQuery),
|
||||
chromedp.Poll(`window.navigated.length === 1`, nil),
|
||||
chromedp.Evaluate(`window.navigated`, &navigated),
|
||||
); err != nil {
|
||||
@@ -635,6 +636,21 @@ func TestProviderSourceWithoutRecentsNavigatesInstead(t *testing.T) {
|
||||
t.Errorf("navigated = %v, want [radiobrowser]", navigated)
|
||||
}
|
||||
|
||||
// LOCAL_INTERNET_RADIO has its own browser: Play URL is what emits that
|
||||
// source, so that is where a click with nothing to resume belongs.
|
||||
var localRadioNav []string
|
||||
if err := chromedp.Run(ctx,
|
||||
chromedp.Evaluate(`window.navigated = []`, nil),
|
||||
chromedp.Click(`.source-btn:nth-child(2)`, chromedp.ByQuery),
|
||||
chromedp.Poll(`window.navigated.length === 1`, nil),
|
||||
chromedp.Evaluate(`window.navigated`, &localRadioNav),
|
||||
); err != nil {
|
||||
t.Fatalf("exercise LOCAL_INTERNET_RADIO without recents: %v", err)
|
||||
}
|
||||
if len(localRadioNav) != 1 || localRadioNav[0] != "playurl" {
|
||||
t.Errorf("navigated = %v, want [playurl]", localRadioNav)
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if writes != 0 {
|
||||
@@ -668,7 +684,7 @@ func TestProviderSourceNavigatesWhenRecentsFail(t *testing.T) {
|
||||
if err := chromedp.Run(ctx,
|
||||
chromedp.Navigate(server.URL+"/fixture"),
|
||||
chromedp.WaitVisible(`.source-btn`, chromedp.ByQuery),
|
||||
chromedp.Click(`.source-btn`, chromedp.ByQuery),
|
||||
chromedp.Click(`.source-btn:nth-child(1)`, chromedp.ByQuery),
|
||||
chromedp.Poll(`window.navigated.length === 1`, nil),
|
||||
chromedp.Evaluate(`window.navigated`, &navigated),
|
||||
); err != nil {
|
||||
|
||||
@@ -24,14 +24,20 @@ const SOURCE_READBACK_DELAYS_MS = [2000, 5000, 10000];
|
||||
// playing. The speaker then reports that stub indefinitely, so the player
|
||||
// shows a source the speaker is not actually playing.
|
||||
//
|
||||
// Verified against real hardware for RADIO_BROWSER. TUNEIN is listed here
|
||||
// because ResolveContentItem treats it identically (both need a Location).
|
||||
// LOCAL_INTERNET_RADIO and ALEXA are also advertised READY but are NOT listed:
|
||||
// whether a bare select resumes something for them is unverified, and leaving
|
||||
// them alone preserves today's behaviour.
|
||||
// Verified against real hardware for RADIO_BROWSER and LOCAL_INTERNET_RADIO:
|
||||
// both produce the byte-identical stub. TUNEIN is listed because
|
||||
// ResolveContentItem treats it identically to RADIO_BROWSER (both need a
|
||||
// Location). ALEXA is also advertised READY but is NOT listed: whether a bare
|
||||
// select resumes anything for it is unverified, so it keeps today's behaviour.
|
||||
//
|
||||
// `page` is the browser to fall back to when there is nothing to resume: the
|
||||
// page in this app that produces content for that source. LOCAL_INTERNET_RADIO
|
||||
// maps to Play URL because that is what HandlePlayURL emits, a ContentItem
|
||||
// with Source "LOCAL_INTERNET_RADIO".
|
||||
const PROVIDER_SOURCES = {
|
||||
RADIO_BROWSER: { page: 'radiobrowser', label: 'RadioBrowser' },
|
||||
TUNEIN: { page: 'tunein', label: 'TuneIn' },
|
||||
RADIO_BROWSER: { page: 'radiobrowser' },
|
||||
TUNEIN: { page: 'tunein' },
|
||||
LOCAL_INTERNET_RADIO: { page: 'playurl' },
|
||||
};
|
||||
|
||||
function isErrorSource(source) {
|
||||
@@ -163,7 +169,7 @@ export function Sources({
|
||||
}
|
||||
|
||||
// Nothing to resume, and a bare select would strand the speaker on a
|
||||
// stub: send the user to the browser to pick a station instead.
|
||||
// stub: send the user to the browser to pick something instead.
|
||||
if (!item) {
|
||||
onNavigate?.(provider.page);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user