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

ExoPlayer无法播放视频,二次播放仅音频且触发Audio Sink异常

ExoPlayer播放异常问题修复

问题现象

  • 首次无法播放视频
  • 第二次播放仅能听到音频,无画面
  • 错误日志:

MediaCodecAudioRenderer: 音频输出错误
com.google.android.exoplayer2.audio.AudioSink$UnexpectedDiscontinuityException: 意外的音频轨道时间戳不连续:预期值1000001184217,实际值1000001391224

问题根源

  1. 单例播放器状态残留:VideoPlayer是单例,切换视频时未完全重置之前的播放状态,新旧媒体时间戳冲突触发音频渲染异常。
  2. 播放流程不规范:playVideo方法直接设置媒体项并准备,未先停止当前播放、重置播放位置,导致时间戳混乱。
  3. 未绑定Composable生命周期:播放器未跟随Composable的生命周期做暂停、资源清理操作,资源占用引发状态异常。
  4. PlayerView缓存未关联URL变化:getStyledPlayerView用remember缓存,但视频URL变化时未同步更新播放器媒体内容。

修复代码

1. 规范播放逻辑,重置状态再加载新视频

修改playVideo方法,确保切换视频前清空旧状态:

fun playVideo(url: String) {
    player?.let { exoPlayer ->
        // 停止当前播放、重置位置、清空旧媒体
        exoPlayer.stop()
        exoPlayer.seekTo(0)
        exoPlayer.clearMediaItems()
        
        val uri = Uri.parse(url)
        val mediaItem = MediaItem.fromUri(uri)
        exoPlayer.setMediaItem(mediaItem)
        exoPlayer.prepare()
        exoPlayer.play()
    }
}

2. 绑定Composable生命周期管理播放器

在VideoLayout中添加生命周期监听,避免资源泄漏:

@Composable
fun VideoLayout(videoUrl: String?, muted: Boolean = false) {
    if (videoUrl == null) return

    val context = LocalContext.current
    val videoPlayer = VideoPlayer.getInstance()
    val exoPlayer = videoPlayer.getExoplayer(context)
    
    // 实时更新静音状态
    LaunchedEffect(muted) {
        exoPlayer.volume = if (muted) 0f else 1f
    }

    // 跟随Composable生命周期处理播放与清理
    DisposableEffect(videoUrl) {
        videoPlayer.playVideo(videoUrl)
        
        onDispose {
            // 退出时暂停播放,清空媒体项(全局单例不要在这里release,避免其他页面无法复用)
            exoPlayer.pause()
            exoPlayer.clearMediaItems()
        }
    }

    val exoPlayerView = getStyledPlayerView(context, exoPlayer)
    exoPlayerView.useController = false

    AndroidView(
        modifier = Modifier.fillMaxSize().background(Color.Black, shape = RectangleShape),
        factory = { exoPlayerView }
    )
}

3. 确保PlayerView随播放器状态更新

修改getStyledPlayerView,依赖播放器实例确保状态同步:

@Composable
private fun getStyledPlayerView(
    context: Context,
    exoPlayer: ExoPlayer
) = remember(exoPlayer) { // 播放器变化时重新创建View
    StyledPlayerView(context).apply {
        player = exoPlayer
        resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT
    }
}

4. 优化单例播放器初始化

确保getExoplayer仅在播放器为空时初始化:

// VideoPlayer类中的getExoplayer方法示例
fun getExoplayer(context: Context): ExoPlayer {
    if (player == null) {
        val dataSourceFactory = DefaultHttpDataSource.Factory()
        val mediaSourceFactory = DefaultMediaSourceFactory(dataSourceFactory)
        player = ExoPlayer.Builder(context)
            .setMediaSourceFactory(mediaSourceFactory)
            .build()
    }
    return player!!
}

额外注意事项

  • 全局单例播放器不要在单个Composable中调用release(),建议在应用退出时统一释放。
  • 若仍无画面,检查视频格式是否被ExoPlayer支持,可添加日志查看视频渲染器状态。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 10:55:28