WIP
diff --git a/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimation.kt b/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimation.kt
index 7c30b6c..21139ed 100644
--- a/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimation.kt
+++ b/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimation.kt
@@ -159,13 +159,10 @@
 fun LottieAnimation(
     compositionSpec: LottieCompositionSpec,
     modifier: Modifier = Modifier,
-    isPlaying: Boolean = true,
-    restartOnPlay: Boolean = true,
+    initialIsPlaying: Boolean = true,
     repeatCount: Int = 1,
     clipSpec: LottieClipSpec? = null,
     speed: Float = 1f,
-    onRepeat: ((repeatCount: Int) -> Unit)? = null,
-    onFinished: (() -> Unit)? = null,
     imageAssetsFolder: String? = null,
     imageAssetDelegate: ImageAssetDelegate? = null,
     outlineMasksAndMattes: Boolean = false,
@@ -176,13 +173,10 @@
     LottieAnimation(
         composition,
         modifier,
-        isPlaying,
-        restartOnPlay,
+        initialIsPlaying,
         clipSpec,
         speed,
         repeatCount,
-        onRepeat,
-        onFinished,
         imageAssetsFolder,
         imageAssetDelegate,
         outlineMasksAndMattes,
@@ -202,13 +196,10 @@
 fun LottieAnimation(
     composition: LottieComposition?,
     modifier: Modifier = Modifier,
-    isPlaying: Boolean = true,
-    restartOnPlay: Boolean = true,
+    initialIsPlaying: Boolean = true,
     clipSpec: LottieClipSpec? = null,
     speed: Float = 1f,
     repeatCount: Int = 1,
-    onRepeat: ((repeatCount: Int) -> Unit)? = null,
-    onFinished: (() -> Unit)? = null,
     imageAssetsFolder: String? = null,
     imageAssetDelegate: ImageAssetDelegate? = null,
     outlineMasksAndMattes: Boolean = false,
@@ -217,13 +208,10 @@
 ) {
     val progress by animateLottieComposition(
         composition,
-        isPlaying,
-        restartOnPlay,
+        initialIsPlaying,
         clipSpec,
         speed,
         repeatCount,
-        onRepeat,
-        onFinished,
     )
     LottieAnimation(
         composition,
diff --git a/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieConstants.kt b/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieConstants.kt
new file mode 100644
index 0000000..12f308e
--- /dev/null
+++ b/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieConstants.kt
@@ -0,0 +1,8 @@
+package com.airbnb.lottie.compose
+
+object LottieConstants {
+    /**
+     * Use with [animateLottieComposition]#repeatCount to repeat forever.
+     */
+    const val RepeatForever = Integer.MAX_VALUE
+}
\ No newline at end of file
diff --git a/lottie-compose/src/main/java/com/airbnb/lottie/compose/animateLottieComposition.kt b/lottie-compose/src/main/java/com/airbnb/lottie/compose/animateLottieComposition.kt
index f3ac523..b70e9f1 100644
--- a/lottie-compose/src/main/java/com/airbnb/lottie/compose/animateLottieComposition.kt
+++ b/lottie-compose/src/main/java/com/airbnb/lottie/compose/animateLottieComposition.kt
@@ -3,8 +3,55 @@
 import androidx.compose.animation.core.*
 import androidx.compose.runtime.*
 import com.airbnb.lottie.LottieComposition
+import kotlinx.coroutines.channels.BufferOverflow
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.first
 import java.util.concurrent.TimeUnit
 
+class LottieAnimationState internal constructor(initialIsPlaying: Boolean) : State<Float> {
+    var isPlaying: Boolean by mutableStateOf(initialIsPlaying)
+        internal set
+
+    override var value: Float by mutableStateOf(0f)
+        internal set
+
+    var currentRepeatCount: Int by mutableStateOf(1)
+        internal set
+
+    internal val actionChannel = Channel<LottieAnimationAction>()
+
+    internal val onFinished = MutableSharedFlow<Long>(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+
+    suspend fun restart() {
+        actionChannel.send(LottieAnimationAction.Reset)
+        actionChannel.send(LottieAnimationAction.Resume)
+    }
+
+    suspend fun toggleIsPlaying() {
+        actionChannel.send(if (isPlaying) LottieAnimationAction.Pause else LottieAnimationAction.Resume)
+    }
+
+    suspend fun pause() {
+        actionChannel.send(LottieAnimationAction.Pause)
+    }
+
+    suspend fun resume() {
+        actionChannel.send(LottieAnimationAction.Resume)
+    }
+
+    suspend fun snapTo(progress: Float) {
+        actionChannel.send(LottieAnimationAction.SnapTo(progress))
+    }
+
+    /**
+     * Suspends until the animation finishes and then returns the last frame time nanos.
+     */
+    suspend fun awaitFinished(): Long {
+        return onFinished.first()
+    }
+}
+
 /**
  * Returns a mutable state representing the progress of an animation.
  *
@@ -30,9 +77,8 @@
  * @param speed The speed the animation should play at. Numbers larger than one will speed it up.
  *              Numbers between 0 and 1 will slow it down. Numbers less than 0 will play it backwards.
  * @param repeatCount The number of times the animation should repeat before stopping. It must be
- *                    a positive number. [Integer.MAX_VALUE] can be used to repeat forever.
- * @param onRepeat An optional callback to be notified every time the animation repeats. Return whether
- *                 or not the animation should continue to repeat.
+ *                    a positive number. [LottieConstants.repeatForever] can be used to repeat forever.
+ * @param onRepeat An optional callback to be notified every time the animation repeats.
  * @param onFinished An optional callback that is invoked when animation completes. Note that the isPlaying
  *                   parameter you pass in may still be true. If you want to restart the animation, increase the
  *                   repeatCount or change isPlaying to false and then true again.
@@ -40,43 +86,65 @@
 @Composable
 fun animateLottieComposition(
     composition: LottieComposition?,
-    isPlaying: Boolean = true,
-    restartOnPlay: Boolean = true,
+    initialIsPlaying: Boolean = true,
     clipSpec: LottieClipSpec? = null,
     speed: Float = 1f,
     repeatCount: Int = 1,
-    onRepeat: ((repeatCount: Int) -> Unit)? = null,
-    onFinished: (() -> Unit)? = null,
-): MutableState<Float> {
+): LottieAnimationState {
     require(repeatCount > 0) { "Repeat count must be a positive number ($repeatCount)." }
     require(speed != 0f) { "Speed must not be 0" }
     require(speed.isFinite()) { "Speed must be a finite number. It is $speed." }
-
-    val progress = remember { mutableStateOf(0f) }
-
-    var currentRepeatCount by remember { mutableStateOf(0) }
-    val currentOnRepeat by rememberUpdatedState(onRepeat)
-    val currentOnFinished by rememberUpdatedState(onFinished)
+    val state = remember { LottieAnimationState(initialIsPlaying) }
 
     LaunchedEffect(composition) {
-        progress.value = when (composition) {
+        state.value = when (composition) {
             null -> 0f
-            else -> if (speed >= 0) clipSpec?.getMinProgress(composition) ?: 0f else clipSpec?.getMaxProgress(composition) ?: 1f
+            else -> when {
+                speed >= 0 -> clipSpec?.getMinProgress(composition) ?: 0f
+                else -> clipSpec?.getMaxProgress(composition) ?: 1f
+            }
         }
-        currentRepeatCount = 0
+        state.currentRepeatCount = 0
     }
 
-    LaunchedEffect(composition, isPlaying, repeatCount, clipSpec, speed) {
-        if (!isPlaying || composition == null) return@LaunchedEffect
+    LaunchedEffect(state) {
+        for (action in state.actionChannel) {
+            when (action) {
+                LottieAnimationAction.Reset -> {
+                    state.value = when {
+                        composition == null -> 0f
+                        speed > 0 -> clipSpec?.getMinProgress(composition) ?: 0f
+                        else -> clipSpec?.getMaxProgress(composition) ?: 1f
+                    }
+                }
+                LottieAnimationAction.Pause -> {
+                    state.isPlaying = false
+                }
+                LottieAnimationAction.Resume -> {
+                    state.isPlaying = true
+                }
+                is LottieAnimationAction.SnapTo -> {
+                    state.value = when {
+                        composition == null -> 0f
+                        clipSpec == null -> action.progress
+                        else -> action.progress.coerceIn(
+                            clipSpec.getMinProgress(composition),
+                            clipSpec.getMaxProgress(composition),
+                        )
+                    }
+                }
+            }
+        }
+    }
+
+    LaunchedEffect(composition, state.isPlaying, repeatCount, clipSpec, speed) {
+        if (!state.isPlaying || composition == null) return@LaunchedEffect
         val minProgress = clipSpec?.getMinProgress(composition) ?: 0f
         val maxProgress = clipSpec?.getMaxProgress(composition) ?: 1f
-        if (speed > 0 && (progress.value == 1f || restartOnPlay)) {
-            progress.value = minProgress
-        } else if (speed < 0 && (progress.value == 0f || restartOnPlay)) {
-            progress.value = maxProgress
-        }
-        if (restartOnPlay || currentRepeatCount >= repeatCount) {
-            currentRepeatCount = 0
+        if (speed >= 0 && (state.value == 1f)) {
+            state.value = minProgress
+        } else if (speed < 0 && (state.value == 0f)) {
+            state.value = maxProgress
         }
         var lastFrameTime = withFrameNanos { it }
         var done = false
@@ -85,19 +153,17 @@
                 val dTime = (frameTime - lastFrameTime) / TimeUnit.MILLISECONDS.toNanos(1).toFloat()
                 lastFrameTime = frameTime
                 val dProgress = (dTime * speed) / composition.duration
-                val rawProgress = minProgress + ((progress.value - minProgress) + dProgress)
+                val rawProgress = minProgress + ((state.value - minProgress) + dProgress)
                 if (speed > 0 && rawProgress > maxProgress) {
-                    currentRepeatCount++
-                    currentOnRepeat?.invoke(repeatCount)
+                    state.currentRepeatCount++
                 } else if (speed < 0 && rawProgress < minProgress) {
-                    currentRepeatCount++
-                    currentOnRepeat?.invoke(repeatCount)
+                    state.currentRepeatCount++
                 }
-                done = if (currentRepeatCount < repeatCount && !rawProgress.isInfinite()) {
-                    progress.value = minProgress + ((rawProgress - minProgress) fmod (maxProgress - minProgress))
+                done = if (state.currentRepeatCount < repeatCount && !rawProgress.isInfinite()) {
+                    state.value = minProgress + ((rawProgress - minProgress) fmod (maxProgress - minProgress))
                     false
                 } else {
-                    progress.value = when {
+                    state.value = when {
                         speed >= 0 -> clipSpec?.getMaxProgress(composition) ?: 1f
                         else -> clipSpec?.getMinProgress(composition) ?: 0f
                     }
@@ -105,13 +171,20 @@
                 }
             }
         }
-        currentOnFinished?.invoke()
+        state.onFinished.emit(lastFrameTime)
     }
-    return progress
+    return state
 }
 
 /**
  * Floor mod instead of % which is remainder. This allows negative speeds to properly wrap around to
  * the max progress.
  */
-private infix fun Float.fmod(other: Float) = ((this % other) + other) % other
\ No newline at end of file
+private infix fun Float.fmod(other: Float) = ((this % other) + other) % other
+
+internal sealed class LottieAnimationAction {
+    object Reset : LottieAnimationAction()
+    object Pause : LottieAnimationAction()
+    object Resume : LottieAnimationAction()
+    class SnapTo(val progress: Float) : LottieAnimationAction()
+}
\ No newline at end of file
diff --git a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/BasicUsageExamplesPage.kt b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/BasicUsageExamplesPage.kt
index acf1eb6..629fade 100644
--- a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/BasicUsageExamplesPage.kt
+++ b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/BasicUsageExamplesPage.kt
@@ -11,6 +11,7 @@
 import androidx.compose.ui.unit.dp
 import com.airbnb.lottie.compose.*
 import com.airbnb.lottie.sample.compose.R
+import kotlinx.coroutines.launch
 
 @Composable
 fun BasicUsageExamplesPage() {
@@ -62,7 +63,7 @@
 private fun Example2() {
     LottieAnimation(
         LottieCompositionSpec.RawRes(R.raw.heart),
-        repeatCount = Integer.MAX_VALUE,
+        repeatCount = LottieConstants.RepeatForever,
     )
 }
 
@@ -73,7 +74,7 @@
 private fun Example3() {
     LottieAnimation(
         LottieCompositionSpec.RawRes(R.raw.heart),
-        repeatCount = Integer.MAX_VALUE,
+        repeatCount = LottieConstants.RepeatForever,
         clipSpec = LottieClipSpec.MinAndMaxProgress(0.5f, 0.75f),
     )
 }
@@ -118,7 +119,7 @@
     val composition by lottieComposition(LottieCompositionSpec.RawRes(R.raw.heart))
     val progress by animateLottieComposition(
         composition,
-        repeatCount = Integer.MAX_VALUE,
+        repeatCount = LottieConstants.RepeatForever,
     )
     LottieAnimation(
         composition,
@@ -131,16 +132,16 @@
  */
 @Composable
 private fun Example7() {
-    var isPlaying by remember { mutableStateOf(false) }
+    val scope = rememberCoroutineScope()
     LottieAnimation(
         LottieCompositionSpec.RawRes(R.raw.heart),
-        repeatCount = Integer.MAX_VALUE,
-        // When this is true, it it will start from 0 every time it is played again.
-        // When this is false, it will resume from the progress it was pause at.
-        restartOnPlay = false,
-        isPlaying = isPlaying,
+        repeatCount = LottieConstants.RepeatForever,
         modifier = Modifier
-            .clickable { isPlaying = !isPlaying }
+            .clickable {
+                scope.launch {
+
+                }
+            }
     )
 }
 
diff --git a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/player/PlayerPage.kt b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/player/PlayerPage.kt
index cab2071..5bf6e47 100644
--- a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/player/PlayerPage.kt
+++ b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/player/PlayerPage.kt
@@ -5,6 +5,9 @@
 import androidx.compose.animation.expandVertically
 import androidx.compose.animation.shrinkVertically
 import androidx.compose.foundation.*
+import androidx.compose.foundation.interaction.DragInteraction
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.PressInteraction
 import androidx.compose.foundation.layout.*
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.itemsIndexed
@@ -40,13 +43,14 @@
 import com.airbnb.lottie.sample.compose.utils.maybeBackground
 import com.airbnb.lottie.sample.compose.utils.maybeDrawBorder
 import com.airbnb.lottie.sample.compose.utils.toDummyBitmap
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
 import kotlin.math.ceil
 import kotlin.math.roundToInt
 
 @Stable
 class PlayerPageState {
-    var isPlaying by mutableStateOf(true)
-    var repeatCount by mutableStateOf(Integer.MAX_VALUE)
+    var repeatCount by mutableStateOf(LottieConstants.RepeatForever)
     var speed by mutableStateOf(1f)
     var outlineMasksAndMattes by mutableStateOf(false)
     var applyOpacityToLayers by mutableStateOf(false)
@@ -167,13 +171,12 @@
             ImageAssetDelegate { if (it.hasBitmap()) null else it.toDummyBitmap(dummyBitmapStrokeWidth) }
         }
     }
-    val progress = animateLottieComposition(
+    val animationState = animateLottieComposition(
         compositionResult(),
-        state.isPlaying,
-        restartOnPlay = false,
+        initialIsPlaying = true,
         repeatCount = state.repeatCount,
         speed = state.speed,
-    ) { state.isPlaying = false }
+    )
 
     Column(
         verticalArrangement = Arrangement.SpaceBetween,
@@ -188,7 +191,7 @@
         ) {
             LottieAnimation(
                 compositionResult(),
-                progress.value,
+                animationState.value,
                 imageAssetDelegate = imageAssetDelegate,
                 modifier = Modifier
                     .fillMaxSize()
@@ -213,7 +216,7 @@
             )
         }
         ExpandVisibility(!state.focusMode) {
-            PlayerControlsRow(compositionResult(), progress, state)
+            PlayerControlsRow(compositionResult(), animationState, state)
         }
         ExpandVisibility(!state.focusMode) {
             Toolbar(state)
@@ -224,15 +227,16 @@
 @Composable
 private fun PlayerControlsRow(
     composition: LottieComposition?,
-    progress: MutableState<Float>,
+    animationState: LottieAnimationState,
     state: PlayerPageState,
 ) {
+    val scope = rememberCoroutineScope()
     val totalTime = ((composition?.duration ?: 0L / state.speed) / 1000.0)
     val totalTimeFormatted = ("%.1f").format(totalTime)
 
-    val progressFormatted = ("%.1f").format(progress.value * totalTime)
+    val progressFormatted = ("%.1f").format(animationState.value * totalTime)
 
-    val frame = composition?.getFrameForProgress(progress.value)?.roundToInt() ?: 0
+    val frame = composition?.getFrameForProgress(animationState.value)?.roundToInt() ?: 0
     val durationFrames = ceil(composition?.durationFrames ?: 0f).roundToInt()
     Box(
         modifier = Modifier
@@ -245,10 +249,14 @@
                 contentAlignment = Alignment.Center
             ) {
                 IconButton(
-                    onClick = { state.isPlaying = !state.isPlaying },
+                    onClick = {
+                        scope.launch {
+                            animationState.toggleIsPlaying()
+                        }
+                    },
                 ) {
                     Icon(
-                        if (state.isPlaying) Icons.Filled.Pause
+                        if (animationState.isPlaying) Icons.Filled.Pause
                         else Icons.Filled.PlayArrow,
                         contentDescription = null
                     )
@@ -261,17 +269,16 @@
                         .padding(top = 48.dp, bottom = 8.dp)
                 )
             }
-            Slider(
-                value = progress.value,
-                onValueChange = { progress.value = it },
+            AnimationSlider(
+                animationState,
                 modifier = Modifier.weight(1f)
             )
             IconButton(onClick = {
-                state.repeatCount = if (state.repeatCount == Integer.MAX_VALUE) 1 else Integer.MAX_VALUE
+                state.repeatCount = if (state.repeatCount == LottieConstants.RepeatForever) 1 else LottieConstants.RepeatForever
             }) {
                 Icon(
                     Icons.Filled.Repeat,
-                    tint = if (state.repeatCount == Integer.MAX_VALUE) Teal else Color.Black,
+                    tint = if (state.repeatCount == LottieConstants.RepeatForever) Teal else Color.Black,
                     contentDescription = null
                 )
             }
@@ -288,6 +295,44 @@
 }
 
 @Composable
+private fun AnimationSlider(
+    animationState: LottieAnimationState,
+    modifier: Modifier = Modifier,
+) {
+    val scope = rememberCoroutineScope()
+    val interactionSource = remember { MutableInteractionSource() }
+    var isInteracting by remember { mutableStateOf(false) }
+
+    LaunchedEffect(interactionSource) {
+        interactionSource.interactions.collect { interaction ->
+            isInteracting = when (interaction) {
+                is PressInteraction.Press, is DragInteraction.Start -> true
+                else -> false
+            }
+        }
+    }
+
+    LaunchedEffect(isInteracting) {
+        if (isInteracting) {
+            animationState.pause()
+        } else {
+            animationState.resume()
+        }
+    }
+
+    Slider(
+        value = animationState.value,
+        interactionSource = interactionSource,
+        onValueChange = { progress ->
+            scope.launch {
+                animationState.snapTo(progress)
+            }
+        },
+        modifier = modifier,
+    )
+}
+
+@Composable
 private fun SpeedToolbar(
     state: PlayerPageState,
 ) {