fix(player,discovery): normalize uuid: prefix for STORED_MUSIC accounts; fill MediaServer.Address

The DLNA UDN from discovery carries a "uuid:" prefix (e.g.
uuid:fa095ecc-...), but a SoundTouch STORED_MUSIC account is the bare UUID
plus /0 (the speaker's /sources reports the bare form). The mismatch made
the player Library tab show an "Add" button for an already-registered
server, and an Add via the UI would have registered a wrong "uuid:.../0"
account. Normalize (strip "uuid:") when mapping discovery results to the DTO
and when building the account in HandleAddLibraryServer, so the LAN list and
the registered list agree and Add builds the correct account. Verified live:
discover now returns the bare UDN, matching /sources.

Also populate the previously-unset MediaServer.Address from the
ContentDirectory control URL host.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tobias Gesellchen
2026-06-09 22:50:49 +02:00
co-authored by Claude Opus 4.8
parent ab7c857630
commit e399b5ab00
4 changed files with 145 additions and 39 deletions
+8
View File
@@ -3,6 +3,7 @@ package discovery
import (
"context"
"log/slog"
"net/url"
"sync"
"time"
)
@@ -140,6 +141,13 @@ func mediaServerFromDescription(desc *Description) (MediaServer, bool) {
CDSControlURL: svc.ControlURL,
}
// Populate Address from the CDS control URL host so callers know which
// host:port to reach the device on. The control URL is absolute after
// FetchDescription resolves it; parse errors leave Address empty.
if u, err := url.Parse(svc.ControlURL); err == nil {
srv.Address = u.Host
}
// Walk sub-devices to fill in UDN / FriendlyName if the root is sparse
// (some devices put it all in the sub-device, e.g. FRITZ!Box).
fillFromTree(desc, &srv)
+19 -2
View File
@@ -47,8 +47,19 @@ func TestMediaServerFromDescription_WithCDS(t *testing.T) {
t.Errorf("IconURL %q is not absolute", srv.IconURL)
}
t.Logf("MediaServer: UDN=%q FriendlyName=%q CDSControlURL=%q IconURL=%q",
srv.UDN, srv.FriendlyName, srv.CDSControlURL, srv.IconURL)
// Address must be the host:port from the CDS control URL.
// cannedDescriptionXML has URLBase http://192.0.2.1:49000 and CDS
// controlURL /ctl/ContentDir, so Address = "192.0.2.1:49000".
if srv.Address == "" {
t.Error("Address is empty")
}
if srv.Address != "192.0.2.1:49000" {
t.Errorf("Address = %q, want %q", srv.Address, "192.0.2.1:49000")
}
t.Logf("MediaServer: UDN=%q FriendlyName=%q CDSControlURL=%q IconURL=%q Address=%q",
srv.UDN, srv.FriendlyName, srv.CDSControlURL, srv.IconURL, srv.Address)
}
// TestMediaServerFromDescription_WithoutCDS verifies that a description
@@ -147,6 +158,12 @@ func TestMediaServerFromDescription_FlatServer(t *testing.T) {
if srv.IconURL != wantIcon {
t.Errorf("IconURL = %q, want %q", srv.IconURL, wantIcon)
}
// Address must reflect the host:port of the CDS control URL.
// URLBase is http://198.51.100.20:8200 and CDS controlURL is /ctl/ContentDir.
if srv.Address != "198.51.100.20:8200" {
t.Errorf("Address = %q, want %q", srv.Address, "198.51.100.20:8200")
}
}
// isAbsoluteURL returns true when s starts with "http://" or "https://".