ViewPager中ExoPlayer播放/暂停控制及滚动播放异常问题
解决ViewPager中ExoPlayer滚动切换页面无法播放的问题
问题根源分析
你遇到的核心问题有两个:
- 全局Player实例冲突:你的适配器里只用了一个
player全局变量,当ViewPager销毁页面(调用destroyItem)时,这个Player会被释放,导致切换到新的视频页面时没有可用的Player实例。 - 页面切换的播放控制未关联到当前页面:之前的
onPageSelected逻辑操作的是全局Player,但这个Player可能已经被释放或者不属于当前页面,自然无法控制播放。
修复方案
我们需要让每个视频页面持有自己的Player实例,并通过ViewPager的回调正确控制当前页面的播放状态。
第一步:修改MediaVPAdapter
重构适配器,为每个视频页面创建独立的Player实例,并通过Map管理这些实例,同时添加外部控制播放/暂停的方法:
class MediaVPAdapter( val context: Context, @LayoutRes val layoutRes: Int, val medias: List<MediaFiles> = listOf() ) : PagerAdapter() { // 保存每个位置对应的Player实例,避免重复创建和内存泄漏 private val playerMap = mutableMapOf<Int, SimpleExoPlayer>() override fun isViewFromObject(view: View, o: Any): Boolean { return view == o } override fun getCount() = medias.size override fun instantiateItem(container: ViewGroup, position: Int): Any { return LayoutInflater.from(context).inflate(layoutRes, container, false).apply { if (medias.isNotEmpty()) { when (medias[position].mediaType) { MediaType.PHOTO -> { ssImageView.makeInvisible(true) exoPlayerView.makeInvisible() // 保留你原本的图片显示逻辑 } MediaType.VIDEO -> { ssImageView.makeInvisible(true) exoPlayerView.makeVisible() // 为当前视频页面创建独立的Player实例 val player = ExoPlayerFactory.newSimpleInstance( DefaultRenderersFactory(context), DefaultTrackSelector(), DefaultLoadControl() ) playerMap[position] = player medias[position].uri?.let { uri -> initPlayer(this, player, uri) } } null -> { /* 处理null媒体类型的情况 */ } } } container.addView(this, 0) } } private fun initPlayer(view: View, player: SimpleExoPlayer, uri: Uri) { Timber.i("initPlayer: uri: $uri") with(view) { exoPlayerView.player = player val mediaSource = buildMediaSource(uri) player.prepare(mediaSource, true, false) player.playWhenReady = false // 可选:恢复之前的播放进度(需要给MediaFiles添加playPosition字段) medias.firstOrNull { it.uri == uri }?.let { media -> player.seekTo(media.playPosition) } } } private fun buildMediaSource(uri: Uri) = ExtractorMediaSource.Factory( DefaultDataSourceFactory(context, Util.getUserAgent(context, context.getString(R.string.app_name))) ) .setExtractorsFactory(DefaultExtractorsFactory()) .createMediaSource(uri) override fun destroyItem(container: ViewGroup, position: Int, obj: Any) { // 释放当前位置的Player,并保存播放进度 playerMap[position]?.let { player -> medias.getOrNull(position)?.playPosition = player.currentPosition player.playWhenReady = false player.release() playerMap.remove(position) } (obj as View).exoPlayerView.player = null container.removeView(obj) } // 外部调用:播放指定位置的视频 fun playPage(position: Int) { playerMap[position]?.let { player -> player.playWhenReady = true } } // 外部调用:暂停非当前位置的所有视频 fun pauseOtherPages(currentPosition: Int) { playerMap.forEach { (pos, player) -> if (pos != currentPosition) { player.playWhenReady = false } } } // 页面销毁时调用:释放所有Player实例 fun releaseAllPlayers() { playerMap.values.forEach { it.release() } playerMap.clear() } }
第二步:添加ViewPager页面切换监听
在你的Activity/Fragment中,给ViewPager添加监听,控制页面切换时的播放状态:
// 初始化适配器和ViewPager val mediaAdapter = MediaVPAdapter( this, R.layout.fragment_media, medias?.toList() ?: listOf() ) vpMedia.adapter = mediaAdapter vpMedia.currentItem = position ?: 0 vpMedia.offscreenPageLimit = 2 // 适当提高预加载数量,减少Player频繁创建销毁 // 添加页面切换监听 vpMedia.addOnPageChangeListener(object : ViewPager.OnPageChangeListener { override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { // 滚动过程中暂停所有非当前页面的视频,避免卡顿 mediaAdapter.pauseOtherPages(position) } override fun onPageSelected(position: Int) { // 选中页面后,如果是视频则自动播放 if (medias[position].mediaType == MediaType.VIDEO) { mediaAdapter.playPage(position) } else { // 如果是图片,暂停所有视频播放 mediaAdapter.pauseOtherPages(position) } } override fun onPageScrollStateChanged(state: Int) { // 滚动停止时,确保当前视频页面处于正确的播放状态 if (state == ViewPager.SCROLL_STATE_IDLE) { val currentPos = vpMedia.currentItem if (medias[currentPos].mediaType == MediaType.VIDEO) { mediaAdapter.playPage(currentPos) } } } }) // 页面销毁时释放所有Player,避免内存泄漏 override fun onDestroy() { super.onDestroy() mediaAdapter.releaseAllPlayers() }
第三步:可选优化(保存播放进度)
给你的MediaFiles类添加一个playPosition字段(类型为Long,默认0),这样用户切换页面后再回来可以继续之前的进度:
data class MediaFiles( val uri: Uri?, val mediaType: MediaType?, var playPosition: Long = 0 // 添加这个字段 )
额外注意事项
- offscreenPageLimit设置:不要设为0,适当提高(比如2),这样ViewPager会预加载前后页面,减少Player的频繁创建和销毁,提升流畅度。
- 内存泄漏防范:一定要在页面销毁时调用
releaseAllPlayers(),释放所有Player资源。 - 播放按钮控制:如果你的页面有自定义的播放/暂停按钮,需要在按钮点击时获取当前页面的Player实例,直接控制
playWhenReady状态,而不是操作全局变量。
内容的提问来源于stack exchange,提问作者Vishak A Kamath
相关产品推荐
相关产品推荐

