本篇参考封装一个视频播放器,原文已经写的非常棒了,本篇加入了个人对其内容的理解。秉承不重复造轮子的良好理念,接下来开始拆解轮子。内容非常多,我都差点放弃写,有耐心的请往下看
github上非常棒的视频相关开源项目有:
最炫的哔哩哔哩的ijkplayer
JiaoziVideoPlayer 基于ijkplayer
GSYVideoPlayer 基于ijkplayer
结合前三篇内容,我们已经对MediaPlayer,SurfaceView,TextureView有了基本了解。相比SurfaceView,TextureView的类View特性,使其更加灵活。因此,我们选择TextureView结合MediaPlayer的方式来自定义视频播放器。只要你静下心来,跟着源码一步步敲,慢慢理解,你就会发现原来自定义视频播放器并不难。
核心思想
类似MVC模式
- NiceVideoPlayer:自定义播放界面,其主要是MediaPlayer和TextureView结合,负责视频数据解析和显示。
- NiceVideoController:控制界面,主要是控制暂停,播放,亮度,音量等
- INiceVideoPlayer:接口,定义了视频播放的相关方法,例如播放,暂停,获取当前播放状态等。
INiceVideoPlayer接口
定义接口前我们先思考下我们需要VideoPlayer实现什么功能,给VideoContronller提供哪些方法。
- 播放状态,根据前面的知识我们知道,MediaPlayer有多种状态,我们需要判断MediaPlayer的状态
1 | /****************************** |
- 播放模式,基本的播放模式有正常模式,全屏模式,以及小窗口模式
1 | /************************* |
- 播放控制
1 | /** |
完整代码
1 | public interface INiceVideoPlayer { |
NiceVideoPlayer
定义播放状态,我们知道MediaPlayer在初始化和reset处于 Idle状态即STATE_IDLE。
播放视频时,MediaPlayer准备就绪(Prepared)后没有马上进入播放状态,中间有一个时间延迟时间段,然后开始渲染图像。所以将Prepared——>“开始渲染”中间这个时间段定义为STATE_PREPARED。
如果是播放网络视频,在播放过程中,缓冲区数据不足时MediaPlayer内部会停留在某一帧画面以进行缓冲。正在缓冲时,MediaPlayer可能是在正在播放也可能是暂停状态,因为在缓冲时如果用户主动点击了暂停,就是处于STATE_BUFFERING_PAUSED,所以缓冲有STATE_BUFFERING_PLAYING和STATE_BUFFERING_PAUSED两种状态,缓冲结束后,恢复播放或暂停。
1 | /**************** |
播放模式普通模式,全屏模式,小屏模式
1 | /************* |
思路
- 让NiceVideoPlayer继承Framelayout,因为我们的内容是嵌套的
- this.addview(Contanier) 创建一个FragmeLayout的容器,用来装载,管理TextureView
- Container界面添加TextureView界面的基础上,再添加Controller界面,这个界面中包含了音量,亮度,控制条等。
1 | public NiceVideoPlayer(@NonNull Context context, @Nullable AttributeSet attrs) { |
播放开始
在start() 方法中,初始化我们的音频,MediaPlayer和TextureView,并建立监听,setSurfaceTextureListener(this)。当onSurfaceTextureAvailable时,MediaPlayer进入准备状态,直到准备就绪便播放。
1 |
|
释放资源
视频播放是很消耗资源的,一定要记得回收
1 |
|
窗口模式变化
切换横竖屏时为了避免Activity重新走生命周期,需要在Manifest.xml的activity标签下添加如下配置:
1 | android:configChanges="orientation|keyboardHidden|screenSize" |
每个Activity里面都有一个android.R.content,它是一个FrameLayout,里面包含了我们setContentView的所有控件。既然它是一个FrameLayout,我们就可以将它作为全屏和小窗口的目标视图。
我们把从当前视图移除的mContainer重新添加到android.R.content中,并且设置成横屏。这个时候还需要注意android.R.content是不包括ActionBar和状态栏的,所以要将Activity设置成全屏模式,同时隐藏ActionBar。
1 | //全屏模式 |
退出全屏也就很简单了,将mContainer从android.R.content中移除,重新添加到当前视图,并恢复ActionBar、清除全屏模式就行了。
1 | //退出全屏 |
进入小窗口播放和退出小窗口的实现原理就和全屏功能一样了,只需要修改它的宽高参数:
1 | //进入小窗口 |
移除小窗口
1 | //退出小窗口 |
注意
当mContainer移除重新添加后,mContainer及其内部的mTextureView和mController都会重绘,mTextureView重绘后,会重新new一个SurfaceTexture,并重新回调onSurfaceTextureAvailable方法,这样mTextureView的数据通道SurfaceTexture发生了变化,但是mMediaPlayer还是持有原先的mSurfaceTexut,所以在切换全屏之前要保存之前的mSufaceTexture,当切换到全屏后重新调用onSurfaceTextureAvailable时,将之前的mSufaceTexture重新设置给mTexutureView。这样就保证了切换时视频播放的无缝衔接。
1 | @Override |
NiceVideoController
这个控制界面绘制有底部进度条,音量控制,亮度控制,播放暂停按钮等。我们通过在NiceVideoPlayer中setController(NiceVideoPlayer)将NiceVideoPlayer传递到Controller中。
1 | /** |
NiceVideoController是一个抽象类,这里我们定义了控制界面要实现的抽象方法,且实现了音量,亮度的方法。
我们另左半屏控制亮度,右半屏控制音量,横向滑动控制进度,进度的变化调用了计时器,间隔一定时间来更新我们的进度信息。
1 | /** |
我们通过Timer和TimerTask每隔一段时间执行一次进度更新,这里介绍了Timer和TimerTask的用法
1 | /** |
TxVideoPlayerController
真正的控制界面类,继承我们的NiceVideoController。在这里我们添加上控制界面。我们可以继承NiceVideoController来自定义想要的控制界面效果。
1 | private void init() { |
在这里我们处理MediaPlayer各种状态中相应的界面显示效果
1 | @Override |
模式变化后,显示相应的界面效果
1 | @Override |
通过CountDownTimer来倒计时来自动消失状态控制栏
1 | /** |
显而易见,在这个类中我们主要做的事就是决定什么情况下显示哪些内容。
NiceVideoPlayerManager
在这个视屏播放器的管理类中,我们通过setCurrentVideoPlayer();来管理NiceVideoPlayer,控制他的销毁,播放。
在NiceVideoPlayer中
1 | @Override |
1 |
|
使用方式为:
在对应视频界面所在的Activity的Manifest.xml中需要添加如下配置:
1 |
|
1 | public class XXXActivity extends AppCompatActivity { |
在布局文件中添加
1 | <XXXX.NiceVideoPlayer |
代码中使用方式
1 | TxVideoPlayerController controller = new TxVideoPlayerController(mContext); |
小例子
注意添加权限
1 | <uses-permission android:name="android.permission.INTERNET" /> |
正常显示方式
1 | public class NormalActivity extends AppCompatActivity { |
布局文件
1 | <?xml version="1.0" encoding="utf-8"?> |
列表使用方式
注意:添加回收
1 | recyclerView.setRecyclerListener(new RecyclerView.RecyclerListener() { |
效果显示
到此为止,整个自定义视频播放器的流程我们已经基本掌握了。想要优化视频播放器可以去看JiaoziVideoPlayer等开源视频播放器的源码。
本篇基于NiceVideoPlayer源码,也可以去我的库中查看加了注释的简化版SimpleNiceVideoPlayer。