Skip to content

Usage

Using this library is simple: construct the lyrics data, pass it to KaraokeLyricsView, and you're done.

Construct the data

Though you can construct the lyrics data manually, we still recommend you to use our lyrics-core to parse lyrics files.

val lyrics = SyncedLyrics(
    lines = listOf(
        KaraokeLine(
            start = 0,
            end = 700,
            syllables = listOf(
                KaraokeSyllable("Hel", 0, 300),
                KaraokeSyllable("lo", 300, 700)
            ),
             // ... other params like translation, isAccompaniment, alignment
        ),
        // ...
    )
)

Pass to the Composable

Pass the constructed lyrics to KaraokeLyricsView. Typical usage with a player position:

@Composable
fun PlayerScreen(player: Player) {
    // 1. Get current position (as state or simple value provider)
    // Note: KaraokeLyricsView accepts a lambda () -> Int for position
    // to avoid excessive recompositions if you read it directly inside the view.
    val currentPosition = { player.currentPosition.toInt() }

    // 2. Remember a list state
    val listState = rememberLazyListState()

    KaraokeLyricsView(
        listState = listState,
        lyrics = lyrics,
        currentPosition = currentPosition,
        onLineClicked = { line -> player.seekTo(line.start.toLong()) },
        onLinePressed = { line -> /* handle long press */ },
        modifier = Modifier.fillMaxWidth()
    )
}

Adjust modifier, normalLineTextStyle, accompanimentLineTextStyle and other parameters according to your design.

Prewarm (optional)

To avoid the first-frame rendering cost (jank) caused by font loading and glyph generation, you can prewarm the NativeTextEngine and SdfAtlasManager.

This is particularly useful when you have large font files or complex scripts (like CJK).

// 1. Create and initialize the engine and atlas manager
// You might want to do this in a higher-level state holder or remember call
val nativeEngine = remember { NativeTextEngine() }
val sharedAtlasManager = rememberSdfAtlasManager(2048, 2048)

val platformContext = getPlatformContext()
val fontBytes = rememberFontResourceBytes(Res.font.my_font) // Or your font loading logic

// 2. Initialize engine (ensure this runs before usage)
LaunchedEffect(nativeEngine, fontBytes) {
    nativeEngine.init(2048, 2048)
    if (fontBytes != null) {
        nativeEngine.loadFont(fontBytes)
    }
    // Load fallbacks if needed
    val fallbacks = getSystemFallbackFontBytes(platformContext)
    fallbacks.forEach { nativeEngine.loadFallbackFont(it) }
}

// 3. (Optional) Warmup ASCII characters
val fontSize = with(LocalDensity.current) { textStyle.fontSize.toPx() }
val fontWeight = textStyle.fontWeight?.weight?.toFloat() ?: 400f

LaunchedEffect(Unit) {
    warmupAscii(
        nativeEngine,
        sharedAtlasManager,
        fontSize,
        fontWeight
    )
}

// 4. Pass them to KaraokeLyricsView
KaraokeLyricsView(
    lyrics = lyrics,
    // ...
    sharedAtlasManager = sharedAtlasManager,
    sharedNativeEngine = nativeEngine // Pass the pre-warmed engine
)

By sharing the engine and atlas manager, you ensure that the glyphs generated during warmup are reused by the lyrics view.

[!NOTE] If you don't share the nativeEngine, the view will create its own instance, rendering the warmup useless.

Notes

  • Prefer building lyrics with lyrics-core when available for accurate timing and richer metadata.
  • Keep timing units consistent (ms in examples).

See lyrics-core for helpers to parse common lyric formats.