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

Android单ExoPlayer实例多视图播放同一视频方案咨询

基于ExoPlayer单实例实现多视图同步播放方案

核心思路

利用SurfaceTexture的纹理共享特性:让单个ExoPlayer实例将视频帧渲染到一个全局SurfaceTexture,多个播放视图各自绑定这个SurfaceTexture,通过监听帧更新事件触发所有视图重绘,实现单实例多视图播放。轻微不同步来自视图各自的重绘时机,符合需求。


基于Android Views的实现

1. 全局SurfaceTexture初始化

创建全局的SurfaceTexture并设置帧可用监听,确保新帧到来时通知所有视图更新:

private lateinit var sharedSurfaceTexture: SurfaceTexture
private val videoViews = mutableListOf<SharedTextureView>()

// 在Activity/Fragment初始化时调用
private fun initSharedTexture() {
    sharedSurfaceTexture = SurfaceTexture(0).apply {
        setOnFrameAvailableListener { st ->
            st.updateTexImage() // 更新纹理到最新帧
            videoViews.forEach { it.invalidate() } // 触发所有视图重绘
        }
    }
}

2. 自定义共享纹理的TextureView

重写TextureView,替换默认的SurfaceTexture为全局共享实例:

class SharedTextureView(context: Context, attrs: AttributeSet? = null) : TextureView(context, attrs) {
    lateinit var globalSurfaceTexture: SurfaceTexture

    override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
        // 释放默认创建的SurfaceTexture,绑定全局共享实例
        surface.release()
        setSurfaceTexture(globalSurfaceTexture)
    }
}

3. 关联ExoPlayer与SurfaceTexture

创建Surface从全局SurfaceTexture,并绑定到ExoPlayer:

private fun initPlayer(videoUrl: String) {
    val player = ExoPlayer.Builder(context).build()
    val videoSurface = Surface(sharedSurfaceTexture)
    player.setVideoSurface(videoSurface)
    
    // 设置媒体源并启动播放
    player.setMediaItem(MediaItem.fromUri(videoUrl))
    player.prepare()
    player.play()

    // 记得在销毁时释放资源
    lifecycle.addObserver(object : DefaultLifecycleObserver {
        override fun onDestroy(owner: LifecycleOwner) {
            player.release()
            sharedSurfaceTexture.release()
        }
    })
}

4. 绑定视图到全局纹理

在布局中添加四个SharedTextureView,并将它们加入视图列表、绑定全局纹理:

// 假设布局中有四个sharedTextureView1-4
videoViews.addAll(listOf(sharedTextureView1, sharedTextureView2, sharedTextureView3, sharedTextureView4))
videoViews.forEach { it.globalSurfaceTexture = sharedSurfaceTexture }

基于Jetpack Compose的实现

1. 全局状态与ExoPlayer初始化

使用remember持有全局SurfaceTexture和ExoPlayer实例,通过状态变量触发视图重绘:

@Composable
fun MultiVideoPlayerScreen(videoUrl: String) {
    // 全局共享的SurfaceTexture
    val sharedSurfaceTexture = remember {
        SurfaceTexture(0).apply {
            setOnFrameAvailableListener { st ->
                st.updateTexImage()
                // 更新状态变量触发所有视图重组重绘
                (LocalContext.current as? Activity)?.runOnUiThread {
                    currentFrameTimestamp = st.timestamp
                }
            }
        }
    }
    var currentFrameTimestamp by remember { mutableStateOf(0L) }

    // 初始化单实例ExoPlayer
    val player = remember {
        ExoPlayer.Builder(LocalContext.current).build().apply {
            setVideoSurface(Surface(sharedSurfaceTexture))
            setMediaItem(MediaItem.fromUri(videoUrl))
            prepare()
            play()
        }
    }

    // 自动释放资源
    DisposableEffect(Unit) {
        onDispose {
            player.release()
            sharedSurfaceTexture.release()
        }
    }

    // 布局四个播放视图
    Column(modifier = Modifier.fillMaxSize()) {
        Row(modifier = Modifier.weight(1f)) {
            SharedVideoView(sharedSurfaceTexture, currentFrameTimestamp, Modifier.weight(1f))
            SharedVideoView(sharedSurfaceTexture, currentFrameTimestamp, Modifier.weight(1f))
        }
        Row(modifier = Modifier.weight(1f)) {
            SharedVideoView(sharedSurfaceTexture, currentFrameTimestamp, Modifier.weight(1f))
            SharedVideoView(sharedSurfaceTexture, currentFrameTimestamp, Modifier.weight(1f))
        }
    }
}

2. 共享纹理的Compose视图

用AndroidView包裹TextureView,绑定全局SurfaceTexture:

@Composable
fun SharedVideoView(
    surfaceTexture: SurfaceTexture,
    triggerRedraw: Long,
    modifier: Modifier = Modifier
) {
    AndroidView(
        factory = { context ->
            TextureView(context).apply {
                setSurfaceTexture(surfaceTexture)
            }
        },
        modifier = modifier,
        update = { view ->
            // 每次帧时间戳变化时触发重绘
            view.invalidate()
        }
    )
}

关键注意事项

  • 所有视图共享同一个SurfaceTexture,无需额外同步逻辑,轻微帧差属于正常现象。
  • 必须确保SurfaceTexture和ExoPlayer的生命周期一致,在页面销毁时及时释放,避免内存泄漏。
  • 如果需要适配视频比例,可在TextureView的onMeasure方法中自定义缩放逻辑,或在Compose中通过modifier调整。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 05:59:54