fix(android): revert x-rincon-mp3radio Sonos URI -- plain https for music tracks
android / Build + lint + test (push) Successful in 4m13s
android / Build + lint + test (push) Successful in 4m13s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+3
-24
@@ -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 `<res>` 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 {
|
||||
|
||||
-2
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+3
-31
@@ -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(
|
||||
"<CurrentURI>x-rincon-mp3radio://example.com/track.mp3</CurrentURI>",
|
||||
),
|
||||
) { "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("<CurrentURI>https://example.com/track.mp3</CurrentURI>")) {
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user