HorizontalPager, Recyclerview, 디바이스의 길이를 넘어가는 Column 등 스크롤의 형태는 다양합니다. 또한 현업에서는 compose와 xml을 혼용하여 사용하는 경우가 많기 때문에 각 상황에 대해 모두 대처해야 합니다.
그렇다면, 이렇게 다양한 상황 속에서 어떻게 영상 재생 로직을 구현할 수 있을까요?
상황별 문제들을 그룹화하여, 어떻게 영상 재생 로직을 구현했는지에 대한 저의 경험을 공유하고자 합니다.
다음 사전 지식을 알고 있다면 글을 이해하는데 더욱 도움이 됩니다
LazyColumn의 경우 LazyListState
를 관찰하여 현재 보이는 리스트 아이템의 LazyListItemInfo
를 알 수 있습니다. 해당 정보를 이용해, 리스트에서 어떤 index의 video를 재생할지 결정할 수 있습니다.
예를 들어, 현재 화면에 보이는 아이템의 개수가 최대 3개일 때 다음과 같은 로직을 구현할 수 있습니다.
위의 로직을 코드로 구현하면 다음과 같습니다.
val playVideoIndex by remember {
derivedStateOf {
val visibleItems = lazyListState.layoutInfo.visibleItemsInfo
return when (visibleItems.size) {
1 -> visibleItems.first().index
2 -> {
visibleItems.firstOrNull {
it.offset + it.size >= it.size * 0.7f
}?.index ?: visibleItems.first().index
}
3 -> {
visibleItems[1].index
}
else -> visibleItems.firstOrNull()?.index ?: 0
}
}
}
playVideoIndex에 해당하는 player를 재생하도록 구현하면, 다음과 같이 스크롤에 따라 자동으로 영상이 재생되도록 구현할 수 있습니다.
RecyclerView에서는 ViewHolder가 화면에 표시되거나 사라질 때 호출되는 콜백 메서드를 제공합니다.
이 두 메서드를 활용하여, 화면에 보이지 않을 때 비디오 재생을 중단하고, 다시 나타날 때 재생을 재개하는 로직을 구현할 수 있습니다.
override fun onViewAttachedToWindow(holder: RecyclerView.ViewHolder) {
super.onViewAttachedToWindow(holder)
holder.replayPlayer()
}
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
super.onViewDetachedFromWindow(holder)
if (holder is VideoViewHolder) {
holder.clearPlayer()
}
}
class VideoViewHolder() : RecyclerView.ViewHolder(binding.root) {
fun replayPlayer() {
// video 재생
}
fun pausePlayer() {
// video 중지
}
}
디바이스 화면을 넘어가는 Composable 내부에 특정 컴포넌트만 Video일 경우, 스크롤을 함에 따라 영상 재생이 중단되어야 합니다.
이 경우, 현재 컴포넌트가 화면에 보이는지 여부를 판단하여 영상 재생 로직을 구현해야 합니다.
우선 이를 확인하기 위해 현재의 displayMatrix를 가져옵니다.
val context = LocalContext.current
val displayMetrics = context.resources.displayMetrics
여기서 widthPixels, heightPixels은 전체 화면 해상도(px)를 나타냅니다.
이후 Video 컴포넌트가 실제 레이아웃에 배치된 이후의 정보가 필요합니다. 이를 위해 onGloballyPositioned
를 사용합니다.
Box(
modifier = modifier
.onGloballyPositioned { coordinates ->
// ..
}
onGloballyPositioned
는 컴포저블이 실제로 레이아웃에 배치된 후의 위치 정보 (LayoutCoordinates)를 제공해주는 콜백입니다. 레이아웃 배치가 끝난 뒤에 컴포넌트의 위치를 측정해야하기 때문에 onGloballyPositioned
콜백 내부에서 가시성을 확인합니다.
val currentlyVisible = componentRect.overlaps(
Rect(
left = 0f,
top = 0f,
right = displayMetrics.widthPixels.toFloat(),
bottom = displayMetrics.heightPixels.toFloat()
)
)
이후 Video 컴포넌트의 현재 위치와 크기를 기준으로 한 화면 상(Window 기준)의 위치 정보를 가져옵니다. 이렇게 화면 상의 컴포넌트 위치와, 전체 화면의 위치를 겹침 여부를 확인하여 Video 컴포넌트의 가시성을 확인할 수 있습니다. 이제 currentlyVisible
을 이용해 비디오 재생을 제어하여 로직을 구현할 수 있습니다.
전체 코드는 다음과 같습니다.
val context = LocalContext.current
val displayMetrics = context.resources.displayMetrics
Box(
modifier = modifier
.onGloballyPositioned { coordinates ->
val componentRect = coordinates.boundsInWindow()
val currentlyVisible = componentRect.overlaps(
Rect(
left = 0f,
top = 0f,
right = displayMetrics.widthPixels.toFloat(),
bottom = displayMetrics.heightPixels.toFloat()
)
)
// currentlyVisible을 이용해 비디오 재생 제어
}
)