You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jetpack Compose中ExoPlayer2裁剪失效:视频仍重叠渲染

Jetpack Compose ExoPlayer视频对比裁剪重叠问题

问题描述

需实现视频对比功能:在Jetpack Compose的Row中并排展示两个视频,支持各自的缩放、平移操作,且每个视频必须被严格裁剪在Row对应的半区内。此前图片对比方案可正常运行,但替换为ExoPlayer2后出现裁剪失效问题——缩放后的视频会超出所在半区,与另一侧视频重叠渲染,clipToBounds等裁剪修饰符无法完全生效。

现有代码

VideoComparison Composable

@Composable
fun VideoComparison(
) {
    Row(modifier = Modifier.fillMaxSize()) {
        // 左侧视频
        Box(
            modifier = Modifier.background(Color.Blue).weight(1f).clipToBounds()
        ) {
            // 缩小缩放
            Box(modifier = Modifier.graphicsLayer { scaleX = 0.6f; scaleY = 0.6f}) {
                MyPlayer(
                    Uri.parse("https://storage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4")
                )
            }
        }
        // 右侧视频
        Box(
            modifier = Modifier.weight(1f).clipToBounds()
        ) {
            // 放大缩放
            Box(modifier = Modifier.graphicsLayer { scaleX = 1.8f; scaleY = 1.8f}) {
                MyPlayer(
                    Uri.parse("https://storage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4")
                )
            }
        }
    }
}

MyPlayer 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() }
    }
}

已尝试方案

  • 使用clipToBounds、clip()、graphicsLayer(clip = true)等修饰符,在父Box、AndroidView等不同元素上组合使用,均无法解决重叠问题。

解决方案

问题根源在于ExoPlayer的StyledPlayerView是原生View组件,Compose的裁剪修饰符无法直接作用于其独立的渲染层级,需从以下层面处理:

1. 为Player添加Compose层级裁剪约束

在MyPlayer的AndroidView外层包裹带裁剪的Box,同时强制PlayerView填充父容器:

@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

    // 外层添加带裁剪的Box,确保PlayerView被约束在容器内
    Box(modifier = Modifier.fillMaxSize().graphicsLayer(clip = true)) {
        DisposableEffect(
            AndroidView(factory = {
                StyledPlayerView(context).apply {
                    hideController()
                    clipToOutline = true
                    clipChildren = true
                    useController = false
                    player = exoPlayer
                    // 强制PlayerView填充父容器
                    layoutParams = ViewGroup.LayoutParams(
                        ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.MATCH_PARENT
                    )
                }
            })
        ) {
            onDispose { exoPlayer.release() }
        }
    }
}

2. 合并裁剪与缩放层级

将缩放和裁剪逻辑合并到同一个graphicsLayer中,确保裁剪作用在缩放后的内容上:

@Composable
fun VideoComparison(
) {
    Row(modifier = Modifier.fillMaxSize()) {
        // 左侧视频
        Box(
            modifier = Modifier.background(Color.Blue).weight(1f)
                .graphicsLayer(clip = true, scaleX = 0.6f, scaleY = 0.6f)
        ) {
            MyPlayer(
                Uri.parse("https://storage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4")
            )
        }
        // 右侧视频
        Box(
            modifier = Modifier.weight(1f)
                .graphicsLayer(clip = true, scaleX = 1.8f, scaleY = 1.8f)
        ) {
            MyPlayer(
                Uri.parse("https://storage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4")
            )
        }
    }
}

3. 备选优化:改用TextureView渲染

如果上述方案仍有问题,可禁用ExoPlayer的SurfaceView(其渲染层级独立于Compose),改用TextureView:
在StyledPlayerView配置中添加:

useSurfaceView = false

原理说明

  • Compose的裁剪修饰符基于自身渲染树生效,但SurfaceView的视频渲染在独立窗口层级,不受Compose约束;改用TextureView可让视频渲染融入Compose视图树,便于裁剪。
  • 将缩放与裁剪合并到同一个graphicsLayer,确保Compose合成时对整个区域进行裁剪,避免缩放后的内容溢出。

内容的提问来源于stack exchange,提问作者fragect

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 14:05:28