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

基于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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 11:37:48