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

AndroidView嵌套ExoPlayer动态切换MediaItem失效问题求助

Jetpack Compose集成ExoPlayer2切换MediaItem失效问题解决

问题场景

我在Jetpack Compose的Composable中通过AndroidView集成ExoPlayer2,初始视频可正常播放,但外部调用切换MediaItem的方法时视频无法更新,仅在同一个Composable内调用该方法有效。

相关代码

Composable中集成AndroidView的代码

DisposableEffect(
    AndroidView(
        modifier = Modifier
            .weight(3f)
            .fillMaxSize()
            .clip(RoundedCornerShape(RoundedSizeStatic.Medium))
            .shadow(
                elevation = 4.dp,
                shape = RoundedCornerShape(RoundedSizeStatic.Medium),
                spotColor = Color.White,
                ambientColor = Color.White
            ),
        factory = {
            PlayerView(viewModel.context).apply {
                player = largeVideoPlayerHandler.videoPlayer
                useController = false
                resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FILL

                FrameLayout.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.MATCH_PARENT
                )
            }
        }
    )
) {
    onDispose {
        largeVideoPlayerHandler.videoPlayer.release()
    }
}

ExoPlayer初始化代码

fun GetCameraPlayer(url: String): ExoPlayer {
    var rendersFactory = DefaultRenderersFactory(context).forceEnableMediaCodecAsynchronousQueueing() //prevent buffering memory leaks

    var newExoPlayer: ExoPlayer = ExoPlayer.Builder(context).setRenderersFactory(rendersFactory).build().apply {
        setMediaItem(MediaItem.fromUri(url))
        repeatMode = ExoPlayer.REPEAT_MODE_ONE
        playWhenReady = true

        prepare()
    }

    return newExoPlayer
}

切换MediaItem的方法

fun SwitchLargeVideoDisplay(url: String) {
    largeVideoDisplay.videoPlayer.setMediaItem(MediaItem.fromUri(url))
    largeVideoDisplay.videoPlayer.prepare()
}

问题原因及解决方法

1. 强制在主线程执行播放器操作

ExoPlayer的所有UI相关操作必须在Android主线程执行,外部调用若在非主线程(比如后台协程、子线程),会导致状态更新无法同步到PlayerView。修改切换方法:

fun SwitchLargeVideoDisplay(url: String) {
    Handler(Looper.getMainLooper()).post {
        largeVideoDisplay.videoPlayer.apply {
            setMediaItem(MediaItem.fromUri(url))
            prepare()
            play() // 显式调用play确保触发播放
        }
    }
}

2. 让Composable感知播放器状态变化

如果播放器实例没有被Composable正确观察,重组时PlayerView可能无法关联最新状态。将播放器包装在MutableState中:
在ViewModel中定义:

val videoPlayer = mutableStateOf<ExoPlayer?>(null)

// 初始化时赋值
videoPlayer.value = GetCameraPlayer(initialUrl)

修改Composable中的AndroidView:

AndroidView(
    modifier = Modifier
        .weight(3f)
        .fillMaxSize()
        .clip(RoundedCornerShape(RoundedSizeStatic.Medium))
        .shadow(
            elevation = 4.dp,
            shape = RoundedCornerShape(RoundedSizeStatic.Medium),
            spotColor = Color.White,
            ambientColor = Color.White
        ),
    factory = { context ->
        PlayerView(context).apply {
            useController = false
            resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FILL
        }
    },
    update = { playerView ->
        // 每次重组时同步播放器实例
        playerView.player = viewModel.videoPlayer.value
    }
)

3. 优化MediaItem切换逻辑

切换时先清除旧队列,避免状态冲突:

fun SwitchLargeVideoDisplay(url: String) {
    Handler(Looper.getMainLooper()).post {
        largeVideoDisplay.videoPlayer.apply {
            stop()
            clearMediaItems()
            addMediaItem(MediaItem.fromUri(url))
            prepare()
            play()
        }
    }
}

4. 验证播放器引用一致性

确保外部持有的videoPlayer和Composable中PlayerView关联的是同一个实例,可通过打印哈希值验证:

// Composable内打印
Log.d("ExoPlayer", "PlayerView绑定的播放器: ${largeVideoPlayerHandler.videoPlayer.hashCode()}")

// 外部调用时打印
Log.d("ExoPlayer", "外部持有的播放器: ${largeVideoDisplay.videoPlayer.hashCode()}")

若哈希值不一致,需排查实例传递逻辑,确保引用唯一。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 02:41:59