An LSPosed module for Android 16 that shows the line you're listening to, in time with the music, in place of the clock. No companion app, no notification access, no background service.
โฌ๏ธ Download ยท ๐ Setup ยท ๐ Report a bug ยท ๐ก Request a feature
This project is still under active development. Things may break, some ROMs may not work yet, and some songs may sync badly.
If you see a problem or have an idea, please open an issue. Every report helps, especially:
- ๐ Bugs: lyrics out of sync, the clock not coming back, crashes in SystemUI
- ๐ฑ ROM / device compatibility: tell me your device, ROM and Android version
- ๐ก Feature requests: new players, styling options, anything you'd like to see
- ๐ถ Wrong or missing lyrics: include the song title and artist
Tip: when reporting a bug, attach your LSPosed log and say which player you used (Spotify, YouTube Music, or ReVanced). It makes fixing it much faster.
Pull requests are welcome too. โญ Star the repo if you like it, it helps other people find it.
| ๐ค Synced lyrics | The active line replaces the clock while it's showing |
| ๐ผ Line or karaoke mode | Highlight each word as it's sung, with an optional smooth sweep |
| ๐ง Spotify, YouTube Music, YT Music ReVanced | Reads the media session directly |
| ๐ Two-row layout | Long lines split evenly, at the clock's own text size |
| ๐ Animated transitions | Lines fade out upward and back in from below |
| ๐จ Fully styleable | Colour, shadow and outline with a live preview |
| โฑ Sync offset | ยฑ1500 ms to compensate for Bluetooth latency |
| โก Live changes | No reboot and no SystemUI restart |
- A rooted device with LSPosed (developed on KernelSU + zygisk_lsposed)
- Android 16 (
minSdk 30,targetSdk 36)
That's it. No SuperLyric and no separate renderer app.
- Download the APK from Releases and install it (or build it yourself).
- Open LSPosed โ Modules โ Status Bar Lyric, enable it, and tick System UI in its scope.
- Reboot (or restart SystemUI).
- Open the app once so the settings file is created, then play a song in Spotify or YouTube Music.
Using your own lyrics API mirror
The default base URL is https://api.lrcmux.dev. To override it without touching code, add this to local.properties:
lrcmuxBaseUrl=https://your-lrcmux-mirror.example.comThe app calls {base}/get?artist=<artist>&title=<title>&level=word&format=json.
Everything runs inside the SystemUI process. SystemUI already holds MEDIA_CONTENT_CONTROL and INTERNET, so the module can read any app's media session without notification-listener access and fetch lyrics directly.
โโโโโโโโโโโโโโโโโโโโโโโโโ SystemUI process โโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโ โ MediaSessionReader โโโถ LrcMuxApiClient โโโถ LyricSyncEngine โโโ โ
โ Spotify / โโโผโโถ (getActiveSessions) GET lrcmux.dev active line โ โ
โ YT Music โ โ โผ โ
โโโโโโโโโโโโโโโโโโ โ LyricView (status bar) โฆ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
SystemUiLyricHookhooksClock.onAttachedToWindowand injects a lyricTextViewnext to the status bar clock.LyricControllerticks every 300 ms;MediaSessionReaderreads title, artist, duration, position and play state.- On a track change, lyrics are fetched from the lrcmux API on a background thread (line and word timing when available).
LyricSyncEnginebinary-searches the active line for the current playback position.- The line is drawn in the status bar. The clock is hidden while a lyric shows and restored when playback stops.
๐จ Rendering details
- Line / per-word (karaoke) mode. In word mode the sung part is drawn in the lyric colour and the rest is dimmed.
WordProgressResolverturns the playback position into a fractional character offset that clips the highlight. Tracks with only line timing fall back to a plain line. - Bilingual lyrics.
TranslationFilterdrops interleaved Chinese translation lines that some sources return, so the lyric doesn't flicker between lines. - Animated transitions. Each new line fades out upward, swaps, and fades back in from below (160 ms).
- Two rows on overflow. Lines that don't fit are split at the word boundary that makes the rows most even, at the clock's own text size. It only shrinks if two full-size rows wouldn't fit. If even two rows don't fit, the second row ellipsizes.
- Width is capped at 62 % of the screen so status bar icons are never pushed off.
โฑ Sync details
Playback position is extrapolated from the media session's last PlaybackState sample. Two things get in the way, and both are handled:
- Apps that lie about the position. YouTube Music (including ReVanced) republishes
position=0mid-song while movinglastPositionUpdateTimeto now. Taken at face value, the lyric would jump back to an early line.MediaSessionReaderkeeps its own anchor and rejects a "back at zero" report when playback never stopped. A genuine backwards seek reports where it landed, so it's still honoured. - Everything else that adds delay (Bluetooth latency, late sampling). It can't be measured from outside, so there's a Sync offset slider (ยฑ1500 ms). Positive shows lines earlier. Bluetooth headphones usually want +150 to +300 ms.
The active line is re-evaluated every 100 ms. The media session is only polled every 500 ms (it's a binder call), and the cached snapshot extrapolates in between. PlaybackState.speed is honoured, so non-1ร playback doesn't drift.
๐ Colour, shadow and outline
- Colour: 10 presets, a
#RRGGBBfield, or "follow the clock colour" (default, tracks light/dark status bars). - Shadow: on/off, colour, blur radius, and X/Y offset (dp).
- Outline: on/off, colour and thickness (dp). Drawn as a stroked pass under the fill pass, which is handy over busy wallpapers on a transparent status bar.
Settings live in a shared preference file that LSPosed makes world-readable (xposedsharedprefs), read inside SystemUI with XSharedPreferences at most once a second, so changes apply live. Opening the app once creates the file.
Prerequisites: JDK 21 and the Android SDK with API 36. Make sure local.properties has a correct sdk.dir.
./gradlew assembleDebug # debug APK
./gradlew assembleRelease # release APK
./gradlew testDebugUnitTest # unit tests (sync engine, word timing)APK output: app/build/outputs/apk/<variant>/StatusBarLyric16-<ver>-<variant>.apk
Signing and publishing a release
Without a keystore, assembleRelease signs with the debug key. To use your own, add this to local.properties (git-ignored):
androidStoreFile=C:/path/to/keystore.jks
androidStorePassword=...
androidKeyAlias=...
androidKeyPassword=...Keep signing with the same key across releases. Android only allows updating an installed app with an APK signed by the same key.
app/src/main/kotlin/io/github/afrizz19/statusbarlyric/
โโโ xposed/ SystemUiLyricHook.kt LSPosed entry; injects the lyric view
โ LyricController.kt tick loop: read โ fetch โ render
โ LyricView.kt the status bar view, styled from the clock
โ HookPrefs.kt reads settings inside SystemUI (XSharedPreferences)
โโโ api/ LrcMuxApiClient.kt HttpURLConnection + org.json (no OkHttp)
โ LyricsRepository.kt
โ LyricsModels.kt
โ TranslationFilter.kt strips interleaved translation lines
โโโ media/ MediaSessionReader.kt, TrackInfo.kt
โโโ prefs/ LyricPrefs.kt shared settings (Style, presets, hex parsing)
โโโ sync/ LyricSyncEngine.kt, WordProgressResolver.kt, WordTimingSynthesizer.kt
โโโ ui/ MainActivity.kt setup screen + style settings
Why no OkHttp / kotlinx-serialization? The networking code is loaded into SystemUI, so it uses only framework classes (
HttpURLConnection,org.json). That way no third-party library gets loaded into the system process. Please keep it that way in pull requests.
- Position accuracy: position is extrapolated between
PlaybackStatesamples, assuming steady playback. Fine for line-level sync. - Unsynced tracks: if lrcmux returns nothing, or the track is instrumental, no lyric is shown (logged only).
- Word-level karaoke needs word timings from lrcmux. Tracks without them show as a plain line.
WordTimingSynthesizercan approximate word windows by character weight, but it is not wired into the renderer yet. - ROM compatibility: the hook looks for
Clockincom.android.systemui.statusbar.policyand.views. Heavily customised ROMs may need another class added toCLOCK_CLASSES. Open an issue with your ROM and I'll look into it. - Legality: make sure your lyrics API usage complies with the source's terms.
- Found a bug or have an idea? Open an issue.
- Want to fix it yourself? Fork the repo, make your change, run
./gradlew testDebugUnitTest, and open a pull request.
Released under the GPL-3.0 license. See LICENSE.