Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Theme
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Create API Key
Add Docs
Lyricon
https://github.com/tomakino/lyricon
Admin
Lyricon is an Android status bar lyrics enhancement tool that displays synchronized, word-by-word
...
Tokens:
8,739
Snippets:
62
Trust Score:
9.1
Update:
1 month ago
Context
Skills
Chat
Benchmark
79.4
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# Lyricon Lyricon is an Android status bar lyrics enhancement tool that displays synchronized lyrics directly in the system status bar. Built on the LSPosed/Xposed framework, it hooks into the Android System UI to inject a custom lyric view that supports word-by-word animations, translations, duet mode, and deep visual customization. The tool requires a rooted Android device with LSPosed framework (Android 8.1+ / API 27+) and works through a modular plugin architecture. The project provides a Provider SDK published to Maven Central (`io.github.proify.lyricon:provider`) that enables third-party music player apps to push lyrics to Lyricon. Plugin developers can create adapters for any music player by implementing the Provider API, which handles service connection, playback state synchronization, and lyric data transmission. The SDK supports simple text lyrics, LRC-style timed lyrics, and rich lyrics with word-level timing, translations, romanization, and secondary text for duet songs. ## LyriconFactory.createProvider Creates and initializes a LyriconProvider instance for connecting to the Lyricon central service. This is the entry point for any plugin that wants to push lyrics to Lyricon. ```kotlin import io.github.proify.lyricon.provider.LyriconFactory import io.github.proify.lyricon.provider.ProviderLogo // Basic provider creation val provider = LyriconFactory.createProvider(context) // Provider with custom logo and metadata val provider = LyriconFactory.createProvider( context = context, providerPackageName = "com.example.musicplugin", playerPackageName = "com.example.musicplayer", logo = ProviderLogo.fromDrawable(context, R.drawable.ic_logo), metadata = providerMetadataOf( "version" to "1.0.0", "author" to "Developer Name" ), processName = "com.example.musicplayer:main" ) // For testing without LSPosed (uses LocalCentralService) val testProvider = LyriconFactory.createProvider( context = context, centralPackageName = "io.github.lyricon.localcentralapp" ) ``` ## LyriconProvider.register / unregister / destroy Manages the provider lifecycle with the Lyricon central service. Registration connects the provider and enables lyric data transmission. ```kotlin val provider = LyriconFactory.createProvider(context) // Register with the central service val success = provider.register() if (success) { // Provider is now active and can send lyrics } // Temporarily disconnect while keeping resources provider.unregister() // Permanently destroy and release all resources provider.destroy() // Check provider state val info = provider.providerInfo println("Package: ${info.providerPackageName}") println("Player: ${info.playerPackageName}") ``` ## RemoteService.addConnectionListener Monitors connection status changes between the provider and the Lyricon central service. Essential for handling reconnection scenarios and service availability. ```kotlin val provider = LyriconFactory.createProvider(context) // DSL-style listener registration provider.service.addConnectionListener { onConnected { provider -> Log.d("Lyricon", "Connected to central service") // Safe to send lyrics now } onReconnected { provider -> Log.d("Lyricon", "Reconnected - resending current song") // Re-send current playback state after reconnection resendCurrentSong(provider.player) } onDisconnected { provider -> Log.w("Lyricon", "Disconnected from central service") // Handle disconnection gracefully } onConnectTimeout { provider -> Log.e("Lyricon", "Connection timeout - service may not be running") // Show user notification or retry } } // Check current connection status val status = provider.service.connectionStatus when { status.isConnected() -> println("Connected") status.isConnecting() -> println("Connecting...") status.isDisconnected() -> println("Disconnected") } provider.register() ``` ## RemotePlayer.sendText Sends simple plain text lyrics without timing information. Ideal for real-time lyric display where precise synchronization is not required. ```kotlin val player = provider.player // Set playback state first player.setPlaybackState(true) // true = playing // Send plain text lyric player.sendText("I can't just be an ordinary friend") // Clear the current lyric display player.sendText(null) // Check if player connection is active if (player.isActive) { player.sendText("Next lyric line here...") } ``` ## RemotePlayer.setSong with Song Sets complete song information including metadata and optional lyrics. Use this for timed lyric synchronization with playback position tracking. ```kotlin import io.github.proify.lyricon.lyric.model.Song val player = provider.player // Basic song metadata (placeholder before lyrics are ready) player.setSong( Song( name = "Ordinary Friend", artist = "David Tao" ) ) // Song with duration (for progress tracking) player.setSong( Song( id = "song_unique_id_123", name = "Ordinary Friend", artist = "David Tao", duration = 245000 // Duration in milliseconds ) ) // Clear current song player.setSong(null) // After setting song, sync playback position player.setPosition(15000) // Current position: 15 seconds player.setPlaybackState(true) // Playing ``` ## RichLyricLine for LRC-style timed lyrics Creates line-based timed lyrics with start and end timestamps. Perfect for standard LRC format lyrics where each line has timing information. ```kotlin import io.github.proify.lyricon.lyric.model.Song import io.github.proify.lyricon.lyric.model.RichLyricLine val player = provider.player // LRC-style timed lyrics player.setSong( Song( id = "song_123", name = "Ordinary Friend", artist = "David Tao", duration = 245000, lyrics = listOf( RichLyricLine( begin = 0, end = 5200, text = "I've been thinking about us lately" ), RichLyricLine( begin = 5200, end = 10500, text = "Wondering where we went wrong" ), RichLyricLine( begin = 10500, end = 15800, text = "I can't just be an ordinary friend" ), RichLyricLine( begin = 15800, end = 21000, text = "Don't want to be just friends" ) ) ) ) // Sync playback position (milliseconds) player.setPosition(6000) // Will display second line player.setPlaybackState(true) // Seek to specific position player.seekTo(10500) // Jump to third line ``` ## LyricWord for word-by-word animation Creates syllable-level timing for karaoke-style word-by-word highlighting. Enables smooth animated transitions between words as the song plays. ```kotlin import io.github.proify.lyricon.lyric.model.Song import io.github.proify.lyricon.lyric.model.RichLyricLine import io.github.proify.lyricon.lyric.model.LyricWord val player = provider.player // Word-by-word lyrics with precise timing player.setSong( Song( id = "song_123", name = "Ordinary Friend", artist = "David Tao", duration = 245000, lyrics = listOf( RichLyricLine( begin = 0, end = 5200, text = "I can't just be friends", words = listOf( LyricWord(text = "I", begin = 0, end = 800), LyricWord(text = " can't", begin = 800, end = 1600), LyricWord(text = " just", begin = 1600, end = 2400), LyricWord(text = " be", begin = 2400, end = 3200), LyricWord(text = " friends", begin = 3200, end = 5200) ) ), RichLyricLine( begin = 5200, end = 10500, text = "Not anymore", words = listOf( LyricWord(text = "Not", begin = 5200, end = 6500), LyricWord(text = " anymore", begin = 6500, end = 10500) ) ) ) ) ) player.setPosition(0) player.setPlaybackState(true) ``` ## RichLyricLine with translation and secondary text Creates rich lyrics with translations, romanization, and secondary text for duet songs. Supports displaying multiple text layers simultaneously. ```kotlin import io.github.proify.lyricon.lyric.model.Song import io.github.proify.lyricon.lyric.model.RichLyricLine import io.github.proify.lyricon.lyric.model.LyricWord val player = provider.player // Full-featured lyrics with translation and duet support player.setSong( Song( id = "song_456", name = "Ordinary Friend", artist = "David Tao", duration = 245000, lyrics = listOf( // Line with translation RichLyricLine( begin = 0, end = 5200, text = "I can't just be an ordinary friend", translation = "Je ne peux pas etre juste un ami ordinaire", words = listOf( LyricWord(text = "I", begin = 0, end = 400), LyricWord(text = " can't", begin = 400, end = 1000), LyricWord(text = " just", begin = 1000, end = 1600), LyricWord(text = " be", begin = 1600, end = 2000), LyricWord(text = " an", begin = 2000, end = 2400), LyricWord(text = " ordinary", begin = 2400, end = 3800), LyricWord(text = " friend", begin = 3800, end = 5200) ) ), // Duet line (secondary singer) RichLyricLine( begin = 5200, end = 10500, text = "I know what you mean", secondary = "(Background vocals: Oh yeah...)", isAlignedRight = true, // Display on right side for duet translation = "Je sais ce que tu veux dire" ), // Line with romanization (for non-Latin scripts) RichLyricLine( begin = 10500, end = 15800, text = "Don't want to be just friends", roma = "Dont want to be just friends", // Romanization translation = "Ne veux pas etre juste amis" ) ) ) ) // Enable translation display player.setDisplayTranslation(true) // Enable romanization display player.setDisplayRoma(true) player.setPosition(0) player.setPlaybackState(true) ``` ## ProviderLogo creation methods Creates logo icons for the provider that display in the Lyricon UI. Supports bitmap, drawable resource, and SVG formats. ```kotlin import io.github.proify.lyricon.provider.ProviderLogo import android.graphics.Bitmap // From drawable resource (recommended) val logoFromResource = ProviderLogo.fromDrawable( context = context, id = R.drawable.ic_music_player_logo ) // From drawable with custom dimensions val logoWithSize = ProviderLogo.fromDrawable( context = context, id = R.drawable.ic_logo, width = 48, height = 48 ) // From Bitmap directly val bitmap: Bitmap = // ... your bitmap val logoFromBitmap = ProviderLogo.fromBitmap( bitmap = bitmap, recycle = true // Recycle source bitmap after conversion ) // From SVG string val svgContent = """<svg viewBox="0 0 24 24">...</svg>""" val logoFromSvg = ProviderLogo.fromSvg(svgContent) // From Base64-encoded PNG val base64Png = "iVBORw0KGgoAAAANSUhEUgAA..." val logoFromBase64 = ProviderLogo.fromBase64(base64Png) // Use in provider creation val provider = LyriconFactory.createProvider( context = context, logo = logoFromResource ) ``` ## AndroidManifest.xml configuration Required manifest configuration to declare your app as a Lyricon plugin module. The metadata entries are read by Lyricon to identify and display your plugin. ```xml <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.musicplugin"> <application android:label="My Music Plugin" android:icon="@mipmap/ic_launcher"> <!-- Required: Declare as Lyricon module --> <meta-data android:name="lyricon_module" android:value="true" /> <!-- Required: Plugin author name --> <meta-data android:name="lyricon_module_author" android:value="Developer Name" /> <!-- Required: Plugin description --> <meta-data android:name="lyricon_module_description" android:value="Provides lyrics from My Music Player app" /> <!-- Optional: Feature tags for UI display --> <meta-data android:name="lyricon_module_tags" android:resource="@array/lyricon_module_tags" /> </application> </manifest> ``` ```xml <!-- res/values/arrays.xml --> <resources> <string-array name="lyricon_module_tags"> <!-- Supports word-by-word / karaoke-style lyrics --> <item>$syllable</item> <!-- Supports translated lyrics --> <item>$translation</item> </string-array> </resources> ``` ## Complete plugin implementation example Full working example of a Lyricon plugin that integrates with a music player service to push lyrics to the status bar. ```kotlin import android.app.Service import android.content.Intent import android.os.IBinder import io.github.proify.lyricon.provider.LyriconFactory import io.github.proify.lyricon.provider.LyriconProvider import io.github.proify.lyricon.provider.ProviderLogo import io.github.proify.lyricon.lyric.model.Song import io.github.proify.lyricon.lyric.model.RichLyricLine import io.github.proify.lyricon.lyric.model.LyricWord class LyricProviderService : Service() { private lateinit var provider: LyriconProvider private var isConnected = false override fun onCreate() { super.onCreate() // Initialize the provider provider = LyriconFactory.createProvider( context = this, logo = ProviderLogo.fromDrawable(this, R.drawable.ic_logo) ) // Set up connection monitoring provider.service.addConnectionListener { onConnected { isConnected = true // Send current playing song if any getCurrentSong()?.let { sendSong(it) } } onReconnected { isConnected = true getCurrentSong()?.let { sendSong(it) } } onDisconnected { isConnected = false } onConnectTimeout { isConnected = false // Retry connection after delay retryConnection() } } // Register with Lyricon service provider.register() } fun sendSong(songData: SongData) { if (!isConnected) return val player = provider.player player.setSong( Song( id = songData.id, name = songData.title, artist = songData.artist, duration = songData.durationMs, lyrics = songData.lyrics.map { line -> RichLyricLine( begin = line.startMs, end = line.endMs, text = line.text, translation = line.translation, words = line.words?.map { word -> LyricWord( text = word.text, begin = word.startMs, end = word.endMs ) } ) } ) ) player.setDisplayTranslation(songData.showTranslation) player.setPosition(songData.currentPositionMs) player.setPlaybackState(songData.isPlaying) } fun updatePosition(positionMs: Long) { if (isConnected) { provider.player.setPosition(positionMs) } } fun updatePlaybackState(isPlaying: Boolean) { if (isConnected) { provider.player.setPlaybackState(isPlaying) } } override fun onDestroy() { provider.destroy() super.onDestroy() } override fun onBind(intent: Intent?): IBinder? = null private fun getCurrentSong(): SongData? = null // Implement based on your player private fun retryConnection() { /* Implement retry logic */ } } // Data classes for your music player integration data class SongData( val id: String, val title: String, val artist: String, val durationMs: Long, val currentPositionMs: Long, val isPlaying: Boolean, val showTranslation: Boolean, val lyrics: List<LyricLineData> ) data class LyricLineData( val startMs: Long, val endMs: Long, val text: String, val translation: String? = null, val words: List<WordData>? = null ) data class WordData( val text: String, val startMs: Long, val endMs: Long ) ``` ## Gradle dependency configuration Add the Lyricon Provider SDK dependency to your Android project. The SDK is published to Maven Central. ```kotlin // build.gradle.kts (Module level) plugins { id("com.android.application") // or "com.android.library" id("org.jetbrains.kotlin.android") } android { namespace = "com.example.musicplugin" compileSdk = 35 defaultConfig { minSdk = 27 // Minimum Android 8.1 (API 27) // ... } } dependencies { // Lyricon Provider SDK implementation("io.github.proify.lyricon:provider:0.1.68") // Required transitive dependencies (included automatically) // - androidx.core:core-ktx // - androidx.appcompat:appcompat-resources // - org.jetbrains.kotlinx:kotlinx-serialization-json } ``` ```kotlin // settings.gradle.kts dependencyResolutionManagement { repositories { google() mavenCentral() // Required for Lyricon SDK } } ``` Lyricon serves as a bridge between music player applications and the Android status bar, enabling any music app to display synchronized lyrics without modifying the system UI directly. The primary use cases include: music player developers adding status bar lyric support to their apps through the Provider SDK, enthusiasts creating lyric plugins for popular streaming services that don't natively support status bar lyrics, and power users customizing their Android experience with advanced lyric visualization including animations, translations, and duet mode display. The integration pattern follows a client-server architecture where the Lyricon core module (activated via LSPosed) runs within System UI as the central service, while music player plugins act as clients pushing lyric data through the Provider API. Plugins can be standalone apps that hook into existing music players using Xposed, or they can be integrated directly into music player source code. The SDK handles all IPC communication, connection management, and data serialization, allowing developers to focus on extracting lyrics from their target music source and formatting them into the Song/RichLyricLine data models.