dev → main: Android UPnP/Sonos transport parity + server stream URL extension #79

Merged
bvandeusen merged 69 commits from dev into main 2026-06-04 08:15:15 -04:00
Showing only changes of commit 5db90844cb - Show all commits
@@ -204,22 +204,29 @@ class AVTransportClient(
} }
private fun buildDidlLite(uri: String, mime: String, title: String): String { private fun buildDidlLite(uri: String, mime: String, title: String): String {
// Sonos firmware accepts boolean-style restricted ("true"/"false") only // Sonos requires (a) the rinconnetworks namespace declared on
// and rejects "1"/"0" silently; if it can't parse our DIDL it regenerates // <DIDL-Lite> even if we don't use Rincon elements directly, and
// its own with generic class object.item, no title, and the URL query // (b) a <desc id="cdudn"> element identifying the URI as an
// string as the displayed name (logcat 2026-06-04 confirmed). Match the // external (non-Sonos-library) source. Without those, Sonos
// SoCo convention which the firmware definitely accepts. // discards our metadata content and regenerates its own with
// class=object.item and the URL query string as the title
// (logcat 2026-06-04 confirmed). Match SoCo's pattern.
val safeTitle = title.ifBlank { "Minstrel" } val safeTitle = title.ifBlank { "Minstrel" }
return buildString { return buildString {
append("<DIDL-Lite ") append("<DIDL-Lite ")
append("xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\" ") append("xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\" ")
append("xmlns:dc=\"http://purl.org/dc/elements/1.1/\" ") append("xmlns:dc=\"http://purl.org/dc/elements/1.1/\" ")
append("xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\">") append("xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\" ")
append("xmlns:r=\"urn:schemas-rinconnetworks-com:metadata-1-0/\">")
append("<item id=\"-1\" parentID=\"-1\" restricted=\"true\">") append("<item id=\"-1\" parentID=\"-1\" restricted=\"true\">")
append("<dc:title>").append(xmlEscape(safeTitle)).append("</dc:title>") append("<dc:title>").append(xmlEscape(safeTitle)).append("</dc:title>")
append("<upnp:class>object.item.audioItem.musicTrack</upnp:class>") append("<upnp:class>object.item.audioItem.musicTrack</upnp:class>")
append("<res protocolInfo=\"http-get:*:").append(xmlEscape(mime)) append("<res protocolInfo=\"http-get:*:").append(xmlEscape(mime))
append(":*\">").append(xmlEscape(uri)).append("</res>") append(":*\">").append(xmlEscape(uri)).append("</res>")
append("<desc id=\"cdudn\" ")
append("nameSpace=\"urn:schemas-rinconnetworks-com:metadata-1-0/\">")
append("RINCON_AssociatedZPUDN")
append("</desc>")
append("</item>") append("</item>")
append("</DIDL-Lite>") append("</DIDL-Lite>")
} }