音视频Android音视频系列音视频

MediaPlayer+TextureView实现视频播放器

2019-10-19  本文已影响0人  走右边

Android中实现视频播放器的途径有两种:

1. VideoView

VideoView使用比较简单,配合MediaController可以达到控制播放、暂停、快进、快退、切换视频、进度条显示等,具体使用在这里不在赘述了。

2. MediaPlayer + SurfaceView / TextureView

实现一个相对完善的视频播放器,可以使用 MediaPlayer+SurfaceView /MediaPlayer+TextureView

这里为什么有两种方式呢?然后哪种方式更适合用在项目中呢?接下来我们对比一下。

SurfaceViewSurfaceView提供了嵌入视图层次结构内部的专用绘图面板,可以控制此面板的格式,也可以控制其大小,而且它提供了一个辅助线程用以渲染到屏幕中。

TextureViewTextureView可用于显示内容流,这样的内容流可以是视频或OpenGL场景、可以是来自本地数据源或者是远程数据源。TextureView只能在有硬件加速的窗口中使用,当软件渲染时,TextureView将不绘制任何内容。与SurfaceView不同,TextureView不会创建单独的窗口,而是充当常规View。所以允许用户对TextureView进行移动,转换,设置动画等操作。

如果项目需求简单,当然可以选择SurfaceView,但是在这里选择了后者,原因是TextureView更适合视频流,以及具备普通View的特性,可以在项目中达到想要的变化效果。选好了显示的View,接下来看看如何使用。

TextureView如何使用
可以通过调用getSurfaceTexture()来获取TextureViewSurfaceTexture。重要的是只有在将TextureView附加到窗口(并onAttachedToWindow()已被调用)后,SurfaceTexture才可用。因此,在SurfaceTexture可用时通过实现TextureView.SurfaceTextureListener来获取状态的通知。要注意,只有一个生产者可以使用TextureView。例如,如果使用TextureView显示相机预览,则lockCanvas()无法同时在TextureView上绘制。

SurfaceTextureListener的回调。

    override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture?, width: Int, height: Int) {
        //SurfaceTexture缓冲区大小更改时调用(这里的width、height是改变后的画布大小)
    }

    override fun onSurfaceTextureUpdated(surface: SurfaceTexture?) {
       //SurfaceTexture通过更新指定的值 时调用
    }

    override fun onSurfaceTextureDestroyed(surface: SurfaceTexture?): Boolean {
       //当指定的SurfaceTexture对象即将被销毁时调用。返回true,则调用此方法后,
      //表面纹理内不应进行任何渲染。如果返回false,则客户端需要SurfaceTexture.release()。
        return false
    }

    override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
        //当TextureView准备使用SurfaceTexture时调用(这里的width、height是原始画布大小)
    }

MediaPlaye如何使用

MediaPlayer可用于控制音频/视频文件和流的播放。

在官网给出的图中,显示了受支持的播放控制操作驱动的MediaPlayer对象的生命周期和状态。

MediaPlayer的生命周期:

开始实现视频播放器
这里简单地画了下播放器的流程图(有点丑...)。

流程图

项目结构的话借鉴Google官方MVP模式,能实现视频播放器解耦,功能操作和UI逻辑分离,使用契约类实现PresenterViewsPresenter中实现各种功能逻辑,Views负责UI展示。下面是项目的结构:

JVideoView项目结构
    interface JVideoViewContract {
        interface Views {
            //设置presenter
            fun setPresenter(presenter: Presenter)
            //设置播放标题
            fun setTitle(title:String)
            //缓冲中
            fun buffering(percent:Int)
            /**
            * 其他状态下需实现的UI,例如:暂停、加载中、播放结束等
            */
        }

        interface Presenter {
            //实现订阅关系
            fun subscribe()
            //移除订阅关系
            fun unSubscribe()
            //开始播放
            fun startPlay(position: Int = 0)
            //暂停播放
            fun pausePlay()
            /**
            * 其他改变播放状态或者播放器参数的功能,比如初始化播放器、开始播放,滑动快进、音量调节等
            */
        }
    }
    class JVideoState {
         //播放器状态
         class PlayState{
         }

         // 播放模式
         class PlayMode{
         }

         //调节模式
         class PlayAdjust{
         }
    }
     /**
     * 播放器状态
     */
    class PlayState{
            companion object{
                //播放错误
                const val STATE_ERROR = -1
                //播放未开始
                const val STATE_IDLE = 0
                //播放准备中
                const val STATE_PREPARING = 1
                // 播放准备就绪
                const val STATE_PREPARED = 2
                // 开始播放
                const val STATE_START = 3
                //正在播放
                const val STATE_PLAYING = 4
                // 暂停播放
                const val STATE_PAUSED = 5
                //正在缓冲(播放器正在播放时,缓冲区数据不足,进行缓冲,缓冲区数据足够后恢复播放)
                const val STATE_BUFFERING_PLAYING = 6
                // 正在缓冲(播放器正在播放时,缓冲区数据不足,进行缓冲,此时暂停播放器,继续缓冲,缓冲区    
               //数据足够后恢复暂停
                const val STATE_BUFFERING_PAUSED = 7
                // 播放完成
                const val STATE_COMPLETED = 8
            }
        }
class JVideoView : LinearLayout, JVideoViewContract.Views, TextureView.SurfaceTextureListener {
    /*具体逻辑*/
 override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture?, width: Int, height: Int) {
        //SurfaceTexture缓冲区大小改变时
     
    }

    override fun onSurfaceTextureUpdated(surface: SurfaceTexture?) {
        // SurfaceTexture更新时
    }

    override fun onSurfaceTextureDestroyed(surface: SurfaceTexture?): Boolean {
        //幕布销毁时释放资源
        return false
    }

    override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
        //幕布准备完毕,这里是原始画布大小

    }
}
    override fun setPresenter(presenter: JVideoViewContract.Presenter) {
        mPresenter = presenter
    }
    override fun setTitle(title: String) {

    }
    override fun preparedVideo(videoTime: String, max: Int) {
    }

    override fun startVideo(position: Int) {
    }

    override fun buffering(percent: Int) {
    }

    override fun continueVideo() {
    }

    override fun pauseVideo() {
    }

    override fun playing(videoTime: String, position: Int) {
    }

    override fun completedVideo() {
    }

    override fun showLoading(isShow: Boolean, text: String) {
    }
class JVideoViewPresenter(
    private val mContext: Context,
    private val mView: JVideoViewContract.Views,
    private val mVideoRepository: VideoRepository
) : JVideoViewContract.Presenter {

    init {
        mView.setPresenter(this)
    }
/*其他操作*/
}
    override fun subscribe() {
    }

    override fun unSubscribe() {
    }

    override fun startPlay(position: Int) {
         //开始播放
    }

    override fun pausePlay() {
        //暂停播放视频
    }
    override fun continuePlay() {
        //继续暂停播放视频
    }
    
    override fun onPause() {
        //onPause,可在此处暂停播放视频
    }
    
    override fun onResume() {
        //onResume,可在此处恢复播放视频
    }

    override fun getDuration(): Int {
        /*总进度获取*/
    }

    override fun getPosition(): Int {
        /*当前进度获取*/
    }

    override fun getBufferPercent(): Int {
        /*获取缓冲*/
    }

    override fun releasePlay(destroyUi: Boolean) {
        /*结束播放时释放资源,如对一些强引用的置空等*/
    }
    //获取数据源
    private fun loadVideosData() {
        mVideoRepository.getVideos(object : VideoDataSource.LoadVideosCallback {
            override fun onVideosLoaded(videos: List<Video>) {
                /*数据源获取成功*/
            }

            override fun onDataNotAvailable() {
                /*数据源获取失败*/
            }
        })
    }
  1. 设置播放器的幕布以及播放的Url以及相关的音频参数。
    //设置视频的url,本地或者网络
    mPlayer?.setDataSource(video.videoUrl)
    //设置渲染画板
    mPlayer?.setSurface(surface)
    //设置是否循环播放,默认可不写
    isLooping = false
    //设置播放类型、音频参数
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        val attributes = AudioAttributes.Builder()
                  .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                  .setFlags(AudioAttributes.FLAG_LOW_LATENCY)
                  .setUsage(AudioAttributes.USAGE_MEDIA)
                  .setLegacyStreamType(AudioManager.STREAM_MUSIC)
                  .build()
            mPlayer?. setAudioAttributes(attributes)
    } else {
      mPlayer?.setAudioStreamType(AudioManager.STREAM_MUSIC)
    }
    //设置是否保持屏幕常亮
    setScreenOnWhilePlaying(true)
  1. 在设置完MediaPlayer的基本参数之后设置各种监听setOnCompletionListener(播放完毕),setOnSeekCompleteListener(seekTo查找完成),setOnPreparedListener(预加载完成),setOnBufferingUpdateListener(缓冲变化),setOnErrorListener(播放错误),setOnInfoListener(播放器信息),setOnVideoSizeChangedListener(视频尺寸修改),并修改相应的状态表示。
    //播放完成监听
    mPlayer?.setOnCompletionListener {
    }
     //seekTo()调用并实际查找完成之后
    mPlayer?.setOnSeekCompleteListener {
    }
     //预加载监听
    mPlayer?.setOnPreparedListener {
    }
    //相当于缓存进度条
    mPlayer?.setOnBufferingUpdateListener { mp, percent ->
    }
    //播放错误监听
    mPlayer?.setOnErrorListener { mp, what, extra ->
        true
    }
    //播放信息监听
    mPlayer?. setOnInfoListener { mp, what, extra ->
        true
    }
    //播放尺寸
    mPlayer?.setOnVideoSizeChangedListener { mp, width, height ->
        //这里是视频的原始尺寸大小
    }
  1. 幕布准备完毕也就是前面的onSurfaceTextureAvailable()回调中让MediaPlayer进入Prepared状态。在这里用异步的方式prepareAsync,在进入setOnPreparedListener(预加载完成)之前都设为STATE_PREPARING
    //预加载监听
    mPlayer?.setOnPreparedListener {
        mPlayState = PlayState.STATE_PREPARED
        //预加载后播放
        mPlayer?.start()
    }
    //异步的方式装载流媒体文件
     prepareAsync()
  1. 视频播放中会有缓冲,当缓冲区不足时会停止播放并进行缓冲加载,缓冲加载中如果用户未暂停则设为STATE_BUFFERING_PLAYING状态,如果暂停则设为STATE_BUFFERING_PAUSED状态,缓冲完成之后恢复到原来的播放状态。
    //播放信息监听
    mPlayer?. setOnInfoListener { mp, what, extra ->
        when (what) {
            MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START -> {
                // 播放器开始渲染
                mPlayState = PlayState.STATE_PLAYING
            }
            MediaPlayer.MEDIA_INFO_BUFFERING_START -> {
                // MediaPlayer暂时不播放,以缓冲更多的数据
                mPlayState = if (mPlayState == PlayState.STATE_PAUSED || mPlayState == PlayState.STATE_BUFFERING_PAUSED) {
                        PlayState.STATE_BUFFERING_PAUSED
                    } else {
                        PlayState.STATE_BUFFERING_PLAYING
                    }
             }
            MediaPlayer.MEDIA_INFO_BUFFERING_END -> {
                // 填充缓冲区后,MediaPlayer恢复播放/暂停
                if (mPlayState == PlayState.STATE_BUFFERING_PLAYING) {
                    mPlayState = PlayState.STATE_PLAYING
                }
                if (mPlayState == PlayState.STATE_BUFFERING_PAUSED) {
                    mPlayState = PlayState.STATE_PAUSED
                }
            }
            MediaPlayer.MEDIA_INFO_NOT_SEEKABLE -> {
                //无法seekTo
            }
        }
    }

播放器的各种调节控制的实现

    override fun onStopTrackingTouch(seekBar: SeekBar) {
        //seekBar滑动中的回调
        mPlayer?.seekTo(seekBar.progress)
    }
    //进入全屏模式
    mPlayMode = PlayMode.MODE_FULL_SCREEN
    // 隐藏ActionBar、状态栏,并横屏
    (mContext as AppCompatActivity).supportActionBar?.hide()
    mContext.window.setFlags(
        WindowManager.LayoutParams.FLAG_FULLSCREEN,
        WindowManager.LayoutParams.FLAG_FULLSCREEN
    )
    mContext.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
    //设置为充满父布局
    val params = LinearLayout.LayoutParams(
        ViewGroup.LayoutParams.MATCH_PARENT,
        ViewGroup.LayoutParams.MATCH_PARENT
    )
    //隐藏虚拟按键,并且全屏
    mContext.window.decorView.systemUiVisibility =
        View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or View.SYSTEM_UI_FLAG_FULLSCREEN
    //设置播放器为全屏
    (mView as LinearLayout).layoutParams = params
    //亮度调节
    val params = (mContext as AppCompatActivity).window.attributes
    params.screenBrightness = light / 255f
    mContext.window.attributes = params
    // 音量调节
    mAudioManager?.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0)

实现的界面

普通模式 全屏模式

至此,一个简单的有功能的播放器实现了,可能或多或少有待改进的地方,后续仍然会进行优化,欢迎批评指正。

项目地址:Jvideoview
参考文章:

上一篇下一篇

猜你喜欢

热点阅读