最近因项目需求,需要在滚动列表中播放视频。查了半天资料,发现其实现没有想象中的那么难。
原理
众所周知,由于SurfaceView没有UI同步缓冲区,就导致了在列表滚动的时候,正在播放的视频跟不上滚动的步伐;而在SDK 15的时候,谷歌官方提供了新的具有UI同步缓存的视频播放api TextureView。
本文真是基于该新Api实现。
代码实现
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="200dp" android:orientation="vertical" android:focusable="true" >
<TextureView android:id="@+id/video" android:layout_width="match_parent" android:layout_height="wrap_content" />
<ProgressBar android:id="@+id/pb" android:layout_width="wrap_content" android:layout_height="200dp" android:layout_centerInParent="true" /> </RelativeLayout>
|
播放视频的item的布局文件,文件布局很简单,只有一个TextureView和一个ProgressBar,ProgressBar用于给用户展示视频的缓冲情况,TextureView用于处理视频播放。
在adapter里面,除了对TextureView进行UI加载外,还需要MediaPlayer对视频播放提供支持,为了避免卡顿,需要在线程中处理MediaPlayer初始化操作。
private static class VideoPlay implements Runnable { MediaPlayer player; String url; WeakReference<Surface> surface; WeakReference<ProgressBar> pb;
VideoPlay(String url, Surface surface, ProgressBar pb) { this.url = url; this.surface = new WeakReference<>(surface); this.pb = new WeakReference<>(pb); }
void stop() { if (player != null && player.isPlaying()) { currentPosition = player.getCurrentPosition(); player.stop(); player.release(); player = null; } }
@Override public void run() { try { player = new MediaPlayer(); player.setDataSource(url); player.setSurface(this.surface.get()); player.setAudioStreamType(AudioManager.STREAM_VOICE_CALL); player.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(MediaPlayer mp) { if (currentPosition != 0 && currentPosition != mp.getDuration()) { player.seekTo(currentPosition); } player.start(); pb.get().setVisibility(View.INVISIBLE); } }); player.setOnSeekCompleteListener(new MediaPlayer.OnSeekCompleteListener() { @Override public void onSeekComplete(MediaPlayer mp) { currentPosition = 0; } }); player.prepare(); } catch (IOException e) { e.printStackTrace(); } } }
|
利用TextureView播放视频
在列表的Adapter中,需要对TextureView进行初始化处理,在Adapter绑定数据的时候,我们需要实现TextureView.SurfaceTextureListener,然后对TextureView设置生命周期监听,如下所示。
@Override protected void bindData(MyHolder holder, int position, Entity item) { if (!item.isVideo) { Glide.with(getContext()).load(item.url).into(((ImgHolder) holder).img); } else { mUrl = item.url; VideoHolder helper = (VideoHolder) holder; mPb = helper.pb; if (mPlayer == null) { mPb.setVisibility(View.VISIBLE); } helper.video.setVisibility(View.VISIBLE); helper.video.setSurfaceTextureListener(this); } }
|
TextureView生命周期:
@Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { mPlayer = new VideoPlay(mUrl, new Surface(surface), mPb); new Thread(mPlayer).start(); }
@Override public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
}
@Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { if (mPlayer != null) { mPlayer.stop(); } surface.release(); mPlayer = null; return true; }
@Override public void onSurfaceTextureUpdated(SurfaceTexture surface) {
}
|
在TextureView被初始化的时候,我们创建VideoPlay实例,然后开启一条新线程开始播放视频。
当TextureView 被销毁,我们需要调用VideoPlay的stop方法停止播放,并且销毁释放MediaPlayer资源。
最终效果
参考链接
https://developer.android.com/reference/android/view/TextureView.html