Orb
03/09/2026, 2:43 PM@androidx.annotation.OptIn(UnstableApi::class)
@Composable
fun VideoPlayer(
modifier: Modifier,
uri: Uri,
startTimeMs: Long,
endTimeMs: Long
) {
val context = LocalContext.current
val mediaItem = MediaItem.fromUri(uri)
val exoPlayer = remember {
ExoPlayer.Builder(context).build()
}
LaunchedEffect(uri, startTimeMs, endTimeMs) {
exoPlayer.setMediaItem(MediaItem.fromUri(uri))
exoPlayer.seekTo(startTimeMs)
exoPlayer.playWhenReady = true
}
LaunchedEffect(exoPlayer, startTimeMs, endTimeMs) {
//Some stuff
}
DisposableEffect(Unit) {
onDispose {
exoPlayer.release()
}
}
Box {
ContentFrame(
player = exoPlayer
)
}
}
```
And then I have a composable DraggableContainer which I use to move texts or stickers over the image or video playing. It works fine when it's used over an image (coil AsyncImage).
But when it's placed over VideoPlayer in a Box, I lose the ability to drag the texts or the stickers around. Note: DraggableContainer uses graphicsLayer in its implementation. Similar things occur where I couldn't drag the RangeSlider from Material3 when VideoPlayer (which uses ContentFrame or PlayerSurface) was beneath it in a Box.
I also tried setting useTextureView (or something like that), but got the same result. Maybe I need to set texture view but also simultaneously increase the z-axis of my DraggableContainer? I don't really know though, kinda a beginner.
Here's some more code for context:
Codes in threadOrb
03/09/2026, 3:56 PMBox {
if (isVideo) {
VideoPlayer(
modifier = videoSizeGetModifier,
uri = uri,
ong(),
endTimeMs = (endTime * 1000).toLong()
)
} else {
AsyncImage(
model = ImageRequest.Builder(context)
.data(mediaUri)
.crossfade(true)
.build(),
contentDescription = "Selected image",
modifier = Modifier
.fillMaxSize()
.onSizeChanged {
viewModel.updateContainerSize(it)
},
onSuccess = { state ->
mediaIntrinsicSize = state.painter.intrinsicSize
}
)
}
DraggableContainer(
children = uiState.overlayTexts,
stickers = uiState.overlayStickers,
overlayInteraction = uiState.overlayInteraction,
onChildTransformChanged = { id, offset, rotation, scale ->
val contentRect = calculateContentRect(containerSize, mediaIntrinsicSize)
if (contentRect != Rect.Zero) {
val relativeRatioX = (offset.x - contentRect.left) / contentRect.width
val relativeRatioY = (offset.y - <http://contentRect.top|contentRect.top>) / contentRect.height
viewModel.transformOverlayText(
id = id,
offset = offset,
positionX = relativeRatioX,
positionY = relativeRatioY,
rotation = rotation,
scale = scale
)
}
},
onStickerTransformChanged = { id, offset, rotation, scale, parentSize, currentStickerSize ->
//Some stuff,
onChildClick = {
viewModel.selectOverlay(it)
editing.value = true
},
modifier = Modifier.matchParentSize()
)
}
@Composable
fun DraggableContainer(
children: List<TextOverlay>,
stickers: List<OverlaySticker>,
onChildTransformChanged: (id: String, offset: Offset, rotation: Float, scale: Float) -> Unit,
onStickerTransformChanged: (id: String, offset: Offset, rotation: Float, scale: Float, parentSize: Size, currentStickerSize: Size) -> Unit,
overlayInteraction: OverlayInteraction,
onChildClick: (String) -> Unit,
modifier: Modifier = Modifier
) {
val density = LocalDensity.current
val childSizes = remember { mutableStateMapOf<String, Size>() }
val stickerSizes = remember { mutableStateMapOf<String, Size>() }
val currentChildren by rememberUpdatedState(children)
val currentStickers by rememberUpdatedState(stickers)
BoxWithConstraints(modifier) {
val parentWidth = constraints.maxWidth.toFloat()
val parentHeight = constraints.maxHeight.toFloat()
when (overlayInteraction) {
is OverlayInteraction.Selected -> {}
else -> {
children.forEach { child ->
if (child.text.isNotEmpty()) {
Box(
modifier = Modifier
.graphicsLayer {
translationX = child.offsetRatioX * parentWidth
translationY = child.offsetRatioY * parentHeight
rotationZ = child.rotation
scaleX = child.scale
scaleY = child.scale
}
.onSizeChanged {
childSizes[child.id] = Size(it.width.toFloat(), it.height.toFloat())
}
) {
OverlayText(
text = child.text,
onClick = { },
maxWidth = with(density) { (parentWidth / child.scale).toDp() },
maxHeight = with(density) { (parentHeight / child.scale).toDp() },
textStyle = child.config.textStyle,
containerColor = child.config.containerColor
)
}
}
}
// stickers logic...
}
}
// GESTURE OVERLAY
Box(
modifier = Modifier
.fillMaxSize()
.zIndex(1f)
.pointerInput(Unit) {
detectTapGestures(
onTap = { tapOffset ->
val clickedChild = currentChildren.asReversed().find { child ->
val size = childSizes[child.id] ?: Size.Zero
val centerX = child.offsetRatioX * parentWidth + (size.width * child.scale / 2)
val centerY = child.offsetRatioY * parentHeight + (size.height * child.scale / 2)
isPointInRotatedBox(
tapOffset,
Offset(centerX, centerY),
Size(size.width * child.scale, size.height * child.scale),
child.rotation
)
}
if (clickedChild != null) onChildClick(clickedChild.id)
}
)
}
)
}
}