ViewPager2搭配ExoPlayer快速滑动切换视频时音轨混音问题求解
问题核心原因
- ViewPager2默认离屏缓存(offscreenPageLimit)为1,会保留当前页左右各1个Fragment实例,快速滑动时旧Fragment未被销毁,ExoPlayer仍在后台运行
- 现有代码暂停、释放逻辑存在空安全缺陷,未覆盖Fragment不可见但未触发onPause的场景
- prepareMedia方法直接设置
playWhenReady = true,Fragment只要创建完成就自动播放,不判断是否对用户可见 - 现有prepareMedia方法缺失MediaItem设置、prepare调用逻辑,播放器初始化逻辑不完整
修复步骤
1. 给Fragment增加可见性判断,只有可见时才播放
给StoryViewFragment新增可见状态标记,统一管理播放器状态:
// 新增变量标记当前Fragment是否对用户可见 private var isFragmentVisible = false override fun setUserVisibleHint(isVisibleToUser: Boolean) { super.setUserVisibleHint(isVisibleToUser) isFragmentVisible = isVisibleToUser handlePlayerState() } override fun onHiddenChanged(hidden: Boolean) { super.onHiddenChanged(hidden) isFragmentVisible = !hidden handlePlayerState() } // 统一处理播放器状态 private fun handlePlayerState() { if (isFragmentVisible && isResumed) { restartVideo() } else { pauseVideo() // 滑动频次高的场景可直接释放播放器节省内存 // releasePlayer() } } // 修改生命周期回调,配合可见性判断 override fun onResume() { super.onResume() handlePlayerState() } override fun onPause() { super.onPause() handlePlayerState() }
2. 修复现有代码空指针和逻辑缺陷
// 暂停逻辑做空判断 private fun pauseVideo() { simplePlayer?.playWhenReady = false } // 释放逻辑补全置空操作 private fun releasePlayer() { simplePlayer?.let { it.stop() it.release() simplePlayer = null } } // 补全prepareMedia的播放器初始化逻辑,移除自动播放配置 private fun prepareMedia(datamodel: Gettrendingvideos.Data.Postlist?) { val uri = Uri.parse(datamodel?.video_url) simplePlayer?.repeatMode = Player.REPEAT_MODE_ONE // 删除原有自动播放配置 simplePlayer.playWhenReady = true // 新增媒体源设置逻辑 val mediaItem = MediaItem.fromUri(uri) simplePlayer?.setMediaItem(mediaItem) simplePlayer?.prepare() // 原有其他业务逻辑保留 // ... simplePlayer?.addListener(playerCallback) toPlayVideoPosition = -1 // 最后统一判断状态决定是否播放 handlePlayerState() }
3. 可选优化:在宿主层统一管理播放状态
如果不想每个Fragment独立管理状态,可在ViewPager2的宿主中监听页面切换,统一控制播放:
// 宿主初始化ViewPager2时添加监听 viewPager2.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) { super.onPageSelected(position) supportFragmentManager.fragments.forEach { fragment -> if (fragment is StoryViewFragment) { // 匹配Fragment对应的位置,只有当前选中页播放,其他全部暂停 val fragmentPosition = // 可通过给Fragment新增参数存储对应position获取 if (fragmentPosition == position) { fragment.restartVideo() } else { fragment.pauseVideo() } } } } })
4. 补充View销毁时的释放逻辑
在View销毁时就释放播放器,不要等到Fragment全销毁再处理:
override fun onDestroyView() { releasePlayer() super.onDestroyView() }
内容的提问来源于stack exchange,提问作者Amit Chavan
相关产品推荐
相关产品推荐

