### Basic KaraokeLyricsView Setup Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/guides/karaoke-lyrics Minimal setup for KaraokeLyricsView, requiring a scroll state, lyrics data, current position lambda, and click/press callbacks. ```kotlin @Composable fun LyricsPanel( lyrics: SyncedLyrics, currentPosition: () -> Int, onSeek: (Int) -> Unit, onShare: (ISyncedLine) -> Unit ) { val listState = rememberLazyListState() KaraokeLyricsView( listState = listState, lyrics = lyrics, currentPosition = currentPosition, onLineClicked = { line -> onSeek(line.start) }, onLinePressed = { line -> onShare(line) } ) } ``` -------------------------------- ### Basic KaraokeLyricsView Setup Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/quickstart Shows the basic parameters for configuring KaraokeLyricsView, including options for displaying translations and phonetic text. ```kotlin showTranslation = true, // Show translation layer if present showPhonetic = true, // Show phonetic layer (e.g. pinyin) modifier = Modifier.fillMaxSize() ``` -------------------------------- ### Seek to Line on Click Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/guides/karaoke-lyrics Handle line clicks by seeking the media player to the start time of the clicked lyric line. The start time is provided in milliseconds. ```kotlin onLineClicked = { line -> player.seekTo(line.start.toLong()) } ``` -------------------------------- ### Modify Color Channels Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/api/color-brush-utils Examples demonstrating how to use Color.copyHsl to adjust lightness, saturation, hue, and alpha of a brand color. ```kotlin // Make a color 20 % lighter (lightness 0.0–1.0, so +0.2 moves toward white) val lighter = brandColor.copyHsl(lightness = 0.7f) // Desaturate to a neutral gray (preserves lightness) val gray = brandColor.copyHsl(saturation = 0f) // Shift hue to 210 degrees (a cool blue tint) val shifted = brandColor.copyHsl(hue = 210f) // Reduce opacity while keeping color val faded = brandColor.copyHsl(alpha = 0.4f) ``` -------------------------------- ### Import KaraokeLyricsView for Verification Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/installation Verify the installation by importing the main composable, KaraokeLyricsView, into a Kotlin file. A successful import indicates correct dependency configuration. ```kotlin import com.mocharealm.accompanist.lyrics.ui.composable.lyrics.KaraokeLyricsView ``` -------------------------------- ### Custom Easing Example with NewtonPolynomialInterpolationEasing Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/api/easing-functions Demonstrates how to create and use a custom easing function with Compose's Animatable or animateFloatAsState. The easing is applied via the animationSpec. ```kotlin val myEasing = NewtonPolynomialInterpolationEasing( 0.0 to 0.0, 0.3 to 0.8, 1.0 to 1.0 ) // Use with Compose Animatable or animateFloatAsState: val progress by animateFloatAsState( targetValue = 1f, animationSpec = tween(durationMillis = 600, easing = myEasing) ) ``` -------------------------------- ### Apple Music TTML Example Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/concepts/lyric-formats This TTML excerpt demonstrates word-timed lyrics and background vocals using the 'x-bg' role. Ensure the 'itunes:timing="Word"' attribute is present for syllable-level timing. ```xml

Dun-dun , dun-dun-dun

Dun-dun , dun-dun-dun

It was just two lovers Mm , ooh

``` -------------------------------- ### LYS Format Example Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/concepts/lyric-formats This is an example of the LYS format, which is native to the Accompanist ecosystem. It encodes syllable-level timing compactly on a single line per lyric phrase, with each word followed by its (start_ms, duration_ms) pair. Voice identifiers appear at the start of each line. ```lys [4]I (0,214)promise (214,345)that (559,185)you'll (744,154)never (898,334)find (1232,202)another (1434,470)like (1904,363)me(2267,658) [4]I (3476,185)know (3661,150)that (3811,161)I'm (3972,184)a (4156,155)handful, (4311,672)baby, (4983,672)uh(5655,401) [4]I (6113,213)know (6326,237)I (6563,165)never (6728,293)think (7021,339)before (7360,649)I (8009,113)jump(8122,563) [7]And (11407,134)there's (11541,178)a (11719,125)lot (11844,100)of (11944,245)cool (12189,309)chicks (12498,399)out (12897,220)there(13117,758) ``` -------------------------------- ### Custom Text Styles for Main and Accompaniment Lines Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/guides/multivoice-duet This example demonstrates how to configure distinct text styles for the primary vocal line and the accompaniment line within KaraokeLyricsView. It sets different font sizes and font weights to visually differentiate the two. ```kotlin 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 ) ) ``` -------------------------------- ### Custom DipAndRise Easing Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/api/easing-functions Example of creating a custom DipAndRise easing instance with modified parameters for a gentler animation. ```kotlin val gentleRise = DipAndRise(dip = 0.1, rise = 1.0) ``` -------------------------------- ### LRC Lyric Format Example Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/concepts/lyric-formats This LRC example shows line-based timestamps. This format is supported for convenience but does not enable syllable-level animations. ```lrc [00:04.11]It was just two lovers [00:06.09]Sittin' in the car, listening to Blonde, [00:07.98]fallin' for each other [00:09.84]Pink and orange skies, [00:10.95]feelin' super childish, ``` -------------------------------- ### Configuring KaraokeLyricsView with Translations Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/guides/translations-phonetics This example demonstrates how to enable or disable the display of line-level translations globally for the KaraokeLyricsView by setting the `showTranslation` flag. ```kotlin KaraokeLyricsView( listState = listState, lyrics = lyrics, currentPosition = currentPositionProvider, onLineClicked = { onSeekTo(it.start) }, onLinePressed = { /* ... */ }, showTranslation = true // set to false to hide all translations ) ``` -------------------------------- ### Standard Interactive Lyric Line Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/api/lyrics-line-item Example of an interactive lyric line that can be focused and clicked for seeking. It uses KaraokeLineText to display the lyric content. ```kotlin @Composable fun MyLyricLine( line: KaraokeLine.MainKaraokeLine, isFocused: Boolean, blurRadius: () -> Float, onSeek: (Int) -> Unit ) { LyricsLineItem( isFocused = isFocused, isRightAligned = false, onLineClicked = { onSeek(line.start) }, onLinePressed = { /* show context menu */ }, blurRadius = blurRadius, blendMode = BlendMode.Plus ) { KaraokeLineText( line = line, currentTimeProvider = { /* player position */ 0 }, activeColor = Color.White, blendMode = BlendMode.Plus ) } } ``` -------------------------------- ### Providing Current Position for Karaoke Animation Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/guides/karaoke-lyrics Example demonstrating how to provide a stable lambda for the current playback position to ensure smooth, frame-locked animation without recomposition. Updates the position state on each vsync. ```kotlin @Composable fun PlayerScreen(playerViewModel: PlayerViewModel) { val listState = rememberLazyListState() // 1. A mutable long that can be updated off the main thread's composition cycle val animatedPositionState = remember { mutableLongStateOf(0L) } // 2. A stable lambda captured once — reading the state happens only when the // lambda is *invoked* inside the Canvas, not at composition time val currentPositionProvider = remember { { animatedPositionState.longValue.toInt() } } val uiState = playerViewModel.uiState.collectAsState() val isPlaying by remember { derivedStateOf { uiState.value.playbackState.isPlaying } } // 3. A frame loop that updates the position state every vsync LaunchedEffect(isPlaying) { if (isPlaying) { while (true) { val playbackState = uiState.value.playbackState val elapsed = System.currentTimeMillis() - playbackState.lastUpdateTime val newPosition = (playbackState.position + elapsed) .coerceAtMost(playbackState.duration) // Only write when necessary to avoid thrashing if (animatedPositionState.longValue <= newPosition || kotlin.math.abs(newPosition - animatedPositionState.longValue) >= 100 ) { animatedPositionState.longValue = newPosition } awaitFrame() // suspend until the next vsync frame } } else { animatedPositionState.longValue = uiState.value.playbackState.position } } KaraokeLyricsView( listState = listState, lyrics = lyrics, currentPosition = currentPositionProvider, onLineClicked = { line -> playerViewModel.seekTo(line.start) }, onLinePressed = { line -> /* show share sheet */ } ) } ``` -------------------------------- ### Lyrics Measurement and Rendering Pass Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/api/wrapped-line A typical usage example demonstrating the sequence of operations for measuring syllables, wrapping them into lines, assigning static layouts, and computing RowRenderData within a LaunchedEffect. This shows the integration of WrappedLine and RowRenderData. ```kotlin // 1. Measure syllables val syllableLayouts = measureSyllablesAndDetermineAnimation( syllables = line.syllables, textMeasurer = textMeasurer, style = lyricsStyle, phoneticStyle = phoneticStyle, isAccompanimentLine = line.isAccompaniment, spaceWidth = spaceWidth ) // 2. Wrap into rows → List val wrappedLines = calculateBalancedLines( syllableLayouts = syllableLayouts, availableWidthPx = canvasWidthPx, textMeasurer = textMeasurer, style = lyricsStyle ) // 3. Assign positions → List> val lineLayouts = calculateStaticLineLayout( wrappedLines = wrappedLines, isLineRightAligned = isRightAligned, canvasWidth = canvasWidthPx, lineHeight = lineHeightPx, phoneticHeight = phoneticHeightPx, isRtl = isRtl ) // 4. Compute render data → List val rowRenderData = calculateRowRenderData( lineLayouts = lineLayouts, showPhonetic = showPhonetic, density = density.density ) // rowRenderData is now ready for the drawing layer ``` -------------------------------- ### RTL Auto-Detection Example Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/api/synced-line-text Demonstrates how to automatically detect if a line of text is right-to-left using the isRtl() utility function. This is typically done within KaraokeLyricsView or a custom layout. ```kotlin // Inside KaraokeLyricsView val isLineRtl = remember(line.content) { line.content.isRtl() } ``` -------------------------------- ### Run Desktop JVM Target for Lyrics Preview Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/sample/overview Execute the sample's desktop entry point to preview lyrics rendering on a JVM target without needing an Android device. This is useful for quick visual checks of the KaraokeLyricsView. ```bash ./gradlew :sample:run ``` -------------------------------- ### Clone and Build Accompanist from Source Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/sample/overview Clone the Accompanist repository and build the sample application from the command line. This is useful for development or if you prefer building from source. ```bash git clone https://github.com/6xingyv/Accompanist.git cd Accompanist # Open in Android Studio, or run from CLI: ./gradlew :sample:installDebug ``` -------------------------------- ### KaraokeAlignment Enum Definition Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/concepts/lyrics-model Defines the horizontal anchor points for lyric lines. Use Start for leading edge alignment, End for trailing edge alignment (e.g., second voice in duets), and Unspecified which defaults to Start. ```kotlin enum class KaraokeAlignment { Start, End, Unspecified } ``` -------------------------------- ### Spring Animation Specification Logic Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/api/spring-placement-modifier Illustrates the internal logic for selecting the animation specification, defaulting to snap() for the first frame or manual scrolling, and spring() otherwise. ```kotlin val spec = if (isFirstFrame || isManualScrolling) snap() else spring(dampingRatio = 0.95f, stiffness = stiffness) ``` -------------------------------- ### Character Staggering Calculation Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/concepts/animations Calculates the animation start time for individual characters within a word to distribute animations evenly. ```kotlin val charRatio = if (n > 1) i.toFloat() / (n - 1) else 0.5f val awesomeStartTime = earliestStart + (latestStart - earliestStart) * charRatio ``` -------------------------------- ### ISyncedLine Interface Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/concepts/lyrics-model The common interface for all line types, providing the time window (start and end in milliseconds) during which a line is active. ```kotlin interface ISyncedLine { val start: Int // milliseconds val end: Int // milliseconds } ``` -------------------------------- ### Non-Interactive Accompaniment Line Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/api/lyrics-line-item Example of a non-interactive sub-line, such as a decorative accompaniment element. It demonstrates setting various alpha values and disabling interactivity. ```kotlin // Example of a non-interactive sub-line (e.g. a decorative accompaniment element) LyricsLineItem( isFocused = true, isRightAligned = isRightAligned, onLineClicked = { }, onLinePressed = { }, blurRadius = { 0f }, blendMode = blendMode, activeAlpha = 0.6f, inactiveAlpha = 0.2f, isInteractive = false ) { KaraokeLineText( line = accompanimentLine, currentTimeProvider = currentTimeProvider, // ... ) } ``` -------------------------------- ### Implement Version Catalog Dependencies in build.gradle.kts Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/installation Reference the declared accompanist libraries from your version catalog in your module's build.gradle.kts file. ```kotlin dependencies { implementation(libs.accompanist.lyrics.ui) implementation(libs.accompanist.lyrics.core) } ``` -------------------------------- ### Spring Animation Configuration Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/concepts/animations Configures the spring physics for list item placement with a fixed damping ratio and dynamic stiffness. ```kotlin spring(dampingRatio = 0.95f, stiffness = stiffness) ``` -------------------------------- ### Minimal Preview Composable for Verification Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/installation Create a minimal preview composable to confirm that the KaraokeLyricsView can be rendered at design time, ensuring the dependency is fully resolved. ```kotlin import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview import com.mocharealm.accompanist.lyrics.ui.composable.lyrics.KaraokeLyricsView @Preview @Composable fun LyricsPreview() { // KaraokeLyricsView requires a SyncedLyrics object — // a successful import here confirms the dependency is resolved. } ``` -------------------------------- ### PlayerLyrics Composable with Glow Effect Setup Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/sample/player-screen Configures the PlayerLyrics composable to render lyrics with a glow effect using additive blending and offscreen compositing. ```kotlin @Composable fun PlayerLyrics( listState: LazyListState, lyrics: SyncedLyrics?, currentPosition: () -> Int, showTranslation: Boolean, showPhonetic: Boolean, onSeekTo: (Int) -> Unit, onShare: (KaraokeLine) -> Unit, modifier: Modifier = Modifier ) { if (lyrics == null) return val currentTextStyle = LocalTextStyle.current val sf = SFPro() val normalStyle = remember(currentTextStyle) { currentTextStyle.copy( fontSize = 34.sp, fontFamily = sf, fontWeight = FontWeight.Bold, textMotion = TextMotion.Animated, ) } val accompanimentStyle = remember(currentTextStyle) { currentTextStyle.copy( fontSize = 20.sp, fontFamily = sf, fontWeight = FontWeight.Bold, textMotion = TextMotion.Animated, ) } KaraokeLyricsView( listState = listState, lyrics = lyrics, currentPosition = currentPosition, onLineClicked = { line -> onSeekTo(line.start) }, onLinePressed = { line -> onShare(line as KaraokeLine) }, showTranslation = showTranslation, showPhonetic = showPhonetic, normalLineTextStyle = normalStyle, accompanimentLineTextStyle = accompanimentStyle, modifier = modifier.graphicsLayer { blendMode = BlendMode.Plus // additive blending = glow effect compositingStrategy = CompositingStrategy.Offscreen // required for correct blend }, useBlurEffect = false ) } ``` -------------------------------- ### Declare Libraries in gradle/libs.versions.toml Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/installation Define the accompanist-lyrics-ui and accompanist-lyrics-core libraries in your Gradle version catalog for centralized version management. ```toml [libraries] accompanist-lyrics-ui = { module = "com.mocharealm.accompanist:lyrics-ui", version = "1.0.18" } accompanist-lyrics-core = { module = "com.mocharealm.accompanist:lyrics-core", version = "0.4.4" } ``` -------------------------------- ### Apple Music Glow Effect with KaraokeLyricsView Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/quickstart Demonstrates how to achieve the Apple Music glow effect by using additive blending with `graphicsLayer` and `CompositingStrategy.Offscreen`. ```kotlin Box( modifier = Modifier .fillMaxSize() .graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen } ) { KaraokeLyricsView( // ... blendMode = BlendMode.Plus, ) } ``` -------------------------------- ### Basic KaraokeLyricsView Usage Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/api/karaoke-lyrics-view Demonstrates how to use the KaraokeLyricsView composable within a Jetpack Compose screen. It requires a list of SyncedLyrics and a MediaPlayer to track the current playback position and handle seeking. Customization options for text styles, colors, effects, and layout are shown. ```kotlin import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextMotion import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp // Assuming SyncedLyrics, MediaPlayer, showShareSheet, and KaraokeLyricsView are defined elsewhere @Composable fun LyricsScreen( lyrics: SyncedLyrics, player: MediaPlayer ) { val listState = rememberLazyListState() KaraokeLyricsView( listState = listState, lyrics = lyrics, currentPosition = { player.currentPositionMs }, onLineClicked = { line -> player.seekTo(line.start) }, onLinePressed = { line -> showShareSheet(line) }, modifier = Modifier.fillMaxSize(), normalLineTextStyle = TextStyle( fontSize = 34.sp, fontWeight = FontWeight.Bold, textMotion = TextMotion.Animated ), accompanimentLineTextStyle = TextStyle( fontSize = 20.sp, fontWeight = FontWeight.Bold, textMotion = TextMotion.Animated ), textColor = Color.White, blendMode = BlendMode.Plus, useBlurEffect = true, showTranslation = true, showPhonetic = true, offset = 32.dp, keepAliveZone = 100.dp, blurDelta = 3f, breathingDotsDefaults = KaraokeBreathingDotsDefaults( number = 3, size = 16.dp, breathingDotsColor = Color.White ) ) } ``` -------------------------------- ### Standalone KaraokeLineText Demo Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/api/karaoke-line-text Demonstrates how to use KaraokeLineText within a composable. It requires a KaraokeLine, a MediaPlayer for time tracking, and a TextMeasurer. Customizes text styles, colors, and display of translations and phonetics. ```kotlin import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextMotion import androidx.compose.ui.unit.sp import android.media.MediaPlayer import androidx.compose.foundation.text.rememberTextMeasurer import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.TextStyle import com.example.accompanist.lyrics.ui.karaoke.KaraokeLine import com.example.accompanist.lyrics.ui.karaoke.KaraokeLineText import com.example.accompanist.lyrics.ui.karaoke.LyricsLineItem @Composable fun StandaloneKaraokeLineDemo( line: KaraokeLine.MainKaraokeLine, player: MediaPlayer ) { val textMeasurer = rememberTextMeasurer() LyricsLineItem( isFocused = true, isRightAligned = false, onLineClicked = { player.seekTo(line.start) }, onLinePressed = { }, blurRadius = { 0f }, blendMode = BlendMode.Plus ) { KaraokeLineText( line = line, currentTimeProvider = { player.currentPositionMs }, normalLineTextStyle = TextStyle( fontSize = 34.sp, fontWeight = FontWeight.Bold, textMotion = TextMotion.Animated ), accompanimentLineTextStyle = TextStyle( fontSize = 20.sp, fontWeight = FontWeight.Bold, textMotion = TextMotion.Animated ), phoneticTextStyle = TextStyle( fontSize = 13.sp, fontWeight = FontWeight.Normal ), activeColor = Color.White, blendMode = BlendMode.Plus, showTranslation = true, showPhonetic = true, textMeasurer = textMeasurer ) } } ``` -------------------------------- ### Usage Example for Spring Placement Modifier Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/api/spring-placement-modifier Demonstrates how to use the springPlacement modifier within a LazyColumn inside a LookaheadScope. Ensure the composable is a child of LookaheadScope to avoid runtime crashes. ```kotlin LookaheadScope { LazyColumn { itemsIndexed(lyricLines, key = { _, line -> line.id }) { index, line -> val distanceFromActive = abs(index - activeLine) val stiffness = (120f - distanceFromActive * 10f).coerceAtLeast(20f) LyricsLineItem( line = line, modifier = Modifier.springPlacement( lookaheadScope = this@LookaheadScope, itemKey = line.id, isManualScrolling = isManualScrolling, stiffness = stiffness ) ) } } } ``` -------------------------------- ### AccompanimentKaraokeLine Data Model Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/concepts/lyrics-model Represents a background or harmony vocal line associated with a MainKaraokeLine. It has a smaller text size and reduced alpha, and is rendered above or below the main line based on its start time. ```kotlin data class AccompanimentKaraokeLine( override val start: Int, override val end: Int, val syllables: List, val alignment: KaraokeAlignment, val translation: String?, val phonetic: String? ) : KaraokeLine() ``` -------------------------------- ### Share Flow Initiation in MobilePlayerScreen Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/sample/player-screen Details the sequence of actions performed when the onShare lambda is invoked. It pauses playback, prepares sharing context, and seeds the ShareViewModel. ```kotlin onShare = { line -> lyrics?.let { playerViewModel.onShareRequested() // pauses playback, shows the modal val context = ShareContext( lyrics = lyrics, initialLine = line, backgroundState = backgroundState, title = currentMusicItem?.label ?: "Unknown Title", artist = currentMusicItem?.testTarget?.split(" [")?.get(0) ?: "Unknown", cover = cover // android.graphics.Bitmap for the share card ) shareViewModel.prepareForSharing(context) // seeds ShareViewModel state playerViewModel.onShareRequested() } } ``` -------------------------------- ### Apply Customized Breathing Dots Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/guides/theming-styling Applies a customized `KaraokeBreathingDotsDefaults` instance to `KaraokeLyricsView`, overriding default values for dot appearance and animation timing. This example uses a soft purple tint for the dots. ```kotlin KaraokeLyricsView( listState = listState, lyrics = lyrics, currentPosition = currentPositionProvider, onLineClicked = { onSeekTo(it.start) }, onLinePressed = { /* ... */ }, breathingDotsDefaults = KaraokeBreathingDotsDefaults( number = 3, size = 20.dp, margin = 10.dp, enterDurationMs = 2000, breathingDotsColor = Color(0xFFE8C4FF) // a soft purple tint ) ) ``` -------------------------------- ### Spring Placement Implementation Classes Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/api/spring-placement-modifier Defines the data class for carrying configuration and the actual node that runs the animation for spring placement. ```kotlin data class SpringPlacementNodeElement( val lookaheadScope: LookaheadScope, val itemKey: Any, val isManualScrolling: Boolean, val stiffness: Float ) : ModifierNodeElement() // The actual node that runs the animation class SpringPlacementModifierNode( var lookaheadScope: LookaheadScope, var itemKey: Any, var isManualScrolling: Boolean, var stiffness: Float ) : ApproachLayoutModifierNode, Modifier.Node() ``` -------------------------------- ### Add Dependencies to build.gradle.kts (KMP Module) Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/installation For Kotlin Multiplatform modules, add the lyrics-ui and lyrics-core dependencies within the commonMain source set. ```kotlin kotlin { sourceSets { commonMain { dependencies { implementation("com.mocharealm.accompanist:lyrics-ui:1.0.18") implementation("com.mocharealm.accompanist:lyrics-core:0.4.4") } } } } ``` -------------------------------- ### KaraokeBreathingDots Composable Function Signature Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/api/karaoke-breathing-dots The main composable function for rendering animated breathing dots. It requires alignment, start and end times, and a current time provider. Customize its appearance and timing using the defaults parameter. ```kotlin @Composable fun KaraokeBreathingDots( alignment: KaraokeAlignment, startTimeMs: Int, endTimeMs: Int, currentTimeProvider: () -> Int, modifier: Modifier = Modifier, defaults: KaraokeBreathingDotsDefaults = KaraokeBreathingDotsDefaults() ) ``` -------------------------------- ### Add Dependencies to build.gradle.kts Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/quickstart Add the lyrics-ui and lyrics-core Gradle dependencies to your module's build.gradle.kts file. The lyrics-ui artifact provides the composable renderer, while lyrics-core offers the parser and the SyncedLyrics data model. ```kotlin // build.gradle.kts (module) dependencies { implementation("com.mocharealm.accompanist:lyrics-ui:1.0.18") implementation("com.mocharealm.accompanist:lyrics-core:0.4.4") } ``` -------------------------------- ### Add Dependencies to build.gradle.kts (Module) Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/installation Include the lyrics-ui and lyrics-core artifacts in your module's build.gradle.kts file for standard Android projects. ```kotlin dependencies { implementation("com.mocharealm.accompanist:lyrics-ui:1.0.18") implementation("com.mocharealm.accompanist:lyrics-core:0.4.4") } ``` -------------------------------- ### Deriving Background State in PlayerScreen Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/sample/player-screen Shows how to derive the background visual state efficiently in PlayerScreen using derivedStateOf to avoid recomposing the entire screen when the background state changes. ```kotlin val backgroundState by remember { derivedStateOf { uiStateState.value.backgroundState } } FlowingLightBackground( state = backgroundState, modifier = Modifier.fillMaxSize() ) ``` -------------------------------- ### Animated Visibility for Accompaniment Lines Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/guides/multivoice-duet This snippet shows how to use AnimatedVisibility to control the appearance and disappearance of accompaniment lines. It defines a visibility state based on playback time and applies a combination of enter and exit transitions. ```kotlin 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 } ``` -------------------------------- ### Run the Frame Loop with LaunchedEffect Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/sample/player-screen Launches a coroutine that updates the position at the display refresh rate by suspending until the next VSYNC signal. ```kotlin LaunchedEffect(isPlaying) { while (true) { awaitFrame() // Update position logic here } } ``` -------------------------------- ### calculateBalancedLines Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/api/lyrics-layout-calculator Wraps a flat list of SyllableLayout objects into display rows using a dynamic-programming line-breaking algorithm that minimizes the sum of squared end-of-line slack. ```APIDOC ## calculateBalancedLines ### Description Wraps a flat list of `SyllableLayout` objects into display rows using a dynamic-programming line-breaking algorithm that minimises the sum of squared end-of-line slack (typographic *badness*). ### Method Signature ```kotlin fun calculateBalancedLines( syllableLayouts: List, availableWidthPx: Float, textMeasurer: TextMeasurer, style: TextStyle ): List ``` ### Parameters #### Path Parameters - **syllableLayouts** (List) - Required - The output of `measureSyllablesAndDetermineAnimation()`. Syllables must still be in lyric order. - **availableWidthPx** (Float) - Required - Maximum row width in pixels. Syllables are placed until this limit would be exceeded, then a new row starts. - **textMeasurer** (TextMeasurer) - Required - Used only by the internal `trimDisplayLineTrailingSpaces()` helper to re-measure trailing syllables after trailing whitespace is stripped. - **style** (TextStyle) - Required - Passed through to `trimDisplayLineTrailingSpaces()`. ### Returns `List` — each item holds the syllables for one display row and the row's total rendered width. Syllable positions are **not** set yet. ### Algorithm The algorithm fills a `costs` array of size `n + 1` where `costs[i]` is the minimum total badness achievable when breaking the first `i` syllables optimally. Badness for a line ending at position `i` starting at position `j` is: ``` badness = (availableWidthPx − lineWidth)² ``` Word boundaries are respected — the inner loop skips break points that would split a word across lines. If the DP finds no finite-cost solution (e.g., a single word is wider than `availableWidthPx`), it falls back to `calculateGreedyWrappedLines()`, which breaks at word boundaries and syllable boundaries when no alternative exists. For most lines the DP produces noticeably more even line lengths than greedy wrapping, which matters most for two- and three-line blocks where unbalanced rows look awkward. ``` -------------------------------- ### PlayerScreen Composable with Frame-Loop Position Provider Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/sample/player-screen Demonstrates how to implement a custom position provider for `KaraokeLyricsView` to avoid recompositions. It uses `mutableLongStateOf` and `awaitFrame` to update the position only within the Canvas, ensuring a smooth animation. ```kotlin PlayerScreen.kt @Composable fun PlayerScreen( playerViewModel: PlayerViewModel = koinViewModel(), shareViewModel: ShareViewModel = koinViewModel(), ) { val listState = rememberLazyListState() val animatedPositionState = remember { mutableLongStateOf(0L) } // A stable lambda captured once — only reads the State when KaraokeLyricsView calls it val currentPositionProvider = remember { { animatedPositionState.longValue.toInt() } } val uiStateState = playerViewModel.uiState.collectAsState() val isPlaying by remember { derivedStateOf { uiStateState.value.playbackState.isPlaying } } LaunchedEffect(isPlaying) { if (isPlaying) { while (true) { val playbackState = uiStateState.value.playbackState val elapsed = System.currentTimeMillis() - playbackState.lastUpdateTime val newPosition = (playbackState.position + elapsed).coerceAtMost( playbackState.duration ) val currentAnimPos = animatedPositionState.longValue if (currentAnimPos <= newPosition || abs(newPosition - currentAnimPos) >= 100) { // Writing to MutableLongState does NOT recompose PlayerScreen — // it only invalidates the DrawScope inside KaraokeLyricsView's Canvas animatedPositionState.longValue = newPosition } awaitFrame() // suspend until the next Choreographer frame } } else { animatedPositionState.longValue = uiStateState.value.playbackState.position } } // ... ``` -------------------------------- ### Customizing KaraokeLyricsView with Phonetic Style Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/guides/translations-phonetics Demonstrates how to customize `normalLineTextStyle` and `phoneticTextStyle` when initializing `KaraokeLyricsView`. ```kotlin KaraokeLyricsView( listState = listState, lyrics = lyrics, currentPosition = currentPositionProvider, onLineClicked = { onSeekTo(it.start) }, onLinePressed = { /* ... */ }, normalLineTextStyle = TextStyle( fontSize = 34.sp, fontWeight = FontWeight.Bold, fontFamily = myFontFamily, textMotion = TextMotion.Animated ), phoneticTextStyle = TextStyle( fontSize = 11.sp, fontWeight = FontWeight.Normal, fontFamily = myFontFamily, letterSpacing = 0.05.em ) ) ``` -------------------------------- ### Tablet/Desktop Layout Rendering Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/sample/player-screen Renders the PadPlayerScreen composable for larger screen sizes like tablets and desktops (WindowLayoutType.Pad or other non-phone types). ```kotlin else -> { PadPlayerScreen( listState = listState, animatedPosition = currentPositionProvider, playerViewModel = playerViewModel, shareViewModel = shareViewModel, backgroundState = backgroundState, currentMusicItem = currentMusicItem, showTranslation = showTranslation, showPhonetic = showPhonetic, lyrics = lyrics ) } ``` -------------------------------- ### Calculating Phonetic Height for Layout Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/guides/translations-phonetics Illustrates how to calculate the `phoneticHeight` based on the `phoneticTextStyle` and use it in `calculateStaticLineLayout`. ```kotlin val phoneticHeight = remember(phoneticTextStyle) { textMeasurer.measure("M", phoneticTextStyle).size.height.toFloat() } val finalLineLayouts = remember( wrappedLines, availableWidthPx, lineHeight, isLineRtl, isRightAligned, showPhonetic ) { calculateStaticLineLayout( wrappedLines = wrappedLines, isLineRightAligned = isRightAligned, canvasWidth = availableWidthPx, lineHeight = lineHeight, phoneticHeight = if (showPhonetic) phoneticHeight else 0f, // only add space when enabled isRtl = isLineRtl ) } ``` -------------------------------- ### Render KaraokeLyricsView with Player Integration Source: https://mintlify.wiki/6xingyv/accompanist-lyrics-ui/quickstart Integrate KaraokeLyricsView into your composable hierarchy, providing the LazyListState, SyncedLyrics object, and lambdas for current playback position and line click events. The currentPosition lambda is read per frame without triggering recomposition, and onLineClicked allows for seek-to-line functionality. ```kotlin import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.mocharealm.accompanist.lyrics.ui.composable.lyrics.KaraokeLyricsView @Composable fun LyricsScreen(player: Player, syncedLyrics: SyncedLyrics) { val listState = rememberLazyListState() KaraokeLyricsView( listState = listState, lyrics = syncedLyrics, currentPosition = { player.currentPosition.toInt() }, onLineClicked = { line -> player.seekTo(line.start.toLong()) }, onLinePressed = { line -> /* show share sheet or context menu */ }, modifier = Modifier.fillMaxSize() ) } ```