From 0662c9d5ccb0cee6d3d87c7274d6157fd91a230f Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 3 Jun 2026 10:27:02 -0400 Subject: [PATCH] feat(android): output picker foundation - mediarouter + OutputRoute Bluetooth slice (1/5). Adds the androidx.mediarouter 1.7.0 dep, declares BLUETOOTH_CONNECT (needed on Android 12+ to enumerate paired BT devices by name), and lays down the OutputRoute domain model. OutputRoute decouples the picker UI from MediaRouter.RouteInfo (framework class, can't be constructed in JVM tests - same constraint we hit with LikeMediaCallback). The Protocol enum includes UPNP/CAST/SONOS placeholders so the next slice slots in without a data-model rename - see docs/superpowers/specs/2026-06-03-android-output-picker-upnp-scope.md for the deferred work. Controller + ViewModel + Compose UI land in follow-up commits. --- android/app/build.gradle.kts | 1 + android/app/src/main/AndroidManifest.xml | 1 + .../minstrel/player/output/OutputRoute.kt | 72 +++++++++++++++++++ android/gradle/libs.versions.toml | 2 + 4 files changed, 76 insertions(+) create mode 100644 android/app/src/main/java/com/fabledsword/minstrel/player/output/OutputRoute.kt diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 4e72d441..583e709d 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -173,6 +173,7 @@ dependencies { implementation(libs.media3.exoplayer) implementation(libs.media3.session) implementation(libs.media3.datasource.okhttp) + implementation(libs.mediarouter) implementation(libs.coil.compose) implementation(libs.coil.network.okhttp) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 1ec6dad8..2315ed80 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -9,6 +9,7 @@ + Kind.BuiltIn + MediaRouter.RouteInfo.DEVICE_TYPE_WIRED_HEADSET, + MediaRouter.RouteInfo.DEVICE_TYPE_WIRED_HEADPHONES, + -> Kind.Wired + MediaRouter.RouteInfo.DEVICE_TYPE_BLUETOOTH_A2DP -> Kind.Bluetooth + MediaRouter.RouteInfo.DEVICE_TYPE_TV -> Kind.Other + MediaRouter.RouteInfo.DEVICE_TYPE_SPEAKER -> Kind.Other + else -> Kind.Other + } + val connected = + route.connectionState == MediaRouter.RouteInfo.CONNECTION_STATE_CONNECTED + return OutputRoute( + id = route.id, + name = route.name, + description = route.description, + kind = kind, + protocol = Protocol.SYSTEM, + isConnected = connected, + ) + } + } +} diff --git a/android/gradle/libs.versions.toml b/android/gradle/libs.versions.toml index 8a2be34f..5a35e7a7 100644 --- a/android/gradle/libs.versions.toml +++ b/android/gradle/libs.versions.toml @@ -22,6 +22,7 @@ kotlinx-coroutines = "1.9.0" kotlinx-datetime = "0.6.1" kotlinx-serialization-converter = "1.0.0" media3 = "1.10.1" +mediarouter = "1.7.0" coil = "3.0.0-rc02" palette = "1.0.0" timber = "5.0.1" @@ -71,6 +72,7 @@ media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = media3-session = { module = "androidx.media3:media3-session", version.ref = "media3" } media3-datasource-okhttp = { module = "androidx.media3:media3-datasource-okhttp", version.ref = "media3" } media3-ui = { module = "androidx.media3:media3-ui", version.ref = "media3" } +mediarouter = { module = "androidx.mediarouter:mediarouter", version.ref = "mediarouter" } coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" } coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" } androidx-palette = { module = "androidx.palette:palette-ktx", version.ref = "palette" }