Having problem when trying to take 'screenshot' from composable using AndroidView to show video with exoplayer

Viewed 31

I'm using jetpack compose to build a screen with a background video using exoplayer and, as we know, exoplayer didn't have native support with compose yet, so we need to start a player using AndroidView. But I need to take a screenshot from the composable, I'm taking the screenshot using:

LocalView.current.drawToBitmap()

And the problem is that the video didn't appear on the bitmap. I've create a demo screen to show what's happening:

Demo screen to show the screen (composable above) and the screenshot (composable below)

Above you can see a rectangle composable with the video and a transparent red composable just to see something above the video.

Below is an Image composable that show the bitmap captured. And the video didn't appear, just the red rectangle.

Any idea about what's happening here? I've tried two libs that help take screenshot from compose (capturable and compose-screenshot) and both didn't work.


My code:

@Composable
fun DemoScreen() {
    var backgroundSnapShot by remember { mutableStateOf<Bitmap?>(null) }

    Column(
        Modifier
            .fillMaxSize()
            .background(Color.LightGray)
            .padding(16.dp)
    ) {

        Background(
            modifier = Modifier
                .fillMaxWidth()
                .weight(1f),
            capture = {
                backgroundSnapShot = it
            }
        )

        VerticalSpacer(30.dp)

        Box(
            Modifier
                .fillMaxWidth()
                .height(120.dp)
                .background(Color.DarkGray)
                .clip(RoundedCornerShape(8.dp))
        ) {
            backgroundSnapShot?.let {
                Image(
                    it.asImageBitmap(),
                    contentDescription = "",
                    modifier = Modifier
                        .fillMaxSize(),
                    contentScale = ContentScale.Fit
                )
            }
        }
    }
}

@Composable
fun Background(modifier: Modifier, capture: (Bitmap?) -> Unit) {

    val playerState = rememberVideoPlayerState(
        "my video url",
        prepare = true,
        autoPlay = true,
    )

    Box(modifier.clip(RoundedCornerShape(8.dp))) {
        VideoPlayer(
            state = playerState,
            Modifier.fillMaxSize(),
        )

        Indicator(
            modifier = Modifier
                .fillMaxWidth()
                .height(120.dp),
            capture = capture,
        )
    }
}

@Composable
fun Indicator(modifier: Modifier, capture: (Bitmap?) -> Unit) {
    var offsetY by remember { mutableStateOf(0f) }

    val view = LocalView.current

    Box(
        modifier
            .offset { IntOffset(0, offsetY.roundToInt()) }
            .background(Color.Red.copy(0.3f))
            .clip(RoundedCornerShape(8.dp))
            .pointerInput(Unit) {
                detectDragGestures { change, dragAmount ->
                    change.consume()
                    offsetY += dragAmount.y
                    capture.invoke(view.drawToBitmap())
                    //yes, I moved the box to invoke the capture method, you can see this in the print that the box has Y offset
                }
            }
    )
}

The exoplayer implementation on compose:

@Composable
fun rememberVideoPlayerState(
    video: String,
    prepare: Boolean = true,
    autoPlay: Boolean = true,
    frameListener: VideoFrameMetadataListener? = null,
): VideoPlayerState {
    val context = LocalContext.current

    val currentWindow by remember { mutableStateOf(0) }
    val playbackPosition by remember { mutableStateOf(0L) }
    val mediaItem by remember { mutableStateOf(MediaItem.Builder()) }

    val exoPlayer = remember(context) {
        val extractorsFactory = DefaultExtractorsFactory()
            .setMp4ExtractorFlags(Mp4Extractor.FLAG_WORKAROUND_IGNORE_EDIT_LISTS)
            .setFragmentedMp4ExtractorFlags(FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_EDIT_LISTS)

        val mediaSourceFactory = DefaultMediaSourceFactory(context, extractorsFactory)

        val player = SimpleExoPlayer.Builder(context).apply {
            setLoadControl(DefaultLoadControl())
            setTrackSelector(DefaultTrackSelector(context))
            setMediaSourceFactory(mediaSourceFactory)
            setBandwidthMeter(DefaultBandwidthMeter.getSingletonInstance(context))
        }.build().apply {
            mediaItem.setUri(video)
            setMediaItem(mediaItem.build())
            playWhenReady = true
            seekTo(currentWindow, playbackPosition)
            repeatMode = ExoPlayer.REPEAT_MODE_ONE
            if (prepare) prepare()
        }

        VideoPlayerState(player)
    }

    if (autoPlay) exoPlayer.start()

    return exoPlayer
}

@Composable
fun VideoPlayer(state: VideoPlayerState, modifier: Modifier = Modifier) = AndroidView(
    modifier = modifier,
    factory = { context ->
        StyledPlayerView(context).apply {
            useController = false
            resizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM
            setKeepContentOnPlayerReset(true)
        }
    },
) {
    it.player = state.inner
}
0 Answers
Related