An LSPosed/libxposed API 102 module that bridges timed lyrics from supported Android music players into the ColorOS/OPlus lock-screen lyric pipeline.
The module currently ships DexKit-based compatibility adapters for Salt Player and ConePlayer plus SystemUI renderer hooks. Other players should integrate by publishing the lyricInfo contract themselves.
Release assets also include optional LyricProvider APKs for QQ Music, NetEase Cloud Music, Apple Music, Poweramp, and Spotify. They are separate LSPosed modules that forward complete lyric data to ColorOS Live Lyrics Bridge and Lyricon/词幕.
A player-independent transaction layer associates lyric callbacks with media metadata. Events with media IDs, URIs, or complete title/artist hints bind directly; anonymous passive callbacks wait for the next stable metadata observation so preloads and instrumentals cannot shift lyrics across tracks.
Player process:
android.media.session.MediaSession#setMetadata(android.media.MediaMetadata)
For built-in compatibility adapters, a valid lyric captured for the current track takes priority over a simple player-provided MediaMetadata["lyricInfo"] payload. The simple payload remains a fallback until capture succeeds. A player payload containing rawLyric or timed translation data is treated as an explicit enhanced integration and is kept. Self-integrating players should publish the same payload themselves.
{
"songName": "...",
"artist": "...",
"songId": "lockscreen-lyrics-...",
"lyric": "[00:00.00]...",
"rawLyric": "[00:00.000]word[00:00.120]..."
}SystemUI process:
- Reads
lyricInfofrom OPlus media data. - Normalizes the official line-level LRC so each timestamp produces one primary OPlus list item, while translations and word timing remain in the complete model.
- Builds a word-level timeline from
rawLyricwhen available. - Merges timed translation lines from the original
lyricInfointo the word-level model. - Resolves private OPlus media and lyric targets through DexKit, with legacy class-name fallback.
- Draws inside the official lock-screen lyric
TextView.onDraw(Canvas)path. - Maps official items by timestamp, normalized text, and occurrence order so repeated lyrics and pre-roll lines remain stable.
- Uses compact dynamic lyric slots with a
56dpfloor and about12dpvertical padding, keeps official6dpline spacing, uses a moving two-line window for long main lyrics, and places the active line about48dpbelow the viewport center. - Recovers lyric rendering after transient visibility changes without changing item geometry during playback.
- Dynamically recognizes player-provided
lyricInfowithout a hard-coded package name. - Keeps the screen from timing out while the recognized provider's lock-screen lyric UI is actively visible.
The keep-awake logic runs only in the SystemUI process. It is intentionally tied to the official OPlus lock-screen lyric UI, not to playback alone.
SystemUI hooks used by this feature:
android.util.Log.i(String, String)android.util.Log.println(int, String, String)- OPlus Seedling media playback position/state hooks
- Visible official lyric
TextViewtracking ACTION_SCREEN_OFF,ACTION_SCREEN_ON, andACTION_USER_PRESENTbroadcasts inside SystemUI
The module watches OPlus PluginSeedling--Template logs for supported player packages and checks fields such as:
lyricUiMode=true
lockImmersiveMode: true
containerView.isShown=true
hasLyric=false
It holds a 15-second SCREEN_BRIGHT_WAKE_LOCK lease only when all of these are true:
- The current package is either a built-in compatibility adapter or the active provider of a valid
lyricInfopayload. - OPlus lyric UI mode is active.
- Playback is playing.
- There is lyric evidence from a recently visible official lyric view, with only a short grace window from fresh lyric metadata.
- The screen is interactive and the keyguard is still showing.
While active, the module renews the wake-lock lease and pulses PowerManager.userActivity(...) about every 8 seconds so the system treats the lock-screen lyric view as user-visible activity. The wake lock is released on screen off, true keyguard dismissal, playback stop, missing visible lyric evidence, unsupported package, or any condition change. ACTION_USER_PRESENT is followed by a short keyguard recheck so face unlock can keep the lock-screen lyric UI awake when the keyguard remains visible.
Self-integrating players are recognized from the current media session and do not need to be added to scope.list or PLAYER_ADAPTERS. If OPlus changes the PluginSeedling--Template log format for a device/ROM, the keep-awake detection may need a small SystemUI-side update.
This is the preferred integration for players that already own timed lyrics. Publish a valid lyricInfo JSON string in the active media session; the module dynamically binds that session in SystemUI. A timed lyric field enables native line-level lyrics, while optional rawLyric enables this module's word-level renderer.
Known self-integrating players:
- Halcyon —
lyricInfointegration completed.
See the player integration contract. No module APK dependency, package-name registration, or LSPosed player scope is required.
The release bundle includes the Bridge APK and separate provider APKs:
ColorOS-Live-Lyrics-Bridge-<tag>.apk
LyricProvider-QQMusic-<tag>.apk
LyricProvider-163Music-<tag>.apk
LyricProvider-AppleMusic-<tag>.apk
LyricProvider-Poweramp-<tag>.apk
LyricProvider-Spotify-<tag>.apk
Provider APKs are not part of the Bridge module's static scope. Install only the provider you need and enable that provider module for its target player:
LyricProvider-QQMusic:com.tencent.qqmusicLyricProvider-163Music:com.netease.cloudmusic,com.hihonor.cloudmusicLyricProvider-AppleMusic:com.apple.android.music(word-timed and translated lyrics only; background vocals and duet lanes are excluded)LyricProvider-Poweramp:com.maxmpz.audioplayerLyricProvider-Spotify:com.spotify.music
Then restart the target player and SystemUI. Providers keep compatibility with Lyricon/词幕 and send full lyric documents to ColorOS Live Lyrics Bridge, including word timing and translations when the player exposes them.
Compatibility adapters hook legacy players whose native metadata does not expose complete lyric timing through the lyricInfo contract.
Built-in compatibility adapters are:
new SaltPlayerAdapter()
new ConePlayerAdapter("ink.trantor.coneplayer")
new ConePlayerAdapter("ink.trantor.coneplayer.gp")The Salt adapter has been verified against Salt Player 12.0.0 official and alpha07 builds. The ConePlayer adapter has been verified across versions 1.1.3 through 1.1.5 for the formal package, with Google Play package scope included.
Prefer the player-provided lyricInfo contract for new players. Add a PlayerAdapter only for compatibility cases where the player cannot publish lyricInfo itself.
To add another compatibility adapter:
- Add the package name to
src/main/resources/META-INF/xposed/scope.list. - Implement a new
PlayerAdapternext toSaltPlayerAdapter. - Capture that player's real timed lyric source and call
module.cacheTimedLyric(source, rawLyric). - Add the adapter to
PLAYER_ADAPTERS. - Keep both
systemandcom.android.systemuiinscope.list; they are required for OPlus media-history integration and SystemUI-side lock-screen behavior.
If a player outside the built-in adapter scope already writes a valid OPlus lyricInfo metadata field by itself, a source hook is normally unnecessary. For a built-in adapter package, a line-only payload is a fallback; captured rawLyric replaces it once the adapter has data for the current track.
The local ../LSP_api folder is libxposed API 102.0.0. This project follows its current module layout:
- Entry class extends
io.github.libxposed.api.XposedModule - Entry list:
src/main/resources/META-INF/xposed/java_init.list - Module config:
src/main/resources/META-INF/xposed/module.prop - Static scope:
src/main/resources/META-INF/xposed/scope.list - LSPosed repository metadata:
.github/lsposed/ - Hook API:
hook(method).setId(...).setExceptionMode(...).intercept(...)
libxposed-api-stubs is compile-only and is not packaged into the APK. It exists so the project can compile without downloading io.github.libxposed:api:102.0.0; LSPosed provides the real API classes at runtime.
.\scripts\gradle-local.cmd testDebugUnitTest assembleDebugAPK output:
.gradle-local-build\app\outputs\apk\debug\app-debug.apk
JDK 21 is required to compile the Lyrics Core dependency. The helper discovers it from SALT_LYRIC_JAVA_HOME, JAVA_HOME, or common local JDK locations, and maps the repository to a temporary ASCII drive so Gradle works reliably when the checkout path contains non-ASCII characters. The app itself still targets Java 17 bytecode for Android compatibility.
Build Debug APK: runs on pushes tomainand pull requests when project source or build files change. The generated debug APK is uploaded as a workflow artifact.Release APK Bundle: runs after pushing a tag such asv2.3.0, or from manual dispatch. It builds the signed Bridge APK, checks outAndrea-lyz/LyricProvider, builds the signed provider APKs, publishes all APKs to the source release, and mirrors all APKs to the LSPosed repository release with aversionCode-versionNametag such as104-2.3.0.
The release workflow expects these repository secrets:
SIGNING_KEY: base64-encoded keystore file content.KEY_STORE_PASSWORD: keystore password.KEY_ALIAS: signing key alias.KEY_PASSWORD: signing key password.LSP_REPO_TOKEN: PAT with repository-content and release write access toXposed-Modules-Repo/io.github.andrealtb.lockscreenlyrics.LYRIC_PROVIDER_TOKEN: optional PAT for checking outAndrea-lyz/LyricProviderwhen that repository is private.
Release assets are published as ColorOS-Live-Lyrics-Bridge-<tag>.apk, LyricProvider-QQMusic-<tag>.apk, LyricProvider-163Music-<tag>.apk, LyricProvider-AppleMusic-<tag>.apk, LyricProvider-Poweramp-<tag>.apk, and LyricProvider-Spotify-<tag>.apk.
Install and test with a built-in adapter:
adb install -r .gradle-local-build\app\outputs\apk\debug\app-debug.apk
adb shell am force-stop com.salt.music
# Or: adb shell am force-stop ink.trantor.coneplayerEnable the module in LSPosed for the target player package, system, and System UI, then restart the target player and System UI. Reboot only after changing scopes or when validating the system-side media-history capability. Restart the player, play a song, then lock the screen.
Useful logs:
adb logcat -v time -s LockscreenLyrics
adb logcat -v time | Select-String -Pattern "LockscreenLyrics|OplusMediaDataManagerEx|loadLyricInBg|Failed to parse lyric data|LyricsRecyclerView|hasLyric"Expected module log:
LockscreenLyrics: Hooked MediaSession#setMetadata
LockscreenLyrics: Hooked Salt Player lyric result constructors via DexKit: result=..., source=..., scroll=..., count=2
LockscreenLyrics: Hooked ConePlayer lyric parser via DexKit: ...
LockscreenLyrics: Hooked SystemUI official lyric TextView draw hooks
LockscreenLyrics: Registered SystemUI screen timeout receiver
LockscreenLyrics: Accepted lyric transaction from EMBEDDED, rawChars=..., oplusChars=..., identity=..., association=...
LockscreenLyrics: Injected real LRC_FILE lyricInfo for title=...
LockscreenLyrics: Cached SystemUI word lyric model, lines=...
LockscreenLyrics: Lockscreen lyric UI keep-awake ON
LockscreenLyrics: Acquired bright screen timeout wake lock lease=15000ms
LockscreenLyrics: Pulsed screen timeout user activity without changing lights
LockscreenLyrics: Hooked LyricsRecyclerView#setCurrentLyric, methods=...
LockscreenLyrics: LyricsRecyclerView current index=...
LockscreenLyrics: Seedling playback state=3, playing=true, storedPosition=..., computedPosition=..., speed=...
LockscreenLyrics: Custom-drew official lyric TextView at position=..., playing=true, focused=true, line=...
LockscreenLyrics: Refreshed active lyric renderer at position=..., line=...
If you only see Skip lyricInfo injection because no fresh real lyric is cached, the adapter has not captured a timed LRC result in the current process yet, or the current song only has untimed lyrics.
Copyright 2026 Andrea-lyz. This project is released under the Apache License 2.0.
This project uses Accompanist Lyrics Core 0.4.5 (com.mocharealm.accompanist:lyrics-core-jvm), maintained by 6xingyv, for timed-lyric parsing. Accompanist Lyrics Core is also distributed under the Apache License 2.0.
The optional provider APKs are based on the LyricProvider ecosystem by tomakino/LyricProvider. Thanks to tomakino and LyricProvider contributors for the provider architecture these integrations build on.
Android, ColorOS, OPlus, LSPosed, Salt Player, ConePlayer, and other product names are trademarks of their respective owners. This project is not affiliated with or endorsed by those owners.
