基于ViewPager 2和ExoPlayer的可滑动播放器多视频播放问题求助
ViewPager2 + ExoPlayer2 视频播放器问题修复方案
核心问题根源
- ViewPager2默认预加载相邻页面,快速滑动时未及时处理页面状态切换,导致多Player实例同时播放
- 未精准监听页面可见性/选中状态,无法控制Player的播放/暂停时机
- Player实例与页面绑定逻辑混乱,未在页面回收时释放资源
具体修复步骤
1. 限制ViewPager2预加载数量
在Activity中设置仅加载当前可见页面,避免预加载过多页面导致多Player同时运行:
viewPager2.offscreenPageLimit = 0
注:Android 12及以上版本默认值为0,低版本需手动设置
2. 监听页面状态,精准控制播放/暂停
方案一:Activity中监听ViewPager2页面状态
通过OnPageChangeCallback监听滑动状态,在拖动时暂停所有播放,停止时仅播放当前页面:
viewPager2.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { override fun onPageScrollStateChanged(state: Int) { super.onPageScrollStateChanged(state) when(state) { // 开始拖动,暂停所有视频 ViewPager2.SCROLL_STATE_DRAGGING -> { pauseAllPlayers() } // 滑动停止,播放当前选中页面 ViewPager2.SCROLL_STATE_IDLE -> { val currentPos = viewPager2.currentItem playCurrentPlayer(currentPos) } } } override fun onPageSelected(position: Int) { super.onPageSelected(position) // 切换页面时,确保仅当前页面播放 pauseAllPlayers() playCurrentPlayer(position) } }) // 辅助方法:暂停所有页面的Player private fun pauseAllPlayers() { adapter?.let { for (i in 0 until it.itemCount) { val holder = viewPager2.findViewHolderForAdapterPosition(i) as VideoViewHolder? holder?.player?.pause() } } } // 辅助方法:播放指定位置的Player private fun playCurrentPlayer(position: Int) { val currentHolder = viewPager2.findViewHolderForAdapterPosition(position) as VideoViewHolder? currentHolder?.let { if (!it.player.isPlaying) { it.player.play() } } }
方案二:ViewHolder中监听自身可见性
在ViewHolder内监听ItemView的可见性,直接控制当前Player的状态:
class VideoViewHolder(itemView: View, val player: ExoPlayer) : RecyclerView.ViewHolder(itemView) { init { itemView.viewTreeObserver.addOnScrollChangedListener { val rect = Rect() val isFullyVisible = itemView.getGlobalVisibleRect(rect) && rect.width() == itemView.width && rect.height() == itemView.height if (isFullyVisible) { if (!player.isPlaying) player.play() } else { if (player.isPlaying) player.pause() } } } }
3. 规范Player实例的创建与回收
将Player与ViewHolder绑定,在Adapter中正确管理生命周期,避免复用混乱:
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VideoViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.item_video, parent, false) val player = ExoPlayer.Builder(parent.context).build() val playerView = view.findViewById<PlayerView>(R.id.player_view) playerView.player = player return VideoViewHolder(view, player) } override fun onBindViewHolder(holder: VideoViewHolder, position: Int) { val videoUrl = videoList[position] val mediaItem = MediaItem.fromUri(videoUrl) // 重置Player状态,避免复用旧数据 holder.player.stop() holder.player.setMediaItem(mediaItem) holder.player.prepare() // 仅当前可见页面启动播放 val isCurrentPage = viewPager2.currentItem == position val isVisible = holder.itemView.isShown && holder.itemView.getGlobalVisibleRect(Rect()) if (isCurrentPage && isVisible) { holder.player.play() } else { holder.player.pause() } } override fun onViewRecycled(holder: VideoViewHolder) { super.onViewRecycled(holder) // 页面回收时释放Player资源 holder.player.pause() holder.player.release() }
4. 修复上一个视频无法播放的问题
- 确保
onPageSelected方法中,无论上滑还是下滑,都能正确获取目标页面的ViewHolder并启动播放 - 检查
onBindViewHolder中是否正确重置Player状态,避免旧数据干扰
额外优化建议
- 实现Player实例池,复用Player对象减少创建/销毁的性能开销
- 结合Lifecycle组件,在页面进入
STARTED状态时播放,STOPPED状态时暂停 - 延迟初始化Player,直到页面完全可见后再创建实例,减少内存占用
内容的提问来源于stack exchange,提问作者MDEV
相关产品推荐
相关产品推荐

