I'm integrating ExoPlayer in Compose. I need it in...
# compose
d
I'm integrating ExoPlayer in Compose. I need it in a LazyColumn and the video is supposed to pause/resume every time it leave the screen or enter it. Can anyone help me on how to monitor the visibility of a composable on the Screen? (code in the thread)
🧵 1
f
d
With my very basic integration (just drafting it) I can hear the video stops right away when i scroll UP but it takes a while (I need to scroll away for at least 2 screens) for the video to stop if i scroll DOWN. -- it hits the
exoPlayer.release()
in both cases (I still have to save the state and resume it) (Also can anyone tell me if you see any problem in the code below?)
Copy code
@Composable
fun Video() {
    val context = LocalContext.current
    val lifecycleState by LocalLifecycleOwner.current.lifecycle.collectState()
    val exoPlayer = remember {
        SimpleExoPlayer.Builder(context.applicationContext)
            .build()
    }
    DisposableEffect(key1 = exoPlayer) {
        val videoItem =
            MediaItem.fromUri("<https://storage.googleapis.com/exoplayer-test-media-0/BigBuckBunny_320x180.mp4>")
        exoPlayer.setMediaItem(videoItem)
//        exoPlayer.volume = 0f
        exoPlayer.prepare()
        onDispose { exoPlayer.release() }
    }
    exoPlayer.playWhenReady = lifecycleState.isAtLeast(Lifecycle.State.RESUMED)
    AndroidView(
        modifier = Modifier
            .fillMaxWidth()
            .aspectRatio(16f / 9f),
        factory = { context ->
            PlayerView(context).apply {
                player = exoPlayer
                useController = false
            }
        }
    )
}

@Composable
private fun Lifecycle.collectState(): State<Lifecycle.State> {
    val state = remember { mutableStateOf(currentState) }
    DisposableEffect(this) {
        val listener = LifecycleEventObserver { _, _ ->
            state.value = currentState
        }
        addObserver(listener)
        onDispose {
            removeObserver(listener)
        }
    }
    return state
}
I've found this https://github.com/evant/compose-shown/blob/main/shown/src/main/java/me/tatarka/compose/shown/Shown.kt containing some code to detect when a Composable becomes visible (I also need the opposite) is there any better way / API to monitor how much of a composable is visible on the screen at any given time? Thanks
@Filip Wiesner is this ok?
f
Yes, thank you 👌
c
You should be able to use LazyListState.isVisible() for this. You might need some of the properties of LazyListState.layoutInfo too, depending on your exact requirements
d
Thanks @Chris Miller I'll look into this
I kinda don't like I need to let my component know it is inside a LazyList tho'
c
It shouldn't have to - the enabling/disabling code can live outside the LazyColumn e.g something like:
Copy code
val state = rememberLazyListState()
LazyColumn(state = state, ...) {
  ...
}

if (state.isVisible(videoItemIndex) {
  // start video if not already started
} else {
  // stop video if not already stopped
}
You'll likely need extra smarts around the state handling, but the above is the general gist of it I think
d
The container, in my case, is dynamic with multiple items types in it. The container knows almost nothing of the items inside it. So it doesn't know about video start/stop either. Also I do not see a
isVisible()
method in LazyListState
a
See here and here.
c
Ah sorry, isVisible() is my own 😂 defined as:
Copy code
private fun LazyListState.isVisible(index: Int): Boolean {
  return index >= firstVisibleItemIndex && index < firstVisibleItemIndex + layoutInfo.visibleItemsInfo.size
}
d
@Albert Chang thanks I've read those.
Still, I need to know "when" to check if the item is visible. I need to build some
val isVisible: State<Boolean>
and as far as I can nor
LazyListState.layoutInfo
nor firstVisibleItemIndex give me a state I can build upon
a
You can use a snapshot flow. Something like
snapshotFlow { listState.layoutInfo.visibleItemsInfo.any { it.key == someKey } }.collect()
d
@Albert Chang but snapshotFlow converts a State into a Flow. I don't have a State. LazyListState contains vars (layoutInfo is a var) the state is hidden
Copy code
private val layoutInfoState = mutableStateOf<LazyListLayoutInfo>(EmptyLazyListLayoutInfo)
would it work anyway?
a
layoutInfo
is a property backed by a mutable state, not a variable. And you don't necessarily need a state. Flow is also observable. You can collect the snapshot flow in a
LaunchedEffect
and when the item becomes invisible you stop the player.
d
@Albert Chang I'm trying to integrate this
@Albert Chang thanks I've managed to make it work
👍 1
131 Views