Android中实现视频播放器的途径有两种:
- 使用
VideoView
- 通过
MediaPlayer
+SurfaceView
/TextureView
1. VideoView
VideoView
使用比较简单,配合MediaController
可以达到控制播放、暂停、快进、快退、切换视频、进度条显示等,具体使用在这里不在赘述了。
2. MediaPlayer + SurfaceView / TextureView
实现一个相对完善的视频播放器,可以使用 MediaPlayer
+ SurfaceView
、MediaPlayer
+ TextureView
。
SurfaceView:提供嵌入视图层次结构内部的专用绘图表面,您可以控制此表面的格式,也可以控制其大小,且提供一个辅助线程可以渲染到屏幕中。
TextureView:TextureView
可用于显示内容流,这样的内容流可以例如是视频或OpenGL
场景、可以来自应用程序的过程以及远程过程。TextureView
只能在硬件加速窗口中使用,用软件渲染时,TextureView
将不绘制任何内容。与SurfaceView
不同,TextureView
不会创建单独的窗口,而是充当常规View
。此关键区别允许对TextureView
进行移动,转换,设置动画等。
在这里选择了后者的原因是TextureView
更适合视频流,以及具备普通View
的特性,可以在项目中达到想要的变化效果。
TextureView如何使用
可以通过调用getSurfaceTexture()
来获取TextureView
的SurfaceTexture
。重要的是要知道只有在将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
对象可能驻留的状态。 - 弧形表示驱动对象状态转换的回放控制操作,有两种类型的弧,具有单箭头的弧表示同步方法调用,而具有双箭头的弧表示异步方法调用。
MediaPlayer的生命周期:
-
当新建对象或者在创建之后调用
reset()
时,MediaPlayer
对象处于Idle状态。在调用release()
之后处于End状态。在这两种状态之间是MediaPlayer
对象的生命周期。一旦不再使用MediaPlayer
对象,立即调用release()
释放资源。MediaPlayer
对象处于End状态,就无法再使用它,也无法将其恢复为其他任何状态。 -
某些回放控制操作可能由于各种原因而失败,例如,不支持的音频/视频格式,音频/视频的交错差,分辨率太高,流式传输超时等。在所有这些错误条件下,如果已经
setOnErrorListener()
,则内部播放器引擎将调用用户提供的OnErrorListener.onError()
方法。 -
setDataSource()
用于设置视频资源,只能在Idle状态下调用,其他状态下会抛出IllegalStateException
,调用后处于Initialized状态。 -
MediaPlayer
需要Initialized状态下才能进行准备工作,提供了两个方法prepare()
(同步)、prepareAsync()
(异步)使MediaPlayer
进入Prepared状态,异步调用可以在OnPreparedListener
接口的回调方法onPrepared()
中进行Prepared之后的操作。 -
当
MediaPlayer
在Prepared
状态之后可以调用start()
使它进入Started状态,并开始播放视频,isPlaying()
可以测试MediaPlayer
对象是否处于Started状态。在Started状态时,通过setOnBufferingUpdateListener()
可以在其OnBufferingUpdateListener.onBufferingUpdate()
回调中获取流式传输 音频/视频 时跟踪缓冲状态。 -
播放可以暂停和停止,并且可以调整当前播放位置。
可以通过pause()
暂停播放。当调用pause()
返回时,MediaPlayer
对象将进入Pause状态。注意在播放器引擎中,从Strated 状态到Pause状态的转换是异步的,可能要花一些时间。调用start()
会重新变为Started状态并开始播放。
可以通过stop()
停止播放,Started,Paused,Prepared或PlaybackCompleted状态的MediaPlayer
进入Stopped状态。处于Stopped状态,就无法开始播放,直到调用prepare()
或prepareAsync()
将MediaPlayer
对象重新设置为Prepared状态。 -
调整播放位置可以通过
seekTo()
方法,由于seekTo()
是异步的,实际上查找需要一定时间才能完成,实际的查找位置完成时会走setOnSeekCompleteListener()
的OnSeekComplete.onSeekComplete()
回调。
seekTo()
在Prepared,Paused和PlaybackCompleted 状态下执行仍然会保持当前的状态。
实际的当前播放位置可以通过getCurrentPosition()
获取。 -
当视频播放完成之后默认会走
setOnCompletionListener()
中的OnCompletion.onCompletion()
回调,在回调后处于PlaybackCompleted状态。
如果设置setLooping()
为true
时,会在播放完成后重新变为Started状态并重新播放视频。
在PlaybackCompleted 状态下,调用start()
也可以从音频/视频源的开头重新开始播放。
开始实现视频播放器
这里简单地画了下播放器的流程图(有点丑…)。
项目结构的话借鉴Goolgle官方MVP
模式,能实现视频播放器解耦,功能操作和UI逻辑分离,使用契约类实现Presenter
和Views
,Presenter
中实现各种功能逻辑,Views
负责UI展示。下面是项目的结构:
- 契约类
JVideoViewContract
的实现,分工明确,在Presenter
中实现播放器功能逻辑。
interface JVideoViewContract {
interface Views {
//设置presenter
fun setPresenter(presenter: Presenter)
//设置播放标题
fun setTitle(title:String)
//缓冲中
fun buffering(percent:Int)