Android ListVideo之TextureView实现

撰写于 2017-03-27 修改于 2017-03-27 标签 Listvideo / TextureView / Android视频 / VideoView

转载注明出处:

文章出自 我不只是看客/NotLooker


  产(yi)品(tiao)经(si)理(gou)需要实现瀑布流式的视频播放,就像今日头条那样的。
  今日头条样例:
  这里写图片描述

  猜测大概是RecycleView + VideoView/TextureVeiw 实现瀑布流式video
  经过上一篇博客 Android视频播放 (一)——TextureView+Meidaplayer播放视频分析,VideoView继承与SurfaceView应该不可用,但在查看API之后发现,自从Android N之后 SurfaceView渲染方式发生改变,原文: SurfaceView API

Note: Starting in platform version N, SurfaceView’s window position is updated synchronously with other View rendering. This means that translating and scaling a SurfaceView on screen will not cause rendering artifacts. Such artifacts may occur on previous versions of the platform when its window is positioned asynchronously.

注:自从Android N开始,SurfaceView的窗口位置与其他View渲染同步更新。这意味着SurfaceView的翻转和平移不会导致组件的重新渲染。这种重新渲染组件出现在以前的平台版本上也许会导致窗口位置不同步。

从以上的note得知,在android 7.0上 SurfaceView可以经行翻转和平移操作了。于是在list中使用surfaceView也是可行的,那么android自带的VideoVeiw就可用。


这里使用TextureView进行尝试,VideoView也可实现,但是在MediaController显示会出现问题,
  RecycleView+TextureView+MediaPlayer
  
  Activity.class:
  
本地资源替换为自己的视频

  RVAdapter.class:继承自RecyclerView.Adapter,放出关键函数 onCreateViewHolder和onBindViewHolder,viewholder类;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
MyViewHolder holder = new MyViewHolder(LayoutInflater.from(
context).inflate(R.layout.item, parent,
false));
return holder;
}
@Override
public void onBindViewHolder(final MyViewHolder holder, final int position) {
holder.tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
holder.pb_waiting.setVisibility(View.VISIBLE);
MyApplication.getInstance().getMediaPlayer().reset();
holder.tv.setmMediaPlayer(MyApplication.getInstance().getMediaPlayer());
holder.tv.setPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
holder.pb_waiting.setVisibility(View.GONE);
holder.tv.startPlay();
}
});
holder.tv.setUrl(data.get(position)) ;
}
});
}
class MyViewHolder extends RecyclerView.ViewHolder {
MyTextureView tv;
Button btn;
ProgressBar pb_waiting;
public MyViewHolder(View view) {
super(view);
tv = (MyTextureView) view.findViewById(R.id.textureview);
btn = (Button) view.findViewById(R.id.button);
pb_waiting = (ProgressBar) view.findViewById(R.id.pb_waiting);
}
}

为了使瀑布流同时只能播放一个视频,这里把MediaPlayer提取出来,做一个单例,放到Application中。当item需要播放时,就把MediaPlayer设置给textureview,设置监听, 然后设置播放地址;

MyApplication:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class MyApplication extends Application implements LogUtil {
private static MyApplication instance = null;
private MediaPlayer mediaPlayer;
public static MyApplication getInstance(){
return instance ;
}
@Override
public void onCreate() {
super.onCreate();
instance = this;
}
public MediaPlayer getMediaPlayer() {
if(mediaPlayer == null)
mediaPlayer = new MediaPlayer();
return mediaPlayer;
}
public void setMediaPlayer(MediaPlayer mediaPlayer) {
this.mediaPlayer = mediaPlayer;
}
}

布局文件activity_main.xml:

1
2
3
4
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

布局文件item.xml:

自定义的MyTextureView.class,继承自TextureView,根据需求,封装了MediaPlayer、SurfaceTextureListener和播放暂停等操作;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
public class MyTextureView extends TextureView implements TextureView.SurfaceTextureListener {
private static final String TAG = "MyTextureView";
private String playingUrl = "";
private MediaPlayer mMediaPlayer;
private Surface surface;
private MediaPlayer.OnPreparedListener preparedListener;
private MediaPlayer.OnErrorListener errorListener;
private MediaPlayer.OnCompletionListener completionListener;
private MediaPlayer.OnInfoListener infoListener;
private MediaPlayer.OnSeekCompleteListener seekCompleteListener;
public MyTextureView(Context context) {
super(context);
initView();
}
public MyTextureView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public MyTextureView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView();
}
public void startPlay() {
if (mMediaPlayer != null) {
mMediaPlayer.start();
Log.e(TAG, "startPlay");
} else {
Log.e(TAG, "start Error media is null");
}
}
public void pausePlay() {
if (mMediaPlayer != null) {
mMediaPlayer.pause();
Log.e(TAG, "stopPlay");
} else {
Log.e(TAG, "pause Error media is null");
}
}
public void resetPlay() {
if (mMediaPlayer != null) {
mMediaPlayer.reset();
Log.e(TAG, "resetPlay");
} else {
Log.e(TAG, "reset Error media is null");
}
}
public void destory() {
if (mMediaPlayer != null) {
mMediaPlayer.stop();
mMediaPlayer.release();
mMediaPlayer = null;
}
}
public void setUrl(String path) {
if (!playingUrl.equals(path)) {
mMediaPlayer.reset();
try {
playingUrl = path;
if (path.contains("http")) {
mMediaPlayer.setDataSource(path);
} else {
FileInputStream fis = null;
fis = new FileInputStream(new File(path));
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_RING);
mMediaPlayer.setDataSource(fis.getFD());
}
mMediaPlayer.prepareAsync();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}else {
mMediaPlayer.start();
}
if(preparedListener != null) {
mMediaPlayer.setOnPreparedListener(preparedListener);
}
}
private void initView() {
setSurfaceTextureListener(this);
}
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i2) {
initMediaPlayer(surfaceTexture);
}
/**
* @param surfaceTexture
* @return -1 初始化失败
*/
private int initMediaPlayer(SurfaceTexture surfaceTexture) {
if (surfaceTexture == null)
return 1;
try {
if (mMediaPlayer == null) {
mMediaPlayer = new MediaPlayer();
Log.e(TAG, " initMediaPlayer new media");
}
surface = new Surface(surfaceTexture);
mMediaPlayer.setSurface(surface);
Log.e(TAG, " initMediaPlayer Success");
return 1;
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, " initMediaPlayer-" + e.getMessage());
return -1;
}
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i2) {
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return super.onTouchEvent(event);
}
@Override
public void setOnClickListener(OnClickListener l) {
super.setOnClickListener(l);
}
public void setmMediaPlayer(MediaPlayer mMediaPlayer) {
this.mMediaPlayer = mMediaPlayer;
mMediaPlayer.setSurface(new Surface(this.getSurfaceTexture()));
}
public String getPlayingUrl() {
return playingUrl;
}
public void setPreparedListener(MediaPlayer.OnPreparedListener preparedListener) {
this.preparedListener = preparedListener;
}
public void setErrorListener(MediaPlayer.OnErrorListener errorListener) {
this.errorListener = errorListener;
}
public void setCompletionListener(MediaPlayer.OnCompletionListener completionListener) {
this.completionListener = completionListener;
}
public void setInfoListener(MediaPlayer.OnInfoListener infoListener) {
this.infoListener = infoListener;
}
public void setSeekCompleteListener(MediaPlayer.OnSeekCompleteListener seekCompleteListener) {
this.seekCompleteListener = seekCompleteListener;
}
}

MyTextureView封装了 startPlay,pausePlay等播放相关操作;还有setUrl 统一了播放地址的入口,可以播放本地路径或者http协议的网络url,同时保存了正在播放的url;
这还可以封装更多操作,方便以后功能的扩展。例如 item跳转详情页无缝播放功能的实现;

demo如下


VideoView测试

用RecycleView+VideoView测试list播放,通过查看资料,得知videoview可以实现,更改adapter的代码,放出关键代码 onBindViewHolder()和ViewHolder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Override
public void onBindViewHolder(final MyViewHolder holder, final int position) {
holder.tv.setVideoPath(data.get(position));
holder.tv.setMediaController(new MediaController(context));
holder.tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
holder.btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if( holder.tv.isPlaying())
holder.tv.pause();
else
holder.tv.start();
}
});
}
class MyViewHolder extends RecyclerView.ViewHolder {
VideoView tv;
Button btn;
public MyViewHolder(View view) {
super(view);
tv = (VideoView) view.findViewById(R.id.textureview);
btn = (Button) view.findViewById(R.id.button);
}
}

点击按钮播放,但有个问题,是MediaController是一个Framlayout,点击就会生成,so在item使用中会出现控制器位置错位如下:

而TextureView的话,可以后期自己重写相关单击操作、增加控制器等,so选择TextureView实现listvideo.

本文结束,因为代码较齐全,不提供工程下载

Site by John Doe using Hexo & Random

Hide