Janus

"two faces, one purpose"

android BLE GATT server named after the roman god of transitions and duality. exposes your phone's media as bluetooth low energy characteristics. universal media control for any device. works with spotify, apple music, youtube music, or any android media player.

features

universal media control

hooks into android's MediaSession API. works with any music app that follows android standards. spotify, youtube music, apple music, tidal, soundcloud - all supported automatically.

album art transfer

chunked BLE transfer of album artwork. resizes images to fit BLE MTU limits, encodes as base64, sends in sequential chunks. mercury reassembles on the car thing side.

lyrics sync

fetches time-synced lyrics in LRC format. displays lyrics on the car thing display in real-time with the music. highlights current line based on playback position.

GATT characteristics

janus exposes media data through custom BLE GATT characteristics. mercury scans for these UUIDs and reads the data.

UUIDCharacteristicFormat
0x2001Track TitleUTF-8 string
0x2002Artist NameUTF-8 string
0x2003Album NameUTF-8 string
0x2004Playback StateJSON: {state, position, duration}
0x2005Album ArtChunked base64 (512 bytes/chunk)
0x2006LyricsLRC format
characteristics are read-only from mercury's perspective. future versions may add writeable characteristics for playback control (play/pause, skip).

how it works

┌──────────────────────────────────────────────┐
│          Android MediaSession API            │
│  (Spotify, YouTube Music, Apple Music, etc)  │
└───────────────────┬──────────────────────────┘
                    │ MediaMetadata
                    │ PlaybackState
                    │ MediaController
                    ▼
┌──────────────────────────────────────────────┐
│              Janus Service                   │
│  ┌────────────────────────────────────────┐  │
│  │   MediaSession Listener                │  │
│  │  - onMetadataChanged()                 │  │
│  │  - onPlaybackStateChanged()            │  │
│  │  - Extract title/artist/album/art      │  │
│  └────────────────────────────────────────┘  │
│  ┌────────────────────────────────────────┐  │
│  │   BLE GATT Server                      │  │
│  │  - Start advertising                   │  │
│  │  - Handle characteristic reads         │  │
│  │  - Update values on metadata change    │  │
│  └────────────────────────────────────────┘  │
│  ┌────────────────────────────────────────┐  │
│  │   Lyrics Fetcher (optional)            │  │
│  │  - Query lyrics APIs                   │  │
│  │  - Parse LRC format                    │  │
│  │  - Cache results                       │  │
│  └────────────────────────────────────────┘  │
└──────────────────────────────────────────────┘
                    │ BLE Advertisement
                    │ (Name: "Janus")
                    ▼
              Mercury (scanning)
  1. 1.android app plays music, updates MediaSession
  2. 2.janus receives onMetadataChanged notification
  3. 3.extracts track info, resizes album art, fetches lyrics
  4. 4.updates GATT characteristic values
  5. 5.mercury reads characteristics and pushes to redis

building janus

janus is an android app built with gradle. requires android 8.0+ (API 26) for BLE peripheral mode.

bashbuild.sh
# Clone repository
git clone https://github.com/llizardOS/janus
cd janus

# Build debug APK
./gradlew assembleDebug

# Install to connected device
./gradlew installDebug

# Or build release
./gradlew assembleRelease
you need to grant janus notification listener permission in android settings. this allows it to read MediaSession data from other apps.

required permissions

  • BLUETOOTH - advertise as BLE peripheral
  • BLUETOOTH_ADMIN - start/stop advertising
  • NOTIFICATION_LISTENER - read MediaSession data
  • INTERNET - fetch lyrics (optional)
view on githublearn about mercury