better eq profile assigning

This commit is contained in:
Boof2015
2026-07-14 13:42:48 -04:00
parent 7b35e01fe0
commit 60a50d68fb
13 changed files with 1125 additions and 158 deletions
@@ -131,7 +131,7 @@ class AstraAudioRouteModule : Module() {
val device = selectOutputDevice()
val kind = device?.let { kindForType(it.type) } ?: "unknown"
val label = displayLabel(kind, device, selectedRouteName)
val key = routeKey(kind, label)
val key = buildOutputRouteKey(kind, label, deviceAddress(device))
return mapOf(
"key" to key,
@@ -214,6 +214,17 @@ class AstraAudioRouteModule : Module() {
return routeName ?: deviceName ?: defaultLabel(kind)
}
private fun deviceAddress(device: AudioDeviceInfo?): String? =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
try {
device?.address?.trim()?.ifEmpty { null }
} catch (_: Throwable) {
null
}
} else {
null
}
private fun defaultLabel(kind: String): String =
when (kind) {
"speaker" -> "Phone speaker"
@@ -244,27 +255,4 @@ class AstraAudioRouteModule : Module() {
return kind == "bluetooth" || kind == "usb" || kind == "hdmi"
}
private fun routeKey(kind: String, label: String): String {
if (kind == "bluetooth") {
val slug = slug(label)
if (slug.isNotEmpty() && slug != "bluetooth" && slug != "bluetooth-audio") {
return "bluetooth:$slug"
}
}
return when (kind) {
"speaker" -> "speaker"
"wired" -> "wired"
"bluetooth" -> "bluetooth"
"usb" -> "usb"
"hdmi" -> "hdmi"
else -> "unknown"
}
}
private fun slug(value: String): String =
value
.trim()
.lowercase()
.replace(Regex("[^a-z0-9]+"), "-")
.trim('-')
}
@@ -1,5 +1,7 @@
package expo.modules.astraaudioroute
import java.security.MessageDigest
/** Prefer Android's media-attribute prediction, then retain the legacy fallback. */
internal fun <T> selectPredictedOutputDevice(
predicted: List<T>,
@@ -15,3 +17,41 @@ internal fun <T> selectPredictedOutputDevice(
?: connected.firstOrNull { kindFor(it) == "speaker" }
?: connected.first()
}
private fun slug(value: String): String =
value
.trim()
.lowercase()
.replace(Regex("[^a-z0-9]+"), "-")
.trim('-')
private fun isGenericExternalLabel(kind: String, label: String): Boolean {
val normalized = slug(label)
if (normalized.isEmpty()) return true
return when (kind) {
"bluetooth" -> normalized in setOf("bluetooth", "bluetooth-audio", "headphones", "headset")
"usb" -> normalized in setOf("usb", "usb-audio")
"hdmi" -> normalized in setOf("hdmi", "hdmi-audio")
else -> true
}
}
private fun addressToken(address: String?): String? {
val normalized = address?.trim()?.lowercase()?.ifEmpty { null } ?: return null
return MessageDigest
.getInstance("SHA-256")
.digest(normalized.toByteArray(Charsets.UTF_8))
.take(8)
.joinToString("") { byte -> "%02x".format(byte.toInt() and 0xff) }
}
/** Builds a stable local identity without exposing a raw hardware address to JS/storage. */
internal fun buildOutputRouteKey(kind: String, label: String, address: String?): String {
if (kind == "speaker") return "speaker"
if (kind == "wired") return "wired"
if (kind !in setOf("bluetooth", "usb", "hdmi")) return "unknown"
addressToken(address)?.let { return "$kind:id:$it" }
if (!isGenericExternalLabel(kind, label)) return "$kind:name:${slug(label)}"
return kind
}
@@ -1,6 +1,8 @@
package expo.modules.astraaudioroute
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertTrue
import org.junit.Test
class OutputDeviceSelectorTest {
@@ -34,4 +36,33 @@ class OutputDeviceSelectorTest {
assertEquals(bluetooth, selected)
}
@Test
fun externalAddressProducesStablePrivacySafeKey() {
val first = buildOutputRouteKey("bluetooth", "Sony WH-1000XM5", "AA:BB:CC:DD:EE:FF")
val same = buildOutputRouteKey("bluetooth", "Renamed headphones", "aa:bb:cc:dd:ee:ff")
val other = buildOutputRouteKey("bluetooth", "Sony WH-1000XM5", "11:22:33:44:55:66")
assertTrue(first.startsWith("bluetooth:id:"))
assertEquals(first, same)
assertNotEquals(first, other)
assertTrue(!first.contains("aa:bb"))
}
@Test
fun namedExternalDevicesFallBackToLabelIdentity() {
assertEquals("bluetooth:name:sony-wh-1000xm5", buildOutputRouteKey("bluetooth", "Sony WH-1000XM5", null))
assertEquals("usb:name:fiio-k7", buildOutputRouteKey("usb", "FiiO K7", null))
assertEquals("hdmi:name:living-room-tv", buildOutputRouteKey("hdmi", "Living Room TV", null))
}
@Test
fun genericAndBuiltInOutputsKeepClassKeys() {
assertEquals("speaker", buildOutputRouteKey("speaker", "Pixel speaker", "internal"))
assertEquals("wired", buildOutputRouteKey("wired", "3.5mm", "jack"))
assertEquals("bluetooth", buildOutputRouteKey("bluetooth", "Bluetooth audio", null))
assertEquals("usb", buildOutputRouteKey("usb", "USB audio", null))
assertEquals("hdmi", buildOutputRouteKey("hdmi", "HDMI audio", null))
assertEquals("unknown", buildOutputRouteKey("unknown", "Unknown output", null))
}
}