Search code examples
androidandroid-jetpack-composeexoplayerexoplayer2.x

ExoPlayer2 not clipping correctly in Jetpack Compose and still rendering in the overlap with with other ExoPlayers


My goal is to create a video comparison. I want to display two videos next to each other and be able to freely specify scale and translation of each. The two videos should be clipped to their according half of the Row.

I created a solution that works for displaying Images, however when I tried to use two ExoPlayers instead of images, I encounted the following problem, where the clipping does partly work, however the overlap with the other Player is still rendered.

ExoPlayersClipOverlapDemonstrationGif

Here a schematic drawing of what happens:

ExoPlayersClipOverlapDemonstration

Here is the code of my Composable:

@Composable
fun VideoComparison(
) {
    Row(modifier = Modifier.fillMaxSize()) {
        //First Video
        Box(
            modifier = Modifier.background(Color.Blue).weight(1f).clipToBounds()
        ) {
            //Scaled smaller
            Box(modifier = Modifier.graphicsLayer { scaleX = 0.6f; scaleY = 0.6f}) {
                MyPlayer(
                    Uri.parse("https://storage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4")
                )
            }
        }
        //Second Video
        Box(
            modifier = Modifier.weight(1f).clipToBounds()
        ) {
            //Scaled larger
            Box(modifier = Modifier.graphicsLayer { scaleX = 1.8f; scaleY = 1.8f}) {
                MyPlayer(
                    Uri.parse("https://storage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4")
                )
            }
        }
    }
}

an here is the code of my Player Composable:

@Composable
fun MyPlayer(uri: Uri) {
    val context = LocalContext.current

    val exoPlayer = remember {
        ExoPlayer.Builder(context)
            .build()
            .also { exoPlayer ->
                val mediaItem = MediaItem.Builder()
                    .setUri(uri)
                    .build()
                exoPlayer.setMediaItem(mediaItem)
                exoPlayer.prepare()
            }
    }

    exoPlayer.playWhenReady = true

    DisposableEffect(
        AndroidView(factory = {
            StyledPlayerView(context).apply {
                hideController()
                clipToOutline = true
                clipChildren = true
                useController = false
                player = exoPlayer
            }
        })
    ) {
        onDispose { exoPlayer.release() }
    }
}

In the code posted above I use the modifier .clipToBounds, which works for example for Images but not for two ExoPlayers. I also tried using .clip() or .graphicLayer( clip = true) which all behave similar. I also tried various combinations of putting the clipping modifiers on different elements, for example directly on the AndroidView or on the parent Box.

I found a similar question here SimilarQuestion


Solution

  • When changing the surface type of the StyledPlayerView to a TextureView, the clipping works. This can be done by overwriting the standard attributes with a custom XML file:

    <?xml version="1.0" encoding="utf-8"?>
    <com.google.android.exoplayer2.ui.StyledPlayerView xmlns:app="http://schemas.android.com/apk/res-auto"
        app:surface_type="texture_view" />
    

    Loading it via:

    val parser = context.resources.getXml(R.xml.styled_player_view_custom)
    // Seek to the first tag.
    var type = 0
    while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) {
            type = parser.next()
    }
    val attrs = Xml.asAttributeSet(parser)
    

    and adding it to the StyledPlayerView like this:

    StyledPlayerView(context, attrs)
    

    However, due to the benefits of SurfaceView for ExoPlayer, like lower power consumption, more accurate frame timing etc. (as described here), it would still be interesting how to fix it using a SurfaceView.