From 036da9dea8920e3d87d2885cfee0d394bd769a7e Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 3 Jun 2026 14:54:39 -0400 Subject: [PATCH] fix(android): UPnP - clean Sonos friendlyName for picker display Sonos uses the friendlyName format 'Room - Device Type - RINCON_' The picker was showing it verbatim, so the user saw rows like 'Living Room - Sonos Play:1 Media Renderer - RINCON_5CAAFD79...' Now strips on the first ' - ' for Sonos manufacturer matches, so the chip shows just 'Living Room' / 'Kitchen' / etc. Subtitle (manufacturer + model) still renders below per the existing sheet design, so the device-type info isn't lost. Generic UPnP devices that append a '(192.168.x.x)' IP suffix get that stripped too via an end-of-string-anchored regex. Empty / blank friendlyName still falls back to 'Network speaker'. --- .../output/upnp/UpnpDiscoveryController.kt | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) 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 ad0a8ebb..33427cd0 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 @@ -106,7 +106,7 @@ class UpnpDiscoveryController @Inject constructor( ?.let { desc -> UpnpRoute( id = desc.udn, - name = desc.friendlyName.ifBlank { "Network speaker" }, + name = displayName(desc.friendlyName, desc.manufacturer), manufacturer = desc.manufacturer, modelName = desc.modelName, avTransportControlUrl = desc.avTransportControlUrl, @@ -115,6 +115,30 @@ class UpnpDiscoveryController @Inject constructor( } } + /** + * Clean the raw UPnP friendlyName for picker display. Sonos uses + * the format `Room - Device Type - RINCON_`; we strip + * everything after the first " - " so the chip shows just "Living + * Room" / "Kitchen" / etc. Generic UPnP devices (Yamaha, Samsung + * TV, etc.) often append a "(192.168.x.x)" IP suffix; strip that + * too. Empty / blank → "Network speaker" fallback. + * + * Device subtitle (manufacturer + model) is rendered separately + * by the picker sheet, so room-only here doesn't lose information. + */ + private fun displayName(friendlyName: String, manufacturer: String): String { + val trimmed = friendlyName.trim() + if (trimmed.isBlank()) return "Network speaker" + val byVendor = when { + manufacturer.contains("Sonos", ignoreCase = true) -> { + trimmed.substringBefore(" - ", trimmed) + } + else -> trimmed + } + val withoutIpSuffix = byVendor.replace(IP_SUFFIX_REGEX, "").trim() + return withoutIpSuffix.ifBlank { "Network speaker" } + } + private fun fetchBody(url: HttpUrl): String? { val body = runCatching { okHttp.newCall(Request.Builder().url(url).build()).execute().use { @@ -123,4 +147,11 @@ class UpnpDiscoveryController @Inject constructor( }.getOrNull().orEmpty() return body.ifEmpty { null } } + + private companion object { + // " (192.168.0.77)" trailing host suffix some generic UPnP + // devices append. Anchored to end-of-string so it never eats + // a legitimate parenthetical inside a name. + val IP_SUFFIX_REGEX = Regex("""\s*\(\d+\.\d+\.\d+\.\d+\)$""") + } }