androidx ViewPager2 实现横向、纵向滑动播放短视频(二)

androidx ViewPager2 实现横向、纵向滑动播放短视频(一)

播放器的选择

上篇已经写到,用 androidx Viewpager2 基本实现了横向、纵向滑动展现视图数据的基本需求,接下来要做的就是播放短视频,期初用的播放器最终被放弃了,最终更换成ExoPlayer,ExoPalyer Github完成项目ExoPalyer doc
ExoPlayer 功能强大,使用方便灵活,按照项目说明 配置即可使用,遇到的一些问题:
我使用的是当前最新版本2.12.0,刚开始无法播放m3u8视频流,是因为我没引入exoplayer-hls,后来无法播放rtmp直播流,是因为我没引入extension-rtmp,注意extension-的版本要和exoplayer-的版本保持一致,关于extension- 看下图红色,点击进去就能看到支持的所有扩展在这里插入图片描述
根据实际项目需要,去引入各个依赖,关于HLS,RTMP,DASH 这些流媒体协议可以去查询学习了解。

 // ExoPlayer 
    implementation 'com.google.android.exoplayer:exoplayer-core:2.12.0'
 // implementation 'com.google.android.exoplayer:exoplayer-dash:2.12.0'
    implementation 'com.google.android.exoplayer:exoplayer-hls:2.12.0'
    implementation 'com.google.android.exoplayer:exoplayer-ui:2.12.0'
    implementation 'com.google.android.exoplayer:extension-rtmp:2.12.0'

Java和kotlin 混合开发时增加kotlin的配置

compileOptions {
        targetCompatibility = 1.8
        sourceCompatibility = 1.8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }

播放器的使用

参考ExoPalyer doc使用,抽离出来的简单大概代码如下:

一、播放器的初始化,包括根据需求添加监听器

/**
* 初始化播放器
*/
private fun initExoPlayer() {
   exoPlayer = SimpleExoPlayer.Builder(this.activity!!.applicationContext).build()
   exoPlayer?.repeatMode = Player.REPEAT_MODE_ALL
   exoPlayer?.addVideoListener(object : VideoListener {
       override fun onVideoSizeChanged(width: Int, height: Int, unappliedRotationDegrees: Int, pixelWidthHeightRatio: Float) {
           super.onVideoSizeChanged(width, height, unappliedRotationDegrees, pixelWidthHeightRatio)
           if (currentVideoHolder == null) return
           if (width < height) {
               currentVideoHolder!!.playerView!!.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM
           } else {
               currentVideoHolder!!.playerView!!.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT
           }
       }
   })
   exoPlayer?.addListener(object : Player.EventListener {
       override fun onPlayerError(error: ExoPlaybackException) {
           super.onPlayerError(error)
           ToastUtils.showCommonToast(activity!!.applicationContext, "播放出错!")
           //LogUtils.
       }
       override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) {
           super.onPlayWhenReadyChanged(playWhenReady, reason)
           if (currentVideoHolder == null) return
           if (playWhenReady) {
               currentVideoHolder!!.pauseIv?.visibility = View.GONE
           } else {
               when (reason) {
                   Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST -> {
                       currentVideoHolder!!.pauseIv?.visibility = View.VISIBLE
                   }
               }
           }
       }
       override fun onPlaybackStateChanged(state: Int) {
           super.onPlaybackStateChanged(state)
           when (state) {
               Player.STATE_IDLE -> { //the state when the player is stopped, and when playback failed
                   LogUtils.e("Player.STATE_IDLE:index=" + currentVideoHolder!!.layoutPosition)
                   currentVideoHolder!!.shortVideoCoverView?.visibility = View.VISIBLE
               }
               Player.STATE_BUFFERING -> {                   LogUtils.e("Player.STATE_BUFFERING:startTime=${System.currentTimeMillis()};index=${currentVideoHolder!!.layoutPosition}")
//                        currentVideoHolder!!.shortVideoCoverView?.visibility = View.VISIBLE
                   currentVideoHolder!!.shortVideoLoadingView?.visibility = View.VISIBLE
               }
               Player.STATE_READY -> {                   LogUtils.e("Player.STATE_READY:endTime=${System.currentTimeMillis()};index=${currentVideoHolder!!.layoutPosition}")
                   currentVideoHolder!!.shortVideoCoverView?.visibility = View.GONE
                   currentVideoHolder!!.shortVideoLoadingView?.visibility = View.GONE
               }
               Player.STATE_ENDED -> {
                   LogUtils.e("Player.STATE_ENDED:index=" + currentVideoHolder!!.layoutPosition)
               }
           }
       }
       override fun onIsPlayingChanged(isPlaying: Boolean) {
           super.onIsPlayingChanged(isPlaying)
           if (isPlaying) {// Active playback.
               currentVideoHolder!!.pauseIv?.visibility = View.GONE
           } else {
               // Not playing because playback is paused, ended, suppressed, or the player
               // is buffering, stopped or failed. Check player.getPlayWhenReady,
               // player.getPlaybackState, player.getPlaybackSuppressionReason and
               // player.getPlaybackError for details.
           }
       }
   })
}

exoPlayer?.repeatMode = Player.REPEAT_MODE_ALL 控制循环播放短视频;添加addVideoListener,重写onVideoSizeChanged方法,适配横竖屏的短视频;添加addListener,重写onPlayerError方法处理播放错误,重写onPlayWhenReadyChanged方法,处理playWhenReady的改变,具体reason值,可以点到源码看一下,重写onPlaybackStateChanged方法,处理的状态很多,每个状态值都能从源码里详细看到。

二、播放器的播放控制
由于是横向、纵向滑动播放短视频,必须控制好播放器的暂停以及恢复,无论是Fragment还是Activity,维护一个ExoPlayer就可以了。
纵向滑动时,当前RecyclerView.Adapter 缓存下视图的Holder—currentVideoHolder,拿视图Holder得先拿到当前ViewPager2的RecyclerView:

recyclerView = videoPlayPager!!.getChildAt(0) as RecyclerView

然后就可以缓存当前的视图Holder

currentVideoHolder = recyclerView!!.findViewHolderForLayoutPosition(position) as ShortVideoExoPlayAdapter.RecyclerHolder

把ExoPlayer赋值给当前holderd的playerView的player 就可以了。
具体的播放、暂停时机,我刚开始处理是通过RecyclerView.Adapter的以下两个方法:

    override fun onViewAttachedToWindow(holder: ShortVideoExoPlayAdapter.RecyclerHolder) {
    }
    override fun onViewDetachedFromWindow(holder: ShortVideoExoPlayAdapter.RecyclerHolder) {
    }

后来发现不靠谱,改为通过ViewPager2的registerOnPageChangeCallback 重写onPageSelected方法控制播放和暂停,这样当你慢慢滑动界面,只要没有完成当前Page选择,就不会影响你的播放、暂停逻辑,大致代码简单如下:

videoPlayPager!!.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
            override fun onPageSelected(position: Int) {
                super.onPageSelected(position)
                onHandlePageSelected(position)
            }
        })
/**
* 处理ViewPager选择
*/
private fun onHandlePageSelected(position: Int) {
   if (currentVideoHolder != null) {// 界面当前为活动状态
       stopCurrentStartNextPlay(position)
   } else {
       currentVideoHolder = recyclerView!!.findViewHolderForLayoutPosition(position) as ShortVideoExoPlayAdapter.RecyclerHolder
       currentVideoHolder!!.playerView!!.player = exoPlayer
       exoPlayer?.setMediaItem(currentVideoHolder!!.mediaItem!!)
       exoPlayer?.prepare()
       exoPlayer?.play()
       if (!this.isResumed) {
           exoPlayer?.pause()
       }
   }
   if (position == shortVideoPlayListAdapter!!.data.size - 1) {//加载视频列表
      
   }
  // 视频播放上报
}
/**
 * 停止当前 播放下一个
 */
private fun stopCurrentStartNextPlay(position: Int) {
    exoPlayer?.stop(true)
    currentVideoHolder!!.playerView!!.player = null
    currentVideoHolder = recyclerView!!.findViewHolderForLayoutPosition(position) as ShortVideoExoPlayAdapter.RecyclerHolder
    currentVideoHolder!!.playerView!!.player = exoPlayer
    exoPlayer?.setMediaItem(currentVideoHolder!!.mediaItem!!)
    exoPlayer?.prepare()
    exoPlayer?.play()
}

以上为纵向滑动播放短视频的大概控制,不要担心Anr哦,滑动选择到某一个,先通过currentVideoHolder判断当前是否存在播放,存在则先停止再去播放选择的;这里可能还会遇到跳转定位播放的问题,比如当前数据列表20,点击第12个跳转到播放界面,需要延迟处理一下,因为RecyclerView.Adapter Holder的创造机制,当前Holder 为null呢:

Handler().postDelayed({
     currentVideoHolder = recyclerView!!.findViewHolderForLayoutPosition(position) as ShortVideoExoPlayAdapter.RecyclerHolder
     currentVideoHolder!!.playerView!!.player = exoPlayer
     exoPlayer?.setMediaItem(currentVideoHolder!!.mediaItem!!)
     exoPlayer?.prepare()
     exoPlayer?.play()
 }, 150)

播放、暂停通过属性playWhenReady控制:

override fun onPause() {
    super.onPause()
    exoPlayer?.playWhenReady = false
}
override fun onResume() {
    super.onResume()
    exoPlayer?.playWhenReady = true
  //检查用户关注状态
}
override fun onDestroy() {
    super.onDestroy()
    exoPlayer?.stop(true)
    exoPlayer?.release()
}

横向滑动的时候,当前Fragment的播放器同样用onPause()和onResume()控制,这里如果横向慢慢滑动的时候,容易同时播放两个类别的短视频,所以在onHandlePageSelected方法里增加以下代码,如果Fragment的状态不是resumed,pause一下即可,当然了也可以不是resumed不去播放。

if (!this.isResumed) {
     exoPlayer?.pause()
}

最外层视图如果是Fragment被主界面通过show()、hide()控制,可以重写onHiddenChanged方法控制播放暂停:

override fun onHiddenChanged(hidden: Boolean) {
        super.onHiddenChanged(hidden)
        if (hidden){
            videoTypeFragmentAdapter?.onPausePlayVideo(videoType!!)
        }else{
            videoTypeFragmentAdapter?.onResumePlayVideo(videoType!!)
        }
    }

到这里横向、纵向滑动播放短视频的控制基本完成,阿门!!!

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JavPer

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值