Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/6xingyv/accompanist-lyrics-ui/llms.txt

Use this file to discover all available pages before exploring further.

Real songs often have two singers performing simultaneously, or a main melody with background harmonies weaving in and out. Accompanist Lyrics UI models this through a parent–child relationship between KaraokeLine.MainKaraokeLine and KaraokeLine.AccompanimentKaraokeLine. The main line anchors the position in the list; accompaniment lines orbit around it, appearing and disappearing with scale, fade, slide, and expand transitions.

The Data Model

The core of the duet data model is KaraokeLine.MainKaraokeLine, which carries an optional list of accompaniment lines:
// MainKaraokeLine holds the primary vocal and any accompaniment lines
val duetLine = KaraokeLine.MainKaraokeLine(
    syllables = listOf(
        KaraokeSyllable(content = "I ", start = 1000, end = 1400),
        KaraokeSyllable(content = "need ", start = 1400, end = 1800),
        KaraokeSyllable(content = "you", start = 1800, end = 2400)
    ),
    start = 1000,
    end = 2400,
    alignment = KaraokeAlignment.Start,
    accompanimentLines = listOf(
        KaraokeLine.AccompanimentKaraokeLine(
            syllables = listOf(
                KaraokeSyllable(content = "I ", start = 2500, end = 2900),
                KaraokeSyllable(content = "need ", start = 2900, end = 3300),
                KaraokeSyllable(content = "you ", start = 3300, end = 3700),
                KaraokeSyllable(content = "too", start = 3700, end = 4200)
            ),
            start = 2500,
            end = 4200,
            alignment = KaraokeAlignment.End
        )
    )
)
Each AccompanimentKaraokeLine has its own independent:
  • syllable list — with separate start/end timestamps per syllable
  • start and end timestamps — the window during which the accompaniment line is active
  • alignment — typically KaraokeAlignment.End to visually separate it from the main voice

Layout

Accompaniment lines are rendered as siblings of their anchor main line within a single Column. KaraokeLineText splits the accompaniment lines into two groups relative to the main line’s start time:
  • Lines where accompanimentLine.start < mainLine.start are placed above the main line.
  • Lines where accompanimentLine.start >= mainLine.start are placed below the main line.
This produces a natural conversation layout where a second voice that enters before the main melody appears above it, and one that echoes below appears beneath it.

Alignment

Duet partners typically use KaraokeAlignment.End to anchor them to the trailing edge of the layout — the visual right in LTR layouts and the visual left in RTL layouts. The library maps KaraokeAlignment to visual alignment by combining it with RTL text direction detection:
val isRightAligned = remember(line.alignment, isLineRtl) {
    when (line.alignment) {
        KaraokeAlignment.Start, KaraokeAlignment.Unspecified -> isLineRtl
        KaraokeAlignment.End -> !isLineRtl
    }
}
So KaraokeAlignment.End on an LTR line produces right-alignment (isRightAligned = true), while the same alignment on an RTL line produces left-alignment — exactly what you want for a duet where both parties are singing in the same script.

Animated Visibility

Accompaniment lines animate in and out using a combination of four simultaneous transitions, all sharing a 600 ms duration:
  • scaleIn / scaleOut — scales from/to the line’s anchor corner (the alignment edge)
  • fadeIn / fadeOut — smooth opacity transition
  • slideInVertically / slideOutVertically — slides in from the direction the line will appear
  • expandVertically / shrinkVertically — avoids layout jump by smoothly reserving space
A line is only present in the composition at all when the current playback time is within [start - 600ms, end + 600ms]:
val isAccompanimentVisible by remember(bgLine) {
    derivedStateOf {
        val currentTime = currentTimeProvider()
        currentTime >= (bgLine.start - 600) && currentTime <= (bgLine.end + 600)
    }
}

AnimatedVisibility(
    visible = isAccompanimentVisible,
    enter = scaleIn(tween(600), transformOrigin = TransformOrigin(
        if (isRightAligned) 1f else 0f,
        if (isBefore) 1f else 0f
    )) + fadeIn(tween(600)) + slideInVertically(tween(600)) + expandVertically(tween(600)),
    exit  = scaleOut(tween(600), transformOrigin = TransformOrigin(
        if (isRightAligned) 1f else 0f,
        if (isBefore) 1f else 0f
    )) + fadeOut(tween(600)) + slideOutVertically(tween(600)) + shrinkVertically(tween(600))
) {
    // AccompanimentKaraokeLine rendered here
}
The 600 ms pre-entry window ensures the line has finished animating in by the time its first syllable is due to sing.

Accompaniment Text Style

Main vocal lines and accompaniment lines use separate text styles, making it easy to distinguish the two voices at a glance:
KaraokeLyricsView(
    listState = listState,
    lyrics = lyrics,
    currentPosition = currentPositionProvider,
    onLineClicked = { onSeekTo(it.start) },
    onLinePressed = { /* ... */ },

    // Primary voice — large and prominent
    normalLineTextStyle = TextStyle(
        fontSize = 34.sp,
        fontWeight = FontWeight.Bold,
        textMotion = TextMotion.Animated
    ),

    // Background voice — smaller, visually secondary
    accompanimentLineTextStyle = TextStyle(
        fontSize = 20.sp,
        fontWeight = FontWeight.Bold,
        textMotion = TextMotion.Animated
    )
)
Inside KaraokeLineText, the style selector checks line is KaraokeLine.AccompanimentKaraokeLine and applies accompanimentLineTextStyle for accompaniment lines and normalLineTextStyle for everything else:
val textStyle = remember(line is KaraokeLine.AccompanimentKaraokeLine) {
    val baseStyle =
        if (line is KaraokeLine.AccompanimentKaraokeLine) accompanimentLineTextStyle
        else normalLineTextStyle
    baseStyle.copy(textDirection = TextDirection.Content)
}
The accompaniment line is also rendered at a reduced active alpha (0.6f) and inactive alpha (0.2f) compared to the main line, making the main voice the clear focal point even when both lines are visible simultaneously.
The accompaniment-to-main anchor mapping is computed automatically by KaraokeLyricsView based on timestamp proximity. For each AccompanimentKaraokeLine in the flat lyrics.lines list, the library finds the nearest main line by locating the closest non-accompaniment line by index (the one immediately before and the one immediately after), then choosing whichever has the smaller absolute difference between its start time and the accompaniment line’s start time. You do not need to supply explicit anchor indices in your data model.

Build docs developers (and LLMs) love