From 8a1203c4a1e0f4c6deb9ccc7b8783721494fbd06 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 3 Jun 2026 21:31:31 -0400 Subject: [PATCH] fix(android): revert x-rincon-mp3radio Sonos URI -- plain https for music tracks Co-Authored-By: Claude Sonnet 4.6 --- .../player/output/upnp/AVTransportClient.kt | 27 ++------------- .../output/upnp/UpnpDiscoveryController.kt | 2 -- .../output/upnp/AVTransportClientTest.kt | 34 ++----------------- 3 files changed, 6 insertions(+), 57 deletions(-) diff --git a/android/app/src/main/java/com/fabledsword/minstrel/player/output/upnp/AVTransportClient.kt b/android/app/src/main/java/com/fabledsword/minstrel/player/output/upnp/AVTransportClient.kt index 89c06644..b7dfa85d 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/player/output/upnp/AVTransportClient.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/player/output/upnp/AVTransportClient.kt @@ -12,7 +12,6 @@ import okhttp3.HttpUrl class AVTransportClient( private val soap: SoapClient, private val controlUrl: HttpUrl, - private val applySonosUriFix: Boolean = false, ) { suspend fun setAVTransportURI(uri: String, metadata: String = "") { soap.call( @@ -45,8 +44,7 @@ class AVTransportClient( */ suspend fun setAVTransportURIWithMetadata(uri: String, mime: String, title: String) { val safeTitle = title.ifBlank { "Minstrel" } - val effectiveUri = maybeTransformForSonos(uri) - setAVTransportURI(effectiveUri, buildDidlLite(effectiveUri, mime, safeTitle)) + setAVTransportURI(uri, buildDidlLite(uri, mime, safeTitle)) } /** @@ -56,15 +54,14 @@ class AVTransportClient( */ suspend fun setNextAVTransportURI(uri: String, mime: String, title: String) { val safeTitle = title.ifBlank { "Minstrel" } - val effectiveUri = maybeTransformForSonos(uri) soap.call( controlUrl = controlUrl, serviceType = SERVICE_TYPE, action = "SetNextAVTransportURI", args = mapOf( "InstanceID" to "0", - "NextURI" to effectiveUri, - "NextURIMetaData" to buildDidlLite(effectiveUri, mime, safeTitle), + "NextURI" to uri, + "NextURIMetaData" to buildDidlLite(uri, mime, safeTitle), ), ) } @@ -140,24 +137,6 @@ class AVTransportClient( return TransportInfo(state) } - /** - * Sonos firmware 6.4.2+ silently refuses plain http(s):// URIs in - * SetAVTransportURI -- the SOAP call returns 200 OK but the URI never - * loads. The documented fix (from SoCo et al.) is to replace the - * scheme with x-rincon-mp3radio:// for Sonos targets. Generic UPnP - * renderers (Yamaha, Bose, generic DLNA) reject this scheme, so the - * transform is conditional on the manufacturer. - * - * The transformed URI goes both in the SOAP CurrentURI / NextURI arg - * AND inside the DIDL-Lite `` element so the two stay consistent. - */ - private fun maybeTransformForSonos(uri: String): String { - if (!applySonosUriFix) return uri - return uri - .replaceFirst("https://", "x-rincon-mp3radio://") - .replaceFirst("http://", "x-rincon-mp3radio://") - } - private fun buildDidlLite(uri: String, mime: String, title: String): String { val safeTitle = title.ifBlank { "Minstrel" } return buildString { diff --git a/android/app/src/main/java/com/fabledsword/minstrel/player/output/upnp/UpnpDiscoveryController.kt b/android/app/src/main/java/com/fabledsword/minstrel/player/output/upnp/UpnpDiscoveryController.kt index b43da397..1989f904 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/player/output/upnp/UpnpDiscoveryController.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/player/output/upnp/UpnpDiscoveryController.kt @@ -90,11 +90,9 @@ class UpnpDiscoveryController @Inject constructor( */ fun transportFor(routeId: String): AVTransportClient? { val route = routesInternal.value.firstOrNull { it.id == routeId } ?: return null - val isSonos = route.manufacturer.contains("Sonos", ignoreCase = true) return AVTransportClient( loggingSoapClient(okHttp, route.name), route.avTransportControlUrl, - applySonosUriFix = isSonos, ) } diff --git a/android/app/src/test/java/com/fabledsword/minstrel/player/output/upnp/AVTransportClientTest.kt b/android/app/src/test/java/com/fabledsword/minstrel/player/output/upnp/AVTransportClientTest.kt index 9c15adc4..69bbe329 100644 --- a/android/app/src/test/java/com/fabledsword/minstrel/player/output/upnp/AVTransportClientTest.kt +++ b/android/app/src/test/java/com/fabledsword/minstrel/player/output/upnp/AVTransportClientTest.kt @@ -124,35 +124,7 @@ class AVTransportClientTest { } @Test - fun `setAVTransportURIWithMetadata transforms https to x-rincon-mp3radio when Sonos flag set`() = - runTest { - val sonosClient = AVTransportClient( - SoapClient(OkHttpClient()), - server.url("/MediaRenderer/AVTransport/Control"), - applySonosUriFix = true, - ) - server.enqueue(emptyResponse("SetAVTransportURI")) - sonosClient.setAVTransportURIWithMetadata( - uri = "https://example.com/track.mp3", - mime = "audio/mpeg", - title = "Song", - ) - val body = server.takeRequest().body.readUtf8() - assertTrue( - body.contains( - "x-rincon-mp3radio://example.com/track.mp3", - ), - ) { "missing transformed CurrentURI: $body" } - assertTrue(body.contains("x-rincon-mp3radio://example.com/track.mp3")) { - "transformed URI not present in body: $body" - } - assertFalse(body.contains("https://example.com/track.mp3")) { - "untransformed URI should not appear: $body" - } - } - - @Test - fun `setAVTransportURIWithMetadata leaves https untransformed by default`() = runTest { + fun `setAVTransportURIWithMetadata sends plain https URI for music track`() = runTest { server.enqueue(emptyResponse("SetAVTransportURI")) client.setAVTransportURIWithMetadata( uri = "https://example.com/track.mp3", @@ -161,10 +133,10 @@ class AVTransportClientTest { ) val body = server.takeRequest().body.readUtf8() assertTrue(body.contains("https://example.com/track.mp3")) { - "CurrentURI should be untransformed: $body" + "CurrentURI should be sent as plain https: $body" } assertFalse(body.contains("x-rincon-mp3radio")) { - "Sonos scheme should not appear without flag: $body" + "x-rincon-mp3radio scheme should not appear: $body" } }