├── .gitignore
├── .idea
├── caches
│ ├── build_file_checksums.ser
│ └── gradle_models.ser
├── codeStyles
│ └── Project.xml
├── dictionaries
│ └── zhaoshuang.xml
├── encodings.xml
├── gradle.xml
├── inspectionProfiles
│ └── Project_Default.xml
├── misc.xml
├── modules.xml
├── runConfigurations.xml
└── vcs.xml
├── README.md
├── app
├── .gitignore
├── build.gradle
├── libs
│ ├── armeabi-v7a
│ │ ├── libijkffmpeg.so
│ │ ├── libijkplayer.so
│ │ └── libijksdl.so
│ └── lite-orm-1.9.2.jar
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── zhaoss
│ │ └── videoplayerdemo
│ │ ├── MyApplication.java
│ │ ├── activity
│ │ ├── BaseActivity.java
│ │ ├── MainActivity.java
│ │ └── VideoDetailsActivity.java
│ │ ├── adapter
│ │ ├── MainAdapter.java
│ │ └── VideoDetailsAdapter.java
│ │ ├── bean
│ │ ├── MainVideoBean.java
│ │ └── VideoCacheBean.java
│ │ ├── util
│ │ ├── CacheMediaDataSource.java
│ │ ├── DataUtil.java
│ │ ├── IntentUtil.java
│ │ ├── LogUtil.java
│ │ ├── MediaPlayerTool.java
│ │ ├── RxJavaUtil.java
│ │ ├── StatusBarUtil.java
│ │ ├── Util.java
│ │ ├── VideoCacheDBUtil.java
│ │ └── VideoLRUCacheUtil.java
│ │ └── view
│ │ ├── PlayTextureView.java
│ │ └── VideoTouchView.java
│ └── res
│ ├── drawable-v24
│ └── ic_launcher_foreground.xml
│ ├── drawable
│ ├── ic_launcher_background.xml
│ └── progressbar_with_buffer.xml
│ ├── layout
│ ├── activity_main.xml
│ ├── activity_video_details.xml
│ ├── dialog_loading.xml
│ ├── item_video.xml
│ └── item_video_details.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ ├── ic_launcher_round.png
│ └── video_pause.png
│ ├── mipmap-xhdpi
│ ├── avatar1.jpeg
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ ├── ic_launcher_round.png
│ ├── video_fast_back.png
│ ├── video_fast_forward.png
│ └── video_landscape_cancel.png
│ ├── raw
│ └── demo.mp4
│ └── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 |
--------------------------------------------------------------------------------
/.idea/caches/build_file_checksums.ser:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zhaoss/VideoPlayerDemo/4d60fadd5b35b31cadbf97b4521bbf6fee9bb7fb/.idea/caches/build_file_checksums.ser
--------------------------------------------------------------------------------
/.idea/caches/gradle_models.ser:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zhaoss/VideoPlayerDemo/4d60fadd5b35b31cadbf97b4521bbf6fee9bb7fb/.idea/caches/gradle_models.ser
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | xmlns:android
14 |
15 | ^$
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | xmlns:.*
25 |
26 | ^$
27 |
28 |
29 | BY_NAME
30 |
31 |
32 |
33 |
34 |
35 |
36 | .*:id
37 |
38 | http://schemas.android.com/apk/res/android
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | .*:name
48 |
49 | http://schemas.android.com/apk/res/android
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | name
59 |
60 | ^$
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | style
70 |
71 | ^$
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | .*
81 |
82 | ^$
83 |
84 |
85 | BY_NAME
86 |
87 |
88 |
89 |
90 |
91 |
92 | .*
93 |
94 | http://schemas.android.com/apk/res/android
95 |
96 |
97 | ANDROID_ATTRIBUTE_ORDER
98 |
99 |
100 |
101 |
102 |
103 |
104 | .*
105 |
106 | .*
107 |
108 |
109 | BY_NAME
110 |
111 |
112 |
113 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/.idea/dictionaries/zhaoshuang.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
15 |
16 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Android
10 |
11 |
12 | C/C++
13 |
14 |
15 | ComplianceLintAndroid
16 |
17 |
18 | Control flow issuesJava
19 |
20 |
21 | CorrectnessLintAndroid
22 |
23 |
24 | General
25 |
26 |
27 | GeneralC/C++
28 |
29 |
30 | GradleMigrationKotlin
31 |
32 |
33 | IconsUsabilityLintAndroid
34 |
35 |
36 | Java
37 |
38 |
39 | Kotlin
40 |
41 |
42 | LintAndroid
43 |
44 |
45 | MessagesCorrectnessLintAndroid
46 |
47 |
48 | MigrationKotlin
49 |
50 |
51 | PerformanceJava
52 |
53 |
54 | PerformanceLintAndroid
55 |
56 |
57 | Plugin DevKit
58 |
59 |
60 | Probable bugsJava
61 |
62 |
63 | SecurityJava
64 |
65 |
66 | SecurityLintAndroid
67 |
68 |
69 | Threading issuesJava
70 |
71 |
72 | UsabilityLintAndroid
73 |
74 |
75 | Verbose or redundant code constructsJava
76 |
77 |
78 |
79 |
80 | Android
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### v1.1 增加本地视频播放功能, 视频会自动初始化宽高 不用手动传视频宽高了
2 |
3 | [源码下载](https://github.com/Zhaoss/VideoPlayerDemo)
4 | [演示Demo下载](https://fir.im/VideoPlayerDemo)
5 |
6 | 
7 |
8 | ### 本项目使用播放器是[ijkplay](https://github.com/Bilibili/ijkplayer), 并且进行封装和修改
9 | ```
10 | 主要功能:
11 | 1.重新编辑ijkplay的so库, 使其更精简和支持https协议
12 | 2.自定义MediaDataSource, 使用okhttp重写网络框架, 网络播放更流畅
13 | 3.实现视频缓存, 并且自定义LRUCache算法管理缓存文件
14 | 4.全局使用一个播放器, 实现视频在多个Activity之前无缝切换, 流畅播放
15 | 5.加入更多兼容性判断, 适配绝大数机型
16 | ```
17 | ---
18 |
19 | ### ①导入ijkplay:
20 | 
21 | ```
22 | //需要的权限
23 |
24 |
25 |
26 |
27 | 首先将lib文件夹下的so库粘贴过来, (因为官方自带的so库是不支持https的, 我重新编译的这个so库支持https协议,
28 | 并且使用的是精简版的配置, 网上关于ijkplay编译的流程和配置挺多的, 可以根据自己的需求自定义)
29 |
30 | 然后在module的build中加入 "implementation 'tv.danmaku.ijk.media:ijkplayer-java:0.8.8'"
31 | ```
32 |
33 |
34 | ### ②使用播放器的方法:
35 |
36 | 1.我封装了一个MediaPlayerTool工具类包含的初始化so库和一些回调等等
37 | ```
38 | //通过单例得到媒体播放工具
39 | mMediaPlayerTool = MediaPlayerTool.getInstance();
40 | //这里会自动初始化so库 有些手机会找不到so, 会自动使用系统的播放器
41 | private MediaPlayerTool(){
42 | try {
43 | IjkMediaPlayer.loadLibrariesOnce(null);
44 | IjkMediaPlayer.native_profileBegin("libijkplayer.so");
45 | loadIjkSucc = true;
46 | }catch (UnsatisfiedLinkError e){
47 | e.printStackTrace();
48 | loadIjkSucc = false;
49 | }
50 | }
51 |
52 | //一些生命周期回调
53 | public static abstract class VideoListener {
54 | //视频开始播放
55 | public void onStart(){};
56 | //视频被停止播放
57 | public void onStop(){};
58 | //视频播放完成
59 | public void onCompletion(){};
60 | //视频旋转角度参数初始化完成
61 | public void onRotationInfo(int rotation){};
62 | //播放进度 0-1
63 | public void onPlayProgress(long currentPosition){};
64 | //缓存速度 1-100
65 | public void onBufferProgress(int progress){};
66 | }
67 | ```
68 |
69 | 2.因为我使用的是RecyclerView,所以先找到当前屏幕中 处于可以播放范围的item
70 | ```
71 | //首先循环RecyclerView中所有itemView, 找到在屏幕可见范围内的item
72 | private void checkPlayVideo(){
73 | currentPlayIndex = 0;
74 | videoPositionList.clear();
75 |
76 | int childCount = rv_video.getChildCount();
77 | for (int x = 0; x < childCount; x++) {
78 | View childView = rv_video.getChildAt(x);
79 | //isPlayRange()这个方法很重要
80 | boolean playRange = isPlayRange(childView.findViewById(R.id.rl_video), rv_video);
81 | if(playRange){
82 | int position = rv_video.getChildAdapterPosition(childView);
83 | if(position>=0 && !videoPositionList.contains(position)){
84 | videoPositionList.add(position);
85 | }
86 | }
87 | }
88 | }
89 |
90 | //检查当前item是否在RecyclerView可见的范围内
91 | private boolean isPlayRange(View childView, View parentView){
92 |
93 | if(childView==null || parentView==null){
94 | return false;
95 | }
96 |
97 | int[] childLocal = new int[2];
98 | childView.getLocationOnScreen(childLocal);
99 |
100 | int[] parentLocal = new int[2];
101 | parentView.getLocationOnScreen(parentLocal);
102 |
103 | boolean playRange = childLocal[1]>=parentLocal[1] &&
104 | childLocal[1]<=parentLocal[1]+parentView.getHeight()-childView.getHeight();
105 |
106 | return playRange;
107 | }
108 | ```
109 |
110 | 3.我还封装了一个TextureView, 里面包含一些初始化SurfaceTexture和视频裁剪播放的方法
111 | ```
112 | //视频居中播放
113 | private void setVideoCenter(float viewWidth, float viewHeight, float videoWidth, float videoHeight){
114 |
115 | Matrix matrix = new Matrix();
116 | float sx = viewWidth/videoWidth;
117 | float sy = viewHeight/videoHeight;
118 | float maxScale = Math.max(sx, sy);
119 |
120 | matrix.preTranslate((viewWidth - videoWidth) / 2, (viewHeight - videoHeight) / 2);
121 | matrix.preScale(videoWidth/viewWidth, videoHeight/viewHeight);
122 | matrix.postScale(maxScale, maxScale, viewWidth/2, viewHeight/2);
123 |
124 | mTextureView.setTransform(matrix);
125 | mTextureView.postInvalidate();
126 | }
127 |
128 | //初始化SurfaceTexture
129 | public SurfaceTexture newSurfaceTexture(){
130 |
131 | int[] textures = new int[1];
132 | GLES20.glGenTextures(1, textures, 0);
133 | int texName = textures[0];
134 | SurfaceTexture surfaceTexture = new SurfaceTexture(texName);
135 | surfaceTexture.detachFromGLContext();
136 | return surfaceTexture;
137 | }
138 | ```
139 |
140 | 4.接下来就是播放代码了
141 | ```
142 | private void playVideoByPosition(int position){
143 | //根据传进来的position找到对应的ViewHolder
144 | final MainAdapter.MyViewHolder vh = (MainAdapter.MyViewHolder)
145 | rv_video.findViewHolderForAdapterPosition(position);
146 | if(vh == null){
147 | return ;
148 | }
149 |
150 | currentPlayView = vh.rl_video;
151 |
152 | //初始化一些播放状态, 如进度条,播放按钮,加载框等
153 | //显示正在加载的界面
154 | vh.iv_play_icon.setVisibility(View.GONE);
155 | vh.pb_video.setVisibility(View.VISIBLE);
156 | vh.iv_cover.setVisibility(View.VISIBLE);
157 | vh.tv_play_time.setText("");
158 |
159 | //初始化播放器
160 | mMediaPlayerTool.initMediaPLayer();
161 | mMediaPlayerTool.setVolume(0);
162 |
163 | //设置视频url
164 | String videoUrl = dataList.get(position).getVideoUrl();
165 | mMediaPlayerTool.setDataSource(videoUrl);
166 |
167 | myVideoListener = new MediaPlayerTool.VideoListener() {
168 | @Override
169 | public void onStart() {
170 | //将播放图标和封面隐藏
171 | vh.iv_play_icon.setVisibility(View.GONE);
172 | vh.pb_video.setVisibility(View.GONE);
173 | //防止闪屏
174 | vh.iv_cover.postDelayed(new Runnable() {
175 | @Override
176 | public void run() {
177 | vh.iv_cover.setVisibility(View.GONE);
178 | }
179 | }, 300);
180 | }
181 | @Override
182 | public void onStop() {
183 | //播放停止
184 | vh.pb_video.setVisibility(View.GONE);
185 | vh.iv_cover.setVisibility(View.VISIBLE);
186 | vh.iv_play_icon.setVisibility(View.VISIBLE);
187 | vh.tv_play_time.setText("");
188 | currentPlayView = null;
189 | }
190 | @Override
191 | public void onCompletion() {
192 | //播放下一个
193 | currentPlayIndex++;
194 | playVideoByPosition(-1);
195 | }
196 | @Override
197 | public void onRotationInfo(int rotation) {
198 | //设置旋转播放
199 | vh.playTextureView.setRotation(rotation);
200 | }
201 | @Override
202 | public void onPlayProgress(long currentPosition) {
203 | //显示播放时长
204 | String date = MyUtil.fromMMss(mMediaPlayerTool.getDuration() - currentPosition);
205 | vh.tv_play_time.setText(date);
206 | }
207 | };
208 | mMediaPlayerTool.setVideoListener(myVideoListener);
209 |
210 | //这里重置一下TextureView
211 | vh.playTextureView.resetTextureView();
212 | mMediaPlayerTool.setPlayTextureView(vh.playTextureView);
213 | mMediaPlayerTool.setSurfaceTexture(vh.playTextureView.getSurfaceTexture());
214 | //准备播放
215 | mMediaPlayerTool.prepare();
216 | }
217 | ```
218 |
219 | ### ③重写MediaDataSource, 使用okhttp实现边下边播和视频缓存
220 | 1.一共需要重写3个方法getSize(),close()和readAt(); 先说getSize()
221 | ```
222 | public long getSize() throws IOException {
223 | //开始播放时, 播放器会调用一下getSize()来初始化视频大小, 这时我们就要初始化一条视频播放流
224 | if(networkInPutStream == null) {
225 | initInputStream();
226 | }
227 | return contentLength;
228 | }
229 |
230 | //初始化一个视频流出来, 可能是本地或网络
231 | private void initInputStream() throws IOException{
232 |
233 | File file = checkCache(mMd5);
234 | if(file != null){
235 | //更新一下缓存文件
236 | VideoLRUCacheUtil.updateVideoCacheBean(mMd5, file.getAbsolutePath(), file.length());
237 | //读取的本地缓存文件
238 | isCacheVideo = true;
239 | localVideoFile = file;
240 | //开启一个本地视频流
241 | localStream = new RandomAccessFile(localVideoFile, "rw");
242 | contentLength = file.length();
243 | }else {
244 | //没有缓存 开启一个网络流, 并且开启一个缓存流, 实现视频缓存
245 | isCacheVideo = false;
246 | //开启一个网络视频流
247 | networkInPutStream = openHttpClient(0);
248 | //要写入的本地缓存文件
249 | localVideoFile = VideoLRUCacheUtil.createCacheFile(MyApplication.mContext, mMd5, contentLength);
250 | //要写入的本地缓存视频流
251 | localStream = new RandomAccessFile(localVideoFile, "rw");
252 | }
253 | }
254 | ```
255 | 2.然后是readAt()方法, 也是最重要的一个方法
256 | ```
257 | /**
258 | * @param position 视频流读取进度
259 | * @param buffer 要把读取到的数据存到这个数组
260 | * @param offset 数据开始写入的坐标
261 | * @param size 本次一共读取数据的大小
262 | * @throws IOException
263 | */
264 | //记录当前读取流的索引
265 | long mPosition = 0;
266 | @Override
267 | public int readAt(long position, byte[] buffer, int offset, int size) throws IOException {
268 |
269 | if(position>=contentLength || localStream==null){
270 | return -1;
271 | }
272 |
273 | //是否将此字节缓存到本地
274 | boolean isWriteVideo = syncInputStream(position);
275 |
276 | //读取的流的长度不能大于contentLength
277 | if (position+size > contentLength) {
278 | size -= position+size-contentLength;
279 | }
280 |
281 | //读取指定大小的视频数据
282 | byte[] bytes;
283 | if(isCacheVideo){
284 | //从本地读取
285 | bytes = readByteBySize(localStream, size);
286 | }else{
287 | //从网络读取
288 | bytes = readByteBySize(networkInPutStream, size);
289 | }
290 | if(bytes != null) {
291 | //写入到播放器的数组中
292 | System.arraycopy(bytes, 0, buffer, offset, size);
293 | if (isWriteVideo && !isCacheVideo) {
294 | //将视频缓存到本地
295 | localStream.write(bytes);
296 | }
297 | //记录数据流读取到哪步了
298 | mPosition += size;
299 | }
300 |
301 | return size;
302 | }
303 |
304 | /**
305 | * 从inputStream里读取size大小的数据
306 | */
307 | private byte[] readByteBySize(InputStream inputStream, int size) throws IOException{
308 |
309 | ByteArrayOutputStream out = new ByteArrayOutputStream();
310 |
311 | byte[] buf = new byte[size];
312 | int len;
313 | while ((len = inputStream.read(buf)) != -1) {
314 | out.write(buf, 0, len);
315 | if (out.size() == size) {
316 | return out.toByteArray();
317 | } else {
318 | buf = new byte[size - out.size()];
319 | }
320 | }
321 | return null;
322 | }
323 |
324 | /**
325 | * 删除file一部分字节, 从position到file.size
326 | */
327 | private void deleteFileByPosition(long position) throws IOException{
328 |
329 | FileInputStream in = new FileInputStream(localVideoFile);
330 |
331 | File tempFile = VideoLRUCacheUtil.createTempFile(MyApplication.mContext);
332 | FileOutputStream out = new FileOutputStream(tempFile);
333 |
334 | byte[] buf = new byte[8192];
335 | int len;
336 | while ((len = in.read(buf)) != -1) {
337 | if(position <= len){
338 | out.write(buf, 0, (int) position);
339 | out.close();
340 |
341 | in.close();
342 | localVideoFile.delete();
343 | tempFile.renameTo(localVideoFile);
344 | localStream = new RandomAccessFile(localVideoFile, "rw");
345 | return ;
346 | }else{
347 | position -= len;
348 | out.write(buf, 0, len);
349 | }
350 | }
351 | tempFile.delete();
352 | }
353 | ```
354 | 3.主要说一下syncInputStream(), 因为有可能出现一种情况,
355 | 比如一个视频长度100, 播放器首先读取视频的1到10之间的数据, 然后在读取90到100之间的数据, 然后在从1播放到100;
356 | 所以这时我们需要同步视频流, 和播放进度保持一致这时就需要重新开启一个IO流(如果在读取本地缓存时可以直接使用RandomAccessFile.seek()方法跳转)
357 | ```
358 | //同步数据流
359 | private boolean syncInputStream(long position) throws IOException{
360 | boolean isWriteVideo = true;
361 | //判断两次读取数据是否连续
362 | if(mPosition != position){
363 | if(isCacheVideo){
364 | //如果是本地缓存, 直接跳转到该索引
365 | localStream.seek(position);
366 | }else{
367 | if(mPosition > position){
368 | //同步本地缓存流
369 | localStream.close();
370 | deleteFileByPosition(position);
371 | localStream.seek(position);
372 | }else{
373 | isWriteVideo = false;
374 | }
375 | networkInPutStream.close();
376 | //重新开启一个网络流
377 | networkInPutStream = openHttpClient((int) position);
378 | }
379 | mPosition = position;
380 | }
381 | return isWriteVideo;
382 | }
383 | ```
384 | 4.最后一个是close()方法, 主要播放停止后释放一些资源
385 | ```
386 | public void close() throws IOException {
387 | if(networkInPutStream != null){
388 | networkInPutStream.close();
389 | networkInPutStream = null;
390 | }
391 | if(localStream != null){
392 | localStream.close();
393 | localStream = null;
394 | }
395 | if(localVideoFile.length()!=contentLength){
396 | localVideoFile.delete();
397 | }
398 | }
399 | ```
400 |
401 | ### ④视频缓存和LRUCache管理
402 | 1.首先创建缓存文件, 在刚才的MediaDataSource.getSize()方法里有一句代码
403 | ```
404 | localVideoFile = VideoLRUCacheUtil.createCacheFile(MyApplication.mContext, mMd5, contentLength);
405 |
406 | public static File createCacheFile(Context context, String md5, long fileSize){
407 | //创建一个视频缓存文件, 在data/data目录下
408 | File filesDir = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
409 |
410 | File cacheFile = new File(filesDir, md5);
411 | if(!cacheFile.exists()) {
412 | cacheFile.createNewFile();
413 | }
414 | //将缓存信息存到数据库
415 | VideoLRUCacheUtil.updateVideoCacheBean(md5, cacheFile.getAbsolutePath(), fileSize);
416 | return cacheFile;
417 | }
418 | ```
419 | 2.然后是读取缓存文件, 在刚才的MediaDataSource.getSize()方法里还有一句代码
420 | ```
421 | //检查本地是否有缓存, 2步确认, 数据库中是否存在, 本地文件是否存在
422 | private File checkCache(String md5){
423 | //查询数据库
424 | VideoCacheBean bean = VideoCacheDBUtil.query(md5);
425 | if(bean != null){
426 | File file = new File(bean.getVideoPath());
427 | if(file.exists()){
428 | return file;
429 | }
430 | }
431 | return null;
432 | }
433 | ```
434 |
435 | 3.LRUCache的实现
436 | ```
437 | //清理超过大小和存储时间的视频缓存文件
438 | VideoLRUCacheUtil.checkCacheSize(mContext);
439 |
440 | public static void checkCacheSize(Context context){
441 |
442 | ArrayList videoCacheList = VideoCacheDBUtil.query();
443 |
444 | //检查一下数据库里面的缓存文件是否存在
445 | for (VideoCacheBean bean : videoCacheList){
446 | if(bean.getFileSize() == 0){
447 | File videoFile = new File(bean.getVideoPath());
448 | //如果文件不存在或者文件大小不匹配, 那么删除
449 | if(!videoFile.exists() && videoFile.length()!=bean.getFileSize()){
450 | VideoCacheDBUtil.delete(bean);
451 | }
452 | }
453 | }
454 |
455 | long currentSize = 0;
456 | long currentTime = System.currentTimeMillis();
457 | for (VideoCacheBean bean : videoCacheList){
458 | //太久远的文件删除
459 | if(currentTime-bean.getPlayTime() > maxCacheTime){
460 | VideoCacheDBUtil.delete(bean);
461 | }else {
462 | //大于存储空间的删除
463 | if (currentSize + bean.getFileSize() > maxDirSize) {
464 | VideoCacheDBUtil.delete(bean);
465 | } else {
466 | currentSize += bean.getFileSize();
467 | }
468 | }
469 | }
470 |
471 | //删除不符合规则的缓存
472 | deleteDirRoom(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), VideoCacheDBUtil.query());
473 | }
474 |
475 | //更新缓存文件的播放次数和最后播放时间
476 | public static void updateVideoCacheBean(String md5, String videoPath, long fileSize){
477 |
478 | VideoCacheBean videoCacheBean = VideoCacheDBUtil.query(md5);
479 | if(videoCacheBean == null){
480 | videoCacheBean = new VideoCacheBean();
481 | videoCacheBean.setKey(md5);
482 | videoCacheBean.setVideoPath(videoPath);
483 | videoCacheBean.setFileSize(fileSize);
484 | }
485 | videoCacheBean.setPlayCount(videoCacheBean.getPlayCount()+1);
486 | videoCacheBean.setPlayTime(System.currentTimeMillis());
487 |
488 | VideoCacheDBUtil.save(videoCacheBean);
489 | }
490 | ```
491 |
492 | ### ⑤关于多个Activity同步播放状态, 无缝切换
493 | 1.首先在跳转时, 通知被覆盖的activity不关闭播放器
494 | ```
495 | //首先跳转时通知一下activity
496 | mainActivity.jumpNotCloseMediaPlay(position);
497 |
498 | //然后在onPause里
499 | protected void onPause() {
500 | super.onPause();
501 | //如果要跳转播放, 那么不关闭播放器
502 | if (videoPositionList.size()>currentPlayIndex && jumpVideoPosition==videoPositionList.get(currentPlayIndex)) {
503 | ...这里就不关闭播放器
504 | }else{
505 | //如果不要求跳转播放, 那么就重置播放器
506 | mMediaPlayerTool.reset();
507 | }
508 | }
509 | ```
510 | 2.然后在新页面初始化播放器
511 | ```
512 | private void playVideoByPosition(int position){
513 | ......一切初始化代码照旧(注意不要重置播放器), 这里省略不提
514 |
515 | //把播放器当前绑定的SurfaceTexture取出起来, 设置给当前界面的TextureView
516 | vh.playTextureView.resetTextureView(mMediaPlayerTool.getAvailableSurfaceTexture());
517 | mMediaPlayerTool.setPlayTextureView(vh.playTextureView);
518 | //最后刷新一下view
519 | vh.playTextureView.postInvalidate();
520 | }
521 | ```
522 | ### 至此代码讲解完毕, 亲测在4g网络下视频初始化速度毫秒级, 并且在低性能手机下, 页面来回切换无卡顿.
523 | ### 大家如果有不解, 可以查看源码了解更多, 有bug或优化思路 也可以提[issues](https://github.com/Zhaoss/VideoPlayerDemo/issues)
524 |
525 | ---
526 |
527 | # MIT License
528 | Copyright (c) 2018 Zhaoss (838198688@qq.com)
529 |
530 | Permission is hereby granted, free of charge, to any person obtaining a copy
531 | of this software and associated documentation files (the "Software"), to deal
532 | in the Software without restriction, including without limitation the rights
533 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
534 | copies of the Software, and to permit persons to whom the Software is
535 | furnished to do so, subject to the following conditions:
536 |
537 | The above copyright notice and this permission notice shall be included in all
538 | copies or substantial portions of the Software.
539 |
540 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
541 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
542 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
543 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
544 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
545 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
546 | SOFTWARE.
547 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 28
5 | defaultConfig {
6 | applicationId "com.zhaoss.videoplayerdemo"
7 | minSdkVersion 21
8 | targetSdkVersion 28
9 | versionCode 1
10 | versionName "1.0"
11 | }
12 | buildTypes {
13 | release {
14 | minifyEnabled false
15 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
16 | }
17 | }
18 |
19 | sourceSets {
20 | main {
21 | //设置so文件夹目录
22 | jniLibs.srcDirs = ['libs']
23 | }
24 |
25 | release { java.srcDirs = ['src/release/java'] }
26 | }
27 | compileOptions {
28 | sourceCompatibility JavaVersion.VERSION_1_8
29 | targetCompatibility JavaVersion.VERSION_1_8
30 | }
31 | }
32 |
33 | dependencies {
34 | implementation fileTree(dir: 'libs', include: ['*.jar'])
35 | implementation 'com.android.support:appcompat-v7:28.0.0'
36 | implementation 'com.android.support.constraint:constraint-layout:1.1.3'
37 |
38 | implementation 'tv.danmaku.ijk.media:ijkplayer-java:0.8.8'
39 | implementation files('libs/lite-orm-1.9.2.jar')
40 | implementation 'io.reactivex.rxjava2:rxjava:2.2.8'
41 | implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
42 | implementation "com.android.support:recyclerview-v7:28.0.0"
43 | implementation 'com.yanzhenjie:permission:1.1.2'
44 |
45 | implementation 'com.squareup.okio:okio:1.17.2'
46 | implementation 'com.squareup.okhttp3:okhttp:3.14.1'
47 | implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
48 |
49 | implementation "com.github.bumptech.glide:glide:4.9.0"
50 | }
51 |
--------------------------------------------------------------------------------
/app/libs/armeabi-v7a/libijkffmpeg.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zhaoss/VideoPlayerDemo/4d60fadd5b35b31cadbf97b4521bbf6fee9bb7fb/app/libs/armeabi-v7a/libijkffmpeg.so
--------------------------------------------------------------------------------
/app/libs/armeabi-v7a/libijkplayer.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zhaoss/VideoPlayerDemo/4d60fadd5b35b31cadbf97b4521bbf6fee9bb7fb/app/libs/armeabi-v7a/libijkplayer.so
--------------------------------------------------------------------------------
/app/libs/armeabi-v7a/libijksdl.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zhaoss/VideoPlayerDemo/4d60fadd5b35b31cadbf97b4521bbf6fee9bb7fb/app/libs/armeabi-v7a/libijksdl.so
--------------------------------------------------------------------------------
/app/libs/lite-orm-1.9.2.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zhaoss/VideoPlayerDemo/4d60fadd5b35b31cadbf97b4521bbf6fee9bb7fb/app/libs/lite-orm-1.9.2.jar
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zhaoss/videoplayerdemo/MyApplication.java:
--------------------------------------------------------------------------------
1 | package com.zhaoss.videoplayerdemo;
2 |
3 | import android.app.Application;
4 |
5 | import com.zhaoss.videoplayerdemo.util.VideoLRUCacheUtil;
6 |
7 | /**
8 | * Created by zhaoshuang on 2018/11/1.
9 | */
10 |
11 | public class MyApplication extends Application {
12 |
13 | public static MyApplication mContext;
14 |
15 | @Override
16 | public void onCreate() {
17 | super.onCreate();
18 |
19 | this.mContext = this;
20 |
21 | //清理超过大小和存储时间的视频缓存文件
22 | VideoLRUCacheUtil.checkCacheSize();
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zhaoss/videoplayerdemo/activity/BaseActivity.java:
--------------------------------------------------------------------------------
1 | package com.zhaoss.videoplayerdemo.activity;
2 |
3 | import android.app.Activity;
4 | import android.graphics.Color;
5 | import android.os.Bundle;
6 | import android.support.annotation.Nullable;
7 | import android.support.v4.content.ContextCompat;
8 | import android.support.v7.app.AlertDialog;
9 | import android.view.View;
10 |
11 | import com.zhaoss.videoplayerdemo.R;
12 | import com.zhaoss.videoplayerdemo.util.StatusBarUtil;
13 |
14 | /**
15 | * Created by zhaoshuang on 2018/6/1.
16 | */
17 |
18 | public class BaseActivity extends Activity {
19 |
20 | protected BaseActivity mContext;
21 | private AlertDialog dialog;
22 |
23 | @Override
24 | protected void onCreate(@Nullable Bundle savedInstanceState) {
25 | super.onCreate(savedInstanceState);
26 | this.mContext = this;
27 |
28 | StatusBarUtil.setColor(mContext, ContextCompat.getColor(mContext, R.color.my_black), false);
29 | }
30 |
31 | @Override
32 | protected void onStart() {
33 | super.onStart();
34 |
35 | StatusBarUtil.setColor(this, Color.WHITE, true);
36 | }
37 |
38 | protected void showProDialog(){
39 |
40 | dismissDialog();
41 |
42 | AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
43 | builder.setCancelable(false);
44 | View view = View.inflate(mContext, R.layout.dialog_loading, null);
45 | builder.setView(view);
46 | dialog = builder.create();
47 | dialog.show();
48 | }
49 |
50 | protected void dismissDialog(){
51 |
52 | if(dialog != null){
53 | dialog.dismiss();
54 | dialog = null;
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zhaoss/videoplayerdemo/activity/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.zhaoss.videoplayerdemo.activity;
2 |
3 | import android.Manifest;
4 | import android.graphics.Rect;
5 | import android.os.Bundle;
6 | import android.support.annotation.NonNull;
7 | import android.support.v7.widget.LinearLayoutManager;
8 | import android.support.v7.widget.RecyclerView;
9 | import android.view.View;
10 |
11 | import com.yanzhenjie.permission.AndPermission;
12 | import com.yanzhenjie.permission.PermissionListener;
13 | import com.zhaoss.videoplayerdemo.R;
14 | import com.zhaoss.videoplayerdemo.adapter.MainAdapter;
15 | import com.zhaoss.videoplayerdemo.bean.MainVideoBean;
16 | import com.zhaoss.videoplayerdemo.util.DataUtil;
17 | import com.zhaoss.videoplayerdemo.util.MediaPlayerTool;
18 | import com.zhaoss.videoplayerdemo.util.Util;
19 |
20 | import java.util.ArrayList;
21 | import java.util.List;
22 |
23 | public class MainActivity extends BaseActivity {
24 |
25 | private RecyclerView rv_video;
26 | private MediaPlayerTool mMediaPlayerTool;
27 | private ArrayList dataList;
28 |
29 | @Override
30 | protected void onCreate(Bundle savedInstanceState) {
31 | super.onCreate(savedInstanceState);
32 | setContentView(R.layout.activity_main);
33 |
34 | rv_video = findViewById(R.id.rv_video);
35 |
36 | rv_video.setLayoutManager(new LinearLayoutManager(mContext));
37 | dataList = DataUtil.createData();
38 | rv_video.setAdapter(new MainAdapter(this, dataList));
39 |
40 | rv_video.addItemDecoration(new RecyclerView.ItemDecoration(){
41 | @Override
42 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
43 |
44 | int position = parent.getChildAdapterPosition(view);
45 | if (position != 0) {
46 | outRect.top = (int) getResources().getDimension(R.dimen.activity_margin2);
47 | }
48 | }
49 | });
50 |
51 | rv_video.addOnScrollListener(new RecyclerView.OnScrollListener() {
52 | @Override
53 | public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
54 | if(currentPlayView!=null){
55 | boolean playRange = isPlayRange(currentPlayView, recyclerView);
56 | if(!playRange){
57 | mMediaPlayerTool.reset();
58 | }
59 | }
60 | }
61 | @Override
62 | public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
63 | if (newState == RecyclerView.SCROLL_STATE_IDLE) {
64 | //检测播放视频
65 | checkPlayVideo();
66 | if(currentPlayView == null){
67 | playVideoByPosition(-1);
68 | }
69 | }
70 | }
71 | });
72 |
73 | mMediaPlayerTool = MediaPlayerTool.getInstance();
74 |
75 | AndPermission.with(mContext).permission(Manifest.permission.WRITE_EXTERNAL_STORAGE).callback(new PermissionListener() {
76 | @Override
77 | public void onSucceed(int requestCode, @NonNull List grantPermissions) {
78 | }
79 | @Override
80 | public void onFailed(int requestCode, @NonNull List deniedPermissions) {
81 |
82 | }
83 | }).start();
84 | }
85 |
86 | boolean isFirst = true;
87 | @Override
88 | public void onWindowFocusChanged(boolean hasFocus) {
89 | super.onWindowFocusChanged(hasFocus);
90 | if(isFirst){
91 | isFirst = false;
92 | refreshVideo();
93 | }
94 | }
95 |
96 | //检测是否播放视频
97 | private void checkPlayVideo(){
98 |
99 | currentPlayIndex = 0;
100 | videoPositionList.clear();
101 |
102 | int childCount = rv_video.getChildCount();
103 | for (int x = 0; x < childCount; x++) {
104 | View childView = rv_video.getChildAt(x);
105 | boolean playRange = isPlayRange(childView.findViewById(R.id.rl_video), rv_video);
106 | if(playRange){
107 | int position = rv_video.getChildAdapterPosition(childView);
108 | if(position>=0 && !videoPositionList.contains(position)){
109 | videoPositionList.add(position);
110 | }
111 | }
112 | }
113 | }
114 |
115 | //检查子view是否在父view显示布局里面
116 | private boolean isPlayRange(View childView, View parentView){
117 |
118 | if(childView==null || parentView==null){
119 | return false;
120 | }
121 |
122 | int[] childLocal = new int[2];
123 | childView.getLocationOnScreen(childLocal);
124 |
125 | int[] parentLocal = new int[2];
126 | parentView.getLocationOnScreen(parentLocal);
127 |
128 | boolean playRange = childLocal[1]>=parentLocal[1] &&
129 | childLocal[1]<=parentLocal[1]+parentView.getHeight()-childView.getHeight();
130 |
131 | return playRange;
132 | }
133 |
134 | MediaPlayerTool.VideoListener myVideoListener;
135 | //当前播放的视频角标
136 | int currentPlayIndex;
137 | //可以播放的视频集合
138 | ArrayList videoPositionList = new ArrayList<>();
139 | View currentPlayView;
140 | /**
141 | * 播放视频
142 | * @param resumePosition 是否继续播放 否则可以传-1
143 | */
144 | private void playVideoByPosition(int resumePosition){
145 |
146 | boolean isResumePlay = resumePosition >= 0;
147 |
148 | if(!isResumePlay && (videoPositionList.size()==0 || mMediaPlayerTool ==null)){
149 | return ;
150 | }
151 |
152 | if(!isResumePlay){
153 | //一定要先重置播放器
154 | mMediaPlayerTool.reset();
155 | }
156 |
157 | int playPosition = 0;
158 | if(isResumePlay){
159 | playPosition = resumePosition;
160 | }else{
161 | if(currentPlayIndex >= videoPositionList.size()){
162 | currentPlayIndex = 0;
163 | }
164 | playPosition = videoPositionList.get(currentPlayIndex);
165 | }
166 |
167 | //根据传进来的position找到对应的ViewHolder
168 | final MainAdapter.MyViewHolder vh = (MainAdapter.MyViewHolder) rv_video.findViewHolderForAdapterPosition(playPosition);
169 | if(vh == null){
170 | return ;
171 | }
172 |
173 | currentPlayView = vh.rl_video;
174 |
175 | //初始化一些播放状态, 如进度条,播放按钮,加载框等
176 | if(isResumePlay){
177 | vh.pb_video.setVisibility(View.GONE);
178 | vh.iv_play_icon.setVisibility(View.GONE);
179 | vh.iv_cover.setVisibility(View.GONE);
180 | vh.playTextureView.setVideoSize(mMediaPlayerTool.getVideoWidth(), mMediaPlayerTool.getVideoHeight());
181 | }else{
182 | //显示正在加载的界面
183 | vh.iv_play_icon.setVisibility(View.GONE);
184 | vh.pb_video.setVisibility(View.VISIBLE);
185 | vh.iv_cover.setVisibility(View.VISIBLE);
186 | vh.tv_play_time.setText("");
187 |
188 | mMediaPlayerTool.initMediaPLayer();
189 |
190 | String videoUrl = dataList.get(playPosition).getVideoUrl();
191 | mMediaPlayerTool.setDataSource(videoUrl);
192 | }
193 |
194 | mMediaPlayerTool.setVolume(0);
195 | myVideoListener = new MediaPlayerTool.VideoListener() {
196 | @Override
197 | public void onStart() {
198 | vh.pb_video.setVisibility(View.GONE);
199 | //防止闪屏
200 | vh.iv_cover.postDelayed(new Runnable() {
201 | @Override
202 | public void run() {
203 | vh.iv_cover.setVisibility(View.GONE);
204 | }
205 | }, 200);
206 | vh.playTextureView.setVideoSize(mMediaPlayerTool.getVideoWidth(), mMediaPlayerTool.getVideoHeight());
207 | vh.iv_play_icon.setVisibility(View.GONE);
208 | }
209 | @Override
210 | public void onStop() {
211 | vh.pb_video.setVisibility(View.GONE);
212 | vh.iv_cover.setVisibility(View.VISIBLE);
213 | vh.iv_play_icon.setVisibility(View.VISIBLE);
214 | vh.tv_play_time.setText("");
215 | currentPlayView = null;
216 | }
217 | @Override
218 | public void onCompletion() {
219 | currentPlayIndex++;
220 | playVideoByPosition(-1);
221 | }
222 | @Override
223 | public void onRotationInfo(int rotation) {
224 | vh.playTextureView.setRotation(rotation);
225 | }
226 | @Override
227 | public void onPlayProgress(long currentPosition) {
228 | String date = Util.fromMMss(mMediaPlayerTool.getDuration() - currentPosition);
229 | vh.tv_play_time.setText(date);
230 | }
231 | };
232 | mMediaPlayerTool.setVideoListener(myVideoListener);
233 |
234 | if(isResumePlay){
235 | //把播放器当前绑定的SurfaceTexture取出起来, 设置给当前界面的TextureView
236 | vh.playTextureView.resetTextureView(mMediaPlayerTool.getAvailableSurfaceTexture());
237 | mMediaPlayerTool.setPlayTextureView(vh.playTextureView);
238 | vh.playTextureView.postInvalidate();
239 | }else {
240 | vh.playTextureView.resetTextureView();
241 | mMediaPlayerTool.setPlayTextureView(vh.playTextureView);
242 | mMediaPlayerTool.setSurfaceTexture(vh.playTextureView.getSurfaceTexture());
243 | mMediaPlayerTool.prepare();
244 | }
245 | }
246 |
247 | //跳转页面时是否关闭播放器
248 | private int jumpVideoPosition = -1;
249 | public void jumpNotCloseMediaPlay(int position){
250 | jumpVideoPosition = position;
251 | }
252 |
253 | public void refreshVideo(){
254 |
255 | if(mMediaPlayerTool !=null) {
256 | mMediaPlayerTool.reset();
257 | checkPlayVideo();
258 | playVideoByPosition(-1);
259 | }
260 | }
261 |
262 | @Override
263 | protected void onResume() {
264 | super.onResume();
265 |
266 | //检测是否继续播放视频
267 | if(jumpVideoPosition!=-1 &&
268 | (videoPositionList.size()>currentPlayIndex && jumpVideoPosition==videoPositionList.get(currentPlayIndex))
269 | && mMediaPlayerTool!=null && mMediaPlayerTool.isPlaying()){
270 | playVideoByPosition(jumpVideoPosition);
271 | }else{
272 | refreshVideo();
273 | }
274 | jumpVideoPosition = -1;
275 |
276 | }
277 |
278 | @Override
279 | protected void onPause() {
280 | super.onPause();
281 |
282 | if(mMediaPlayerTool != null) {
283 | //如果要跳转播放, 那么不关闭播放器
284 | if (videoPositionList.size()>currentPlayIndex && jumpVideoPosition==videoPositionList.get(currentPlayIndex)) {
285 | rv_video.postDelayed(new Runnable() {
286 | @Override
287 | public void run() {
288 | if (myVideoListener != null) {
289 | myVideoListener.onStop();
290 | }
291 | }
292 | }, 300);
293 | } else {
294 | mMediaPlayerTool.reset();
295 | if (!videoPositionList.contains(jumpVideoPosition)) {
296 | videoPositionList.add(jumpVideoPosition);
297 | }
298 | currentPlayIndex = videoPositionList.indexOf(jumpVideoPosition);
299 | }
300 | }
301 | }
302 | }
303 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zhaoss/videoplayerdemo/activity/VideoDetailsActivity.java:
--------------------------------------------------------------------------------
1 | package com.zhaoss.videoplayerdemo.activity;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.support.annotation.Nullable;
6 | import android.support.v7.widget.LinearLayoutManager;
7 | import android.support.v7.widget.PagerSnapHelper;
8 | import android.support.v7.widget.RecyclerView;
9 | import android.view.View;
10 | import android.view.ViewGroup;
11 | import android.view.WindowManager;
12 | import android.widget.ImageView;
13 |
14 | import com.zhaoss.videoplayerdemo.R;
15 | import com.zhaoss.videoplayerdemo.adapter.VideoDetailsAdapter;
16 | import com.zhaoss.videoplayerdemo.bean.MainVideoBean;
17 | import com.zhaoss.videoplayerdemo.util.IntentUtil;
18 | import com.zhaoss.videoplayerdemo.util.MediaPlayerTool;
19 | import com.zhaoss.videoplayerdemo.util.Util;
20 | import com.zhaoss.videoplayerdemo.util.StatusBarUtil;
21 | import com.zhaoss.videoplayerdemo.view.VideoTouchView;
22 |
23 | import java.util.ArrayList;
24 |
25 | /**
26 | * Created by zhaoshuang on 2018/11/12.
27 | */
28 |
29 | public class VideoDetailsActivity extends BaseActivity {
30 |
31 | private RecyclerView rv_video_detail;
32 | private LinearLayoutManager linearLayoutManager;
33 | private PagerSnapHelper pagerSnapHelper;
34 | private MediaPlayerTool mMediaPlayerTool;
35 | private ArrayList dataList;
36 | private ImageView iv_close;
37 | private int playPosition;
38 |
39 | @Override
40 | protected void onCreate(@Nullable Bundle savedInstanceState) {
41 | super.onCreate(savedInstanceState);
42 | getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
43 | setContentView(R.layout.activity_video_details);
44 |
45 | initIntent();
46 | initUI();
47 |
48 | mMediaPlayerTool = MediaPlayerTool.getInstance();
49 | rv_video_detail.post(new Runnable() {
50 | @Override
51 | public void run() {
52 | rv_video_detail.scrollToPosition(playPosition);
53 | rv_video_detail.post(new Runnable() {
54 | @Override
55 | public void run() {
56 | playVisibleVideo(mMediaPlayerTool.isPlaying());
57 | }
58 | });
59 | }
60 | });
61 | }
62 |
63 | private void initIntent() {
64 |
65 | Intent intent = getIntent();
66 | playPosition = intent.getIntExtra(IntentUtil.INTENT_PLAY_POSITION, 0);
67 | dataList = (ArrayList) intent.getSerializableExtra(IntentUtil.INTENT_DATA_LIST);
68 | }
69 |
70 | private void initUI(){
71 |
72 | rv_video_detail = findViewById(R.id.rv_video_detail);
73 | iv_close = findViewById(R.id.iv_close);
74 |
75 | linearLayoutManager = new LinearLayoutManager(mContext);
76 | rv_video_detail.setLayoutManager(linearLayoutManager);
77 |
78 | pagerSnapHelper = new PagerSnapHelper();
79 | pagerSnapHelper.attachToRecyclerView(rv_video_detail);
80 |
81 | rv_video_detail.setAdapter(new VideoDetailsAdapter(mContext, dataList));
82 |
83 | rv_video_detail.addOnScrollListener(new RecyclerView.OnScrollListener() {
84 | @Override
85 | public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
86 | if(newState == RecyclerView.SCROLL_STATE_IDLE){
87 | if(pagerSnapHelper.findSnapView(linearLayoutManager) != playView){
88 | playVisibleVideo(false);
89 | }
90 | }
91 | }
92 | });
93 |
94 | iv_close.setOnClickListener(new View.OnClickListener() {
95 | @Override
96 | public void onClick(View v) {
97 | onBackPressed();
98 | }
99 | });
100 | }
101 |
102 | View playView;
103 | /**
104 | * @param isResumePlay 是否继续上个界面播放
105 | */
106 | private void playVisibleVideo(boolean isResumePlay){
107 |
108 | View snapView = pagerSnapHelper.findSnapView(linearLayoutManager);
109 | if(snapView == null){
110 | return ;
111 | }
112 | final int position = linearLayoutManager.getPosition(snapView);
113 | if(position < 0){
114 | return ;
115 | }
116 |
117 | if(!isResumePlay){
118 | //重置播放器要在前面
119 | mMediaPlayerTool.reset();
120 | }
121 |
122 | playView = snapView;
123 | final VideoDetailsAdapter.MyViewHolder vh = (VideoDetailsAdapter.MyViewHolder) rv_video_detail.getChildViewHolder(playView);
124 |
125 | if(isResumePlay){
126 | vh.pb_video.setVisibility(View.GONE);
127 | vh.iv_cover.setVisibility(View.GONE);
128 | vh.playTextureView.setRotation(mMediaPlayerTool.getRotation());
129 | vh.playTextureView.setVideoSize(mMediaPlayerTool.getVideoWidth(), mMediaPlayerTool.getVideoHeight());
130 | setVideoSize(vh, mMediaPlayerTool.getVideoWidth(), mMediaPlayerTool.getVideoHeight());
131 | }else{
132 | //显示正在加载的界面
133 | vh.pb_video.setVisibility(View.VISIBLE);
134 | vh.iv_cover.setVisibility(View.VISIBLE);
135 |
136 | mMediaPlayerTool.initMediaPLayer();
137 | mMediaPlayerTool.setDataSource(dataList.get(position).getVideoUrl());
138 | }
139 |
140 | vh.videoTouchView.setOnTouchSlideListener(new VideoTouchView.OnTouchSlideListener() {
141 | @Override
142 | public void onSlide(float distant) {
143 | if(mMediaPlayerTool == null){
144 | return ;
145 | }
146 | if(!vh.rl_change_progress.isShown()){
147 | vh.rl_change_progress.setVisibility(View.VISIBLE);
148 | changeProgressTime = mMediaPlayerTool.getCurrentPosition();
149 | }
150 | changeProgressText(vh, distant);
151 | }
152 | @Override
153 | public void onUp() {
154 | if(vh.rl_change_progress.isShown()){
155 | vh.rl_change_progress.setVisibility(View.GONE);
156 | }
157 | mMediaPlayerTool.seekTo(changeProgressTime);
158 | }
159 | @Override
160 | public void onClick() {
161 | mContext.onBackPressed();
162 | }
163 | });
164 |
165 | mMediaPlayerTool.setVolume(1);
166 | mMediaPlayerTool.setVideoListener(new MediaPlayerTool.VideoListener() {
167 | @Override
168 | public void onStart() {
169 | vh.pb_video.setVisibility(View.GONE);
170 | vh.iv_cover.postDelayed(new Runnable() {
171 | @Override
172 | public void run() {
173 | vh.iv_cover.setVisibility(View.GONE);
174 | }
175 | }, 200);
176 | vh.playTextureView.setVideoSize(mMediaPlayerTool.getVideoWidth(), mMediaPlayerTool.getVideoHeight());
177 | setVideoSize(vh, mMediaPlayerTool.getVideoWidth(), mMediaPlayerTool.getVideoHeight());
178 | }
179 | @Override
180 | public void onRotationInfo(int rotation) {
181 | vh.playTextureView.setRotation(rotation);
182 | }
183 | @Override
184 | public void onStop() {
185 | vh.pb_video.setVisibility(View.GONE);
186 | vh.iv_cover.setVisibility(View.VISIBLE);
187 | vh.pb_play_progress.setSecondaryProgress(0);
188 | vh.pb_play_progress.setProgress(0);
189 | vh.tv_progress.setText("");
190 | playView = null;
191 | }
192 | @Override
193 | public void onCompletion() {
194 | onStop();
195 | if(position+1 >= dataList.size()) {
196 | rv_video_detail.smoothScrollToPosition(0);
197 | }else{
198 | rv_video_detail.smoothScrollToPosition(position+1);
199 | }
200 | }
201 | @Override
202 | public void onPlayProgress(long currentPosition) {
203 | int pro = (int) (currentPosition*1f/ mMediaPlayerTool.getDuration()*100);
204 | vh.pb_play_progress.setProgress(pro);
205 |
206 | String currentPositionStr = Util.fromMMss(currentPosition);
207 | String videoDurationStr = Util.fromMMss(mMediaPlayerTool.getDuration());
208 | vh.tv_progress.setText(currentPositionStr + "/" + videoDurationStr);
209 | }
210 | @Override
211 | public void onBufferProgress(int progress) {
212 | vh.pb_play_progress.setSecondaryProgress(progress);
213 | }
214 | });
215 |
216 | if(isResumePlay){
217 | vh.playTextureView.resetTextureView(mMediaPlayerTool.getAvailableSurfaceTexture());
218 | mMediaPlayerTool.setPlayTextureView(vh.playTextureView);
219 | vh.playTextureView.postInvalidate();
220 | }else{
221 | vh.playTextureView.resetTextureView();
222 | mMediaPlayerTool.setPlayTextureView(vh.playTextureView);
223 | mMediaPlayerTool.setSurfaceTexture(vh.playTextureView.getSurfaceTexture());
224 | mMediaPlayerTool.prepare();
225 | }
226 | }
227 |
228 | private void setVideoSize(VideoDetailsAdapter.MyViewHolder vh, int videoWidth, int videoHeight){
229 |
230 | float videoRatio = videoWidth * 1f / videoHeight;
231 | int windowWidth = Util.getWindowWidth();
232 | int windowHeight = Util.getWindowHeight() + StatusBarUtil.getStatusHeight(mContext);
233 | float windowRatio = Util.getWindowWidth()*1f/ Util.getWindowHeight();
234 | ViewGroup.LayoutParams layoutParams = vh.videoTouchView.getLayoutParams();
235 | if (videoRatio >= windowRatio) {
236 | layoutParams.width = windowWidth;
237 | layoutParams.height = (int) (layoutParams.width / videoRatio);
238 | } else {
239 | layoutParams.height = windowHeight;
240 | layoutParams.width = (int) (layoutParams.height * videoRatio);
241 | }
242 | vh.videoTouchView.setLayoutParams(layoutParams);
243 | }
244 |
245 | long changeProgressTime;
246 | private void changeProgressText(VideoDetailsAdapter.MyViewHolder vh, float distant){
247 |
248 | float radio = distant/vh.pb_play_progress.getWidth();
249 | changeProgressTime += mMediaPlayerTool.getDuration()*radio;
250 |
251 | if(changeProgressTime < 0){
252 | changeProgressTime = 0;
253 | }
254 | if(changeProgressTime > mMediaPlayerTool.getDuration()){
255 | changeProgressTime = mMediaPlayerTool.getDuration();
256 | }
257 |
258 | String changeTimeStr = Util.fromMMss(changeProgressTime);
259 | String rawTime = Util.fromMMss(mMediaPlayerTool.getDuration());
260 | vh.tv_change_progress.setText(changeTimeStr+" / "+rawTime);
261 |
262 | if(changeProgressTime > mMediaPlayerTool.getCurrentPosition()){
263 | vh.iv_change_progress.setImageResource(R.mipmap.video_fast_forward);
264 | }else{
265 | vh.iv_change_progress.setImageResource(R.mipmap.video_fast_back);
266 | }
267 | }
268 |
269 | boolean isFirst = true;
270 | @Override
271 | public void onWindowFocusChanged(boolean hasFocus) {
272 | super.onWindowFocusChanged(hasFocus);
273 | if(isFirst){
274 | isFirst = false;
275 | }
276 | }
277 |
278 | @Override
279 | protected void onResume() {
280 | super.onResume();
281 |
282 | if(!isFirst && mMediaPlayerTool !=null && !mMediaPlayerTool.isPlaying()){
283 | playVisibleVideo(false);
284 | }
285 | }
286 |
287 | @Override
288 | protected void onPause() {
289 | super.onPause();
290 |
291 | if(mMediaPlayerTool!=null && mMediaPlayerTool.isPlaying()) {
292 | if (dontPause) {
293 | View snapView = pagerSnapHelper.findSnapView(linearLayoutManager);
294 | if(snapView!=null && linearLayoutManager.getPosition(snapView)!=playPosition){
295 | mMediaPlayerTool.reset();
296 | }
297 | } else {
298 | mMediaPlayerTool.reset();
299 | }
300 | }
301 | }
302 |
303 | boolean dontPause;
304 | @Override
305 | public void finish() {
306 | super.finish();
307 |
308 | dontPause = true;
309 | }
310 | }
311 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zhaoss/videoplayerdemo/adapter/MainAdapter.java:
--------------------------------------------------------------------------------
1 | package com.zhaoss.videoplayerdemo.adapter;
2 |
3 | import android.support.annotation.NonNull;
4 | import android.support.v7.widget.RecyclerView;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 | import android.widget.ImageView;
8 | import android.widget.ProgressBar;
9 | import android.widget.RelativeLayout;
10 | import android.widget.TextView;
11 |
12 | import com.bumptech.glide.Glide;
13 | import com.zhaoss.videoplayerdemo.R;
14 | import com.zhaoss.videoplayerdemo.activity.MainActivity;
15 | import com.zhaoss.videoplayerdemo.bean.MainVideoBean;
16 | import com.zhaoss.videoplayerdemo.util.IntentUtil;
17 | import com.zhaoss.videoplayerdemo.view.PlayTextureView;
18 |
19 | import java.util.ArrayList;
20 |
21 | /**
22 | * Created by zhaoshuang on 2018/11/1.
23 | */
24 |
25 | public class MainAdapter extends RecyclerView.Adapter{
26 |
27 | private MainActivity mContext;
28 | private ArrayList mainVideoBeanList;
29 |
30 | public MainAdapter(MainActivity context, ArrayList mainVideoBeanList){
31 | this.mContext = context;
32 | this.mainVideoBeanList = mainVideoBeanList;
33 | }
34 |
35 | @NonNull
36 | @Override
37 | public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
38 | return new MyViewHolder(View.inflate(mContext, R.layout.item_video, null));
39 | }
40 |
41 | @Override
42 | public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, final int position) {
43 |
44 | final MyViewHolder vh = (MyViewHolder) holder;
45 | MainVideoBean mainVideoBean = mainVideoBeanList.get(position);
46 |
47 | Glide.with(mContext).load(mainVideoBean.getAvatarRes()).into(vh.iv_avatar);
48 | vh.tv_content.setText(mainVideoBean.getContent());
49 | vh.tv_name.setText(mainVideoBean.getUserName());
50 |
51 | Glide.with(mContext).load(mainVideoBean.getCoverUrl()).into(vh.iv_cover);
52 |
53 | vh.playTextureView.setOnClickListener(new View.OnClickListener() {
54 | @Override
55 | public void onClick(View v) {
56 | mContext.jumpNotCloseMediaPlay(position);
57 | IntentUtil.gotoVideoDetailsActivity(mContext, mainVideoBeanList, position, vh.playTextureView);
58 | }
59 | });
60 | }
61 |
62 | @Override
63 | public int getItemCount() {
64 | return mainVideoBeanList.size();
65 | }
66 |
67 | public class MyViewHolder extends RecyclerView.ViewHolder{
68 |
69 | public RelativeLayout rl_video;
70 | public PlayTextureView playTextureView;
71 | public ImageView iv_cover;
72 | public ProgressBar pb_video;
73 | public ImageView iv_play_icon;
74 | public TextView tv_play_time;
75 |
76 | private TextView tv_content;
77 | private ImageView iv_avatar;
78 | private TextView tv_name;
79 |
80 | public MyViewHolder(View itemView) {
81 | super(itemView);
82 |
83 | itemView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
84 |
85 | rl_video = itemView.findViewById(R.id.rl_video);
86 | playTextureView = itemView.findViewById(R.id.playTextureView);
87 | iv_cover = itemView.findViewById(R.id.iv_cover);
88 | pb_video = itemView.findViewById(R.id.pb_video);
89 | iv_play_icon = itemView.findViewById(R.id.iv_play_icon);
90 | tv_content = itemView.findViewById(R.id.tv_content);
91 | iv_avatar = itemView.findViewById(R.id.iv_avatar);
92 | tv_name = itemView.findViewById(R.id.tv_name);
93 | tv_play_time = itemView.findViewById(R.id.tv_play_time);
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zhaoss/videoplayerdemo/adapter/VideoDetailsAdapter.java:
--------------------------------------------------------------------------------
1 | package com.zhaoss.videoplayerdemo.adapter;
2 |
3 | import android.content.Context;
4 | import android.graphics.PorterDuff;
5 | import android.support.annotation.NonNull;
6 | import android.support.v4.content.ContextCompat;
7 | import android.support.v7.widget.RecyclerView;
8 | import android.view.View;
9 | import android.view.ViewGroup;
10 | import android.widget.ImageView;
11 | import android.widget.ProgressBar;
12 | import android.widget.RelativeLayout;
13 | import android.widget.TextView;
14 |
15 | import com.bumptech.glide.Glide;
16 | import com.zhaoss.videoplayerdemo.R;
17 | import com.zhaoss.videoplayerdemo.bean.MainVideoBean;
18 | import com.zhaoss.videoplayerdemo.view.PlayTextureView;
19 | import com.zhaoss.videoplayerdemo.view.VideoTouchView;
20 |
21 | import java.util.ArrayList;
22 |
23 | /**
24 | * Created by zhaoshuang on 2018/11/12.
25 | */
26 |
27 | public class VideoDetailsAdapter extends RecyclerView.Adapter {
28 |
29 | private Context mContext;
30 | private ArrayList mainVideoBeanList;
31 |
32 | public VideoDetailsAdapter(Context mContext, ArrayList mainVideoBeanList) {
33 | this.mContext = mContext;
34 | this.mainVideoBeanList = mainVideoBeanList;
35 | }
36 |
37 | @NonNull
38 | @Override
39 | public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
40 | return new MyViewHolder(View.inflate(mContext, R.layout.item_video_details, null));
41 | }
42 |
43 | @Override
44 | public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
45 |
46 | MyViewHolder vh = (MyViewHolder) holder;
47 | MainVideoBean mainVideoBean = mainVideoBeanList.get(position);
48 |
49 | vh.tv_progress.setText("");
50 | vh.pb_play_progress.setSecondaryProgress(0);
51 | vh.pb_play_progress.setProgress(0);
52 |
53 | Glide.with(mContext).load(mainVideoBean.getAvatarRes()).into(vh.iv_avatar);
54 | Glide.with(mContext).load(mainVideoBean.getCoverUrl()).into(vh.iv_cover);
55 |
56 | vh.tv_content.setText(mainVideoBean.getContent());
57 | vh.tv_name.setText(mainVideoBean.getUserName());
58 | }
59 |
60 | @Override
61 | public int getItemCount() {
62 | return mainVideoBeanList.size();
63 | }
64 |
65 | public class MyViewHolder extends RecyclerView.ViewHolder{
66 |
67 | public VideoTouchView videoTouchView;
68 | public ImageView iv_cover;
69 | public PlayTextureView playTextureView;
70 | public ProgressBar pb_video;
71 | public ProgressBar pb_play_progress;
72 | public TextView tv_progress;
73 | public RelativeLayout rl_change_progress;
74 | public ImageView iv_change_progress;
75 | public TextView tv_change_progress;
76 | private ImageView iv_avatar;
77 | private TextView tv_name;
78 | private TextView tv_content;
79 |
80 | public MyViewHolder(View itemView) {
81 | super(itemView);
82 |
83 | videoTouchView = itemView.findViewById(R.id.videoTouchView);
84 | playTextureView = itemView.findViewById(R.id.playTextureView);
85 | iv_cover = itemView.findViewById(R.id.iv_cover);
86 | pb_video = itemView.findViewById(R.id.pb_video);
87 | pb_play_progress = itemView.findViewById(R.id.pb_play_progress);
88 | tv_progress = itemView.findViewById(R.id.tv_progress);
89 | rl_change_progress = itemView.findViewById(R.id.rl_change_progress);
90 | iv_change_progress = itemView.findViewById(R.id.iv_change_progress);
91 | tv_change_progress = itemView.findViewById(R.id.tv_change_progress);
92 | iv_avatar = itemView.findViewById(R.id.iv_avatar);
93 | tv_name = itemView.findViewById(R.id.tv_name);
94 | tv_content = itemView.findViewById(R.id.tv_content);
95 |
96 | itemView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
97 | pb_play_progress.getProgressDrawable().setColorFilter(ContextCompat.getColor(mContext, R.color.white), PorterDuff.Mode.SRC_IN);
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zhaoss/videoplayerdemo/bean/MainVideoBean.java:
--------------------------------------------------------------------------------
1 | package com.zhaoss.videoplayerdemo.bean;
2 |
3 | import java.io.Serializable;
4 |
5 | /**
6 | * Created by zhaoshuang on 2018/11/1.
7 | */
8 |
9 | public class MainVideoBean implements Serializable{
10 |
11 | private int avatarRes;
12 | private String videoUrl;
13 | private String userName;
14 | private String content;
15 | private String coverUrl;
16 |
17 | public MainVideoBean(int avatarRes, String videoUrl, String userName, String content, String coverUrl) {
18 | this.avatarRes = avatarRes;
19 | this.videoUrl = videoUrl;
20 | this.userName = userName;
21 | this.content = content;
22 | this.coverUrl = coverUrl;
23 | }
24 |
25 | public int getAvatarRes() {
26 | return avatarRes;
27 | }
28 |
29 | public void setAvatarRes(int avatarRes) {
30 | this.avatarRes = avatarRes;
31 | }
32 |
33 | public String getVideoUrl() {
34 | return videoUrl;
35 | }
36 |
37 | public void setVideoUrl(String videoUrl) {
38 | this.videoUrl = videoUrl;
39 | }
40 |
41 | public String getUserName() {
42 | return userName;
43 | }
44 |
45 | public void setUserName(String userName) {
46 | this.userName = userName;
47 | }
48 |
49 | public String getContent() {
50 | return content;
51 | }
52 |
53 | public void setContent(String content) {
54 | this.content = content;
55 | }
56 |
57 | public String getCoverUrl() {
58 | return coverUrl;
59 | }
60 |
61 | public void setCoverUrl(String coverUrl) {
62 | this.coverUrl = coverUrl;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zhaoss/videoplayerdemo/bean/VideoCacheBean.java:
--------------------------------------------------------------------------------
1 | package com.zhaoss.videoplayerdemo.bean;
2 |
3 | import com.litesuits.orm.db.annotation.Column;
4 | import com.litesuits.orm.db.annotation.PrimaryKey;
5 | import com.litesuits.orm.db.enums.AssignType;
6 |
7 | /**
8 | * Created by zhaoshuang on 2018/10/25.
9 | */
10 |
11 | public class VideoCacheBean {
12 |
13 | public static final String PLAY_TIME = "playTime";
14 | public static final String KEY = "key";
15 |
16 | @PrimaryKey(AssignType.BY_MYSELF)
17 | @Column(KEY)
18 | private String key;
19 | @Column(PLAY_TIME)
20 | private long playTime;
21 | @Column("playCount")
22 | private int playCount;
23 | @Column("videoPath")
24 | private String videoPath;
25 | @Column("indexPath")
26 | private String indexPath;
27 | @Column("fileSize")
28 | private long fileSize;
29 |
30 | public String getKey() {
31 | return key;
32 | }
33 |
34 | public void setKey(String key) {
35 | this.key = key;
36 | }
37 |
38 | public long getPlayTime() {
39 | return playTime;
40 | }
41 |
42 | public void setPlayTime(long playTime) {
43 | this.playTime = playTime;
44 | }
45 |
46 | public int getPlayCount() {
47 | return playCount;
48 | }
49 |
50 | public void setPlayCount(int playCount) {
51 | this.playCount = playCount;
52 | }
53 |
54 | public String getVideoPath() {
55 | return videoPath;
56 | }
57 |
58 | public void setVideoPath(String videoPath) {
59 | this.videoPath = videoPath;
60 | }
61 |
62 | public String getIndexPath() {
63 | return indexPath;
64 | }
65 |
66 | public void setIndexPath(String indexPath) {
67 | this.indexPath = indexPath;
68 | }
69 |
70 | public long getFileSize() {
71 | return fileSize;
72 | }
73 |
74 | public void setFileSize(long fileSize) {
75 | this.fileSize = fileSize;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zhaoss/videoplayerdemo/util/CacheMediaDataSource.java:
--------------------------------------------------------------------------------
1 | package com.zhaoss.videoplayerdemo.util;
2 |
3 | import com.zhaoss.videoplayerdemo.bean.VideoCacheBean;
4 |
5 | import java.io.ByteArrayOutputStream;
6 | import java.io.File;
7 | import java.io.FileInputStream;
8 | import java.io.FileOutputStream;
9 | import java.io.IOException;
10 | import java.io.InputStream;
11 | import java.io.RandomAccessFile;
12 | import java.nio.charset.StandardCharsets;
13 | import java.security.MessageDigest;
14 |
15 | import okhttp3.Call;
16 | import okhttp3.OkHttpClient;
17 | import okhttp3.Request;
18 | import okhttp3.ResponseBody;
19 | import tv.danmaku.ijk.media.player.misc.IMediaDataSource;
20 |
21 | /**
22 | * 自己实现播放器的网络下载
23 | * Created by zhaoshuang on 2018/11/9.
24 | */
25 |
26 | public class CacheMediaDataSource implements IMediaDataSource {
27 |
28 | private String mVideoData;
29 | private String mMd5;
30 |
31 | //视频长度
32 | private long contentLength;
33 | //url对应的本地视频文件
34 | private File localVideoFile;
35 | //是否读取的缓存视频
36 | private boolean isCacheVideo;
37 | //网络流
38 | private InputStream networkInPutStream;
39 | //本地文件流
40 | private RandomAccessFile localStream;
41 |
42 | public CacheMediaDataSource(String videoData) {
43 | this.mVideoData = videoData;
44 | mMd5 = MD5(videoData);
45 | }
46 |
47 | public String MD5(String s) {
48 | try {
49 | MessageDigest md = MessageDigest.getInstance("MD5");
50 | byte[] bytes = md.digest(s.getBytes(StandardCharsets.UTF_8));
51 |
52 | final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();
53 | StringBuilder ret = new StringBuilder(bytes.length * 2);
54 | for (int i=0; i> 4) & 0x0f]);
56 | ret.append(HEX_DIGITS[bytes[i] & 0x0f]);
57 | }
58 | return ret.toString();
59 | } catch (Exception e) {
60 | e.printStackTrace();
61 | }
62 | return "";
63 | }
64 |
65 | /**
66 | *
67 | * @param position 视频流读取进度
68 | * @param buffer 要把读取到的数据存到这个数组
69 | * @param offset 数据开始写入的坐标
70 | * @param size 本次一共读取数据的大小
71 | * @throws IOException
72 | */
73 | //记录当前读取流的索引
74 | long mPosition = 0;
75 | @Override
76 | public int readAt(long position, byte[] buffer, int offset, int size) throws IOException {
77 |
78 | if(position>=contentLength || localStream==null){
79 | return -1;
80 | }
81 |
82 | //是否将此字节缓存到本地
83 | boolean isWriteVideo = syncInputStream(position);
84 |
85 | //读取的流的长度不能大于contentLength
86 | if (position+size > contentLength) {
87 | size -= position+size-contentLength;
88 | }
89 |
90 | //读取指定大小的视频数据
91 | byte[] bytes;
92 | if(isCacheVideo){
93 | //从本地读取
94 | bytes = readByteBySize(localStream, size);
95 | }else{
96 | //从网络读取
97 | bytes = readByteBySize(networkInPutStream, size);
98 | }
99 | if(bytes != null) {
100 | //写入到播放器的数组中
101 | System.arraycopy(bytes, 0, buffer, offset, size);
102 | if (isWriteVideo && !isCacheVideo) {
103 | //缓存到本地
104 | localStream.write(bytes);
105 | }
106 | //记录数据流读取到哪步了
107 | mPosition += size;
108 | }
109 |
110 | return size;
111 | }
112 |
113 | //同步数据流
114 | private boolean syncInputStream(long position) throws IOException {
115 |
116 | boolean isWriteVideo = true;
117 | //判断两次读取数据是否连续
118 | if(mPosition != position){
119 | if(isCacheVideo){
120 | //如果是本地缓存, 直接跳转到该索引
121 | localStream.seek(position);
122 | }else{
123 | if(mPosition > position){
124 | //同步本地缓存流
125 | localStream.close();
126 | deleteFileByPosition(position);
127 | localStream.seek(position);
128 | }else{
129 | isWriteVideo = false;
130 | }
131 | networkInPutStream.close();
132 | //重新开启一个网络流
133 | networkInPutStream = openHttpClient((int) position);
134 | }
135 | mPosition = position;
136 | }
137 | return isWriteVideo;
138 | }
139 |
140 | /**
141 | * 从inputStream里读取size大小的数据
142 | */
143 | private byte[] readByteBySize(InputStream inputStream, int size) throws IOException {
144 |
145 | ByteArrayOutputStream out = new ByteArrayOutputStream();
146 |
147 | byte[] buf = new byte[size];
148 | int len;
149 | while ((len = inputStream.read(buf)) != -1) {
150 | out.write(buf, 0, len);
151 | if (out.size() == size) {
152 | return out.toByteArray();
153 | } else {
154 | buf = new byte[size - out.size()];
155 | }
156 | }
157 | return null;
158 | }
159 |
160 | /**
161 | * 从inputStream里读取size大小的数据
162 | */
163 | private byte[] readByteBySize(RandomAccessFile inputStream, int size) throws IOException {
164 |
165 | ByteArrayOutputStream out = new ByteArrayOutputStream();
166 |
167 | byte[] buf = new byte[size];
168 | int len;
169 | while ((len = inputStream.read(buf)) != -1) {
170 | out.write(buf, 0, len);
171 | if (out.size() == size) {
172 | return out.toByteArray();
173 | } else {
174 | buf = new byte[size - out.size()];
175 | }
176 | }
177 | return null;
178 | }
179 |
180 | /**
181 | * 删除file一部分字节, 从position到file.size
182 | */
183 | private void deleteFileByPosition(long position) throws IOException {
184 |
185 | FileInputStream in = new FileInputStream(localVideoFile);
186 |
187 | File tempFile = VideoLRUCacheUtil.createTempFile();
188 | FileOutputStream out = new FileOutputStream(tempFile);
189 |
190 | byte[] buf = new byte[8192];
191 | int len;
192 | while ((len = in.read(buf)) != -1) {
193 | if(position <= len){
194 | out.write(buf, 0, (int) position);
195 | out.close();
196 |
197 | in.close();
198 | localVideoFile.delete();
199 | tempFile.renameTo(localVideoFile);
200 | localStream = new RandomAccessFile(localVideoFile, "rw");
201 | return ;
202 | }else{
203 | position -= len;
204 | out.write(buf, 0, len);
205 | }
206 | }
207 | tempFile.delete();
208 | }
209 |
210 | @Override
211 | public long getSize() throws IOException {
212 |
213 | if(networkInPutStream == null) {
214 | initInputStream();
215 | }
216 | return contentLength;
217 | }
218 |
219 | //初始化一个视频流出来, 可能是本地或网络
220 | private void initInputStream() throws IOException {
221 |
222 | File file;
223 | if(!mVideoData.startsWith("http")){
224 | file = new File(mVideoData);
225 | }else {
226 | file = checkCache(mMd5);
227 | }
228 |
229 | if(file!=null){
230 | if(file.exists()) {
231 | //更新一下缓存文件
232 | VideoLRUCacheUtil.updateVideoCacheBean(mMd5, file.getAbsolutePath(), "");
233 | //读取的本地缓存文件
234 | isCacheVideo = true;
235 | localVideoFile = file;
236 | //开启一个本地视频流
237 | localStream = new RandomAccessFile(localVideoFile, "rw");
238 | contentLength = file.length();
239 | }else{
240 | throw new IOException("文件不存在");
241 | }
242 | }else {
243 | //没有缓存 开启一个网络流, 并且开启一个缓存流, 实现视频缓存
244 | isCacheVideo = false;
245 | //开启一个网络视频流
246 | networkInPutStream = openHttpClient(0);
247 | //要写入的本地缓存文件
248 | localVideoFile = VideoLRUCacheUtil.createCacheFile(mMd5);
249 | //要写入的本地缓存视频流
250 | localStream = new RandomAccessFile(localVideoFile, "rw");
251 | }
252 | }
253 |
254 | //检查本地是否有缓存, 2步确认, 数据库中是否存在, 本地文件是否存在
255 | private File checkCache(String md5){
256 |
257 | //查询数据库
258 | VideoCacheBean bean = VideoCacheDBUtil.query(md5);
259 | if(bean != null){
260 | File file = new File(bean.getVideoPath());
261 | if(file.exists()){
262 | return file;
263 | }
264 | }
265 | return null;
266 | }
267 |
268 | //打开一个网络视频流, 从startIndex开始下载
269 | private InputStream openHttpClient(int startIndex) throws IOException {
270 |
271 | OkHttpClient okHttpClient = new OkHttpClient();
272 | Request request = new Request.Builder()
273 | .header("RANGE", "bytes=" + startIndex + "-")
274 | .url(mVideoData)
275 | .get()
276 | .build();
277 | Call call = okHttpClient.newCall(request);
278 | ResponseBody responseBody = call.execute().body();
279 | if(responseBody != null){
280 | contentLength = responseBody.contentLength()+startIndex;
281 | return responseBody.byteStream();
282 | }else{
283 | return null;
284 | }
285 | }
286 |
287 | public void onError(){
288 | VideoCacheBean bean = VideoCacheDBUtil.query(mMd5);
289 | if(bean != null){
290 | VideoCacheDBUtil.delete(bean);
291 | }
292 | }
293 |
294 | @Override
295 | public void close() throws IOException {
296 | if(networkInPutStream != null){
297 | networkInPutStream.close();
298 | networkInPutStream = null;
299 | }
300 | if(localStream != null){
301 | localStream.close();
302 | localStream = null;
303 | }
304 |
305 | if(localVideoFile!=null && localVideoFile.length()!=contentLength){
306 | localVideoFile.delete();
307 | }
308 | }
309 | }
310 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zhaoss/videoplayerdemo/util/DataUtil.java:
--------------------------------------------------------------------------------
1 | package com.zhaoss.videoplayerdemo.util;
2 |
3 | import com.zhaoss.videoplayerdemo.R;
4 | import com.zhaoss.videoplayerdemo.bean.MainVideoBean;
5 |
6 | import java.util.ArrayList;
7 |
8 | /**
9 | * 假数据
10 | * Created by zhaoshuang on 2018/11/13.
11 | */
12 |
13 | public class DataUtil {
14 |
15 | public static ArrayList createData(){
16 |
17 | ArrayList mainVideoBeanList = new ArrayList<>();
18 |
19 | mainVideoBeanList.add(new MainVideoBean(R.mipmap.avatar1,
20 | "https://oimryzjfe.qnssl.com/content/1F3D7F815F2C6870FB512B8CA2C3D2C1.mp4",
21 | "程序员小陈1",
22 | "在即将正式发布的 Android P 版本中,谷歌彻底取消了对Apache HTTPClient的支持, 谷歌就推荐在Android2.3版本以上使用HttpURLConnection",
23 | "https://oimryzjfe.qnssl.com/content/2EF3AE01AC1D36BDF42EFA87C275BB66.jpg"));
24 | mainVideoBeanList.add(new MainVideoBean(R.mipmap.avatar1,
25 | "https://oimryzjfe.qnssl.com/content/fe9cfd1402bb40490bc9a208db7c0921.mp4",
26 | "程序员小陈2",
27 | "在即将正式发布的 Android P 版本中,谷歌彻底取消了对Apache HTTPClient的支持, 谷歌就推荐在Android2.3版本以上使用HttpURLConnection",
28 | "https://oimryzjfe.qnssl.com/content/022294301d2d7da585eae4bbbe3653f6.png"));
29 | mainVideoBeanList.add(new MainVideoBean(R.mipmap.avatar1,
30 | "https://oimryzjfe.qnssl.com/content/47465d359406bb4b68c8c205e2974807.mp4",
31 | "程序员小陈3",
32 | "在即将正式发布的 Android P 版本中,谷歌彻底取消了对Apache HTTPClient的支持, 谷歌就推荐在Android2.3版本以上使用HttpURLConnection",
33 | "https://oimryzjfe.qnssl.com/content/07e99ee99ce291608687b2a9e87d546e.png"));
34 | mainVideoBeanList.add(new MainVideoBean(R.mipmap.avatar1,
35 | "https://oimryzjfe.qnssl.com/content/93fcbd491e40159e949bb4cb191e231e.mp4",
36 | "程序员小陈4",
37 | "在即将正式发布的 Android P 版本中,谷歌彻底取消了对Apache HTTPClient的支持, 谷歌就推荐在Android2.3版本以上使用HttpURLConnection",
38 | "https://oimryzjfe.qnssl.com/content/1f80d66e88c6215a9e70d59aab0c0dd3.png"));
39 | mainVideoBeanList.add(new MainVideoBean(R.mipmap.avatar1,
40 | "https://oimryzjfe.qnssl.com/content/beee1c9325330b845b13298842f711ff.mp4",
41 | "程序员小陈5",
42 | "在即将正式发布的 Android P 版本中,谷歌彻底取消了对Apache HTTPClient的支持, 谷歌就推荐在Android2.3版本以上使用HttpURLConnection",
43 | "https://oimryzjfe.qnssl.com/content/935d8258e862f8d2bfd50ac1fd6af639.png"));
44 | mainVideoBeanList.add(new MainVideoBean(R.mipmap.avatar1,
45 | "https://oimryzjfe.qnssl.com/content/807403BE56FD9503A609975B81BA4636.mp4",
46 | "程序员小陈6",
47 | "在即将正式发布的 Android P 版本中,谷歌彻底取消了对Apache HTTPClient的支持, 谷歌就推荐在Android2.3版本以上使用HttpURLConnection",
48 | "https://oimryzjfe.qnssl.com/content/D95667408AAC4209701ECF09B896C0B7.jpg"));
49 | mainVideoBeanList.add(new MainVideoBean(R.mipmap.avatar1,
50 | "https://oimryzjfe.qnssl.com/content/68239E7D6DC93D98E083137F0C537D97.mp4",
51 | "程序员小陈7",
52 | "在即将正式发布的 Android P 版本中,谷歌彻底取消了对Apache HTTPClient的支持, 谷歌就推荐在Android2.3版本以上使用HttpURLConnection",
53 | "https://oimryzjfe.qnssl.com/content/DCB821BA35E4DA20DF5CE450F621D10B.jpg"));
54 | mainVideoBeanList.add(new MainVideoBean(R.mipmap.avatar1,
55 | "https://oimryzjfe.qnssl.com/content/afc192cfae2df1366d7268bc7a181555.mp4",
56 | "程序员小陈8",
57 | "在即将正式发布的 Android P 版本中,谷歌彻底取消了对Apache HTTPClient的支持, 谷歌就推荐在Android2.3版本以上使用HttpURLConnection",
58 | "https://oimryzjfe.qnssl.com/content/e72446a57dbf64e234bad0582ecdb44e.png"));
59 | mainVideoBeanList.add(new MainVideoBean(R.mipmap.avatar1,
60 | "https://oimryzjfe.qnssl.com/content/2c61c7c5e95b3f4dec31aa42e4315bb1.mp4",
61 | "程序员小陈9",
62 | "在即将正式发布的 Android P 版本中,谷歌彻底取消了对Apache HTTPClient的支持, 谷歌就推荐在Android2.3版本以上使用HttpURLConnection",
63 | "https://oimryzjfe.qnssl.com/content/a92ba82c9c9c44152f3523d000cfbb9c.png"));
64 | mainVideoBeanList.add(new MainVideoBean(R.mipmap.avatar1,
65 | "https://oimryzjfe.qnssl.com/content/0fcbbe738abf1bf524dc2e7818200cc8.mp4",
66 | "程序员小陈10",
67 | "在即将正式发布的 Android P 版本中,谷歌彻底取消了对Apache HTTPClient的支持, 谷歌就推荐在Android2.3版本以上使用HttpURLConnection",
68 | "https://oimryzjfe.qnssl.com/content/f38ce694e89a462ea79eb6f16b94ead7.png"));
69 |
70 | return mainVideoBeanList;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zhaoss/videoplayerdemo/util/IntentUtil.java:
--------------------------------------------------------------------------------
1 | package com.zhaoss.videoplayerdemo.util;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.support.v4.app.ActivityCompat;
6 | import android.support.v4.app.ActivityOptionsCompat;
7 | import android.view.View;
8 |
9 | import com.zhaoss.videoplayerdemo.activity.VideoDetailsActivity;
10 | import com.zhaoss.videoplayerdemo.bean.MainVideoBean;
11 |
12 | import java.util.ArrayList;
13 |
14 | /**
15 | * Created by zhaoshuang on 2018/11/12.
16 | */
17 |
18 | public class IntentUtil {
19 |
20 | public static final String INTENT_DATA_LIST = "intent_data_list";
21 | public static final String INTENT_PLAY_POSITION = "intent_play_position";
22 |
23 | public static void gotoVideoDetailsActivity(Activity activity, ArrayList dataList, int playPosition, View animationView){
24 |
25 | Intent intent = new Intent(activity, VideoDetailsActivity.class);
26 | intent.putExtra(INTENT_DATA_LIST, dataList);
27 | intent.putExtra(INTENT_PLAY_POSITION, playPosition);
28 |
29 | ActivityOptionsCompat compat = ActivityOptionsCompat.makeClipRevealAnimation(animationView, 0, 0, animationView.getWidth(), animationView.getHeight());
30 | ActivityCompat.startActivity(activity, intent, compat.toBundle());
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zhaoss/videoplayerdemo/util/LogUtil.java:
--------------------------------------------------------------------------------
1 | package com.zhaoss.videoplayerdemo.util;
2 |
3 | import android.util.Log;
4 |
5 | /**
6 | * Created by zhaoshuang on 2018/6/1.
7 | */
8 |
9 | public class LogUtil {
10 |
11 | public static void print(String text){
12 | Log.i("Log.i", text);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/zhaoss/videoplayerdemo/util/MediaPlayerTool.java:
--------------------------------------------------------------------------------
1 | package com.zhaoss.videoplayerdemo.util;
2 |
3 | import android.graphics.SurfaceTexture;
4 | import android.text.TextUtils;
5 | import android.view.Surface;
6 |
7 | import com.zhaoss.videoplayerdemo.view.PlayTextureView;
8 |
9 | import java.io.File;
10 | import java.lang.ref.SoftReference;
11 | import java.util.concurrent.TimeUnit;
12 | import java.util.concurrent.atomic.AtomicInteger;
13 |
14 | import io.reactivex.Observable;
15 | import io.reactivex.ObservableSource;
16 | import io.reactivex.android.schedulers.AndroidSchedulers;
17 | import io.reactivex.disposables.Disposable;
18 | import io.reactivex.functions.Consumer;
19 | import io.reactivex.functions.Function;
20 | import tv.danmaku.ijk.media.player.AndroidMediaPlayer;
21 | import tv.danmaku.ijk.media.player.IMediaPlayer;
22 | import tv.danmaku.ijk.media.player.IjkMediaPlayer;
23 |
24 | /**
25 | * Created by zhaoshuang on 2018/9/13.
26 | */
27 |
28 | public class MediaPlayerTool implements IMediaPlayer.OnCompletionListener, IMediaPlayer.OnErrorListener,
29 | IMediaPlayer.OnBufferingUpdateListener, IMediaPlayer.OnPreparedListener, IMediaPlayer.OnInfoListener {
30 |
31 | //ijkio协议
32 | public static final String IJK_CACHE_HEAD = "ijkio:cache:ffio:";
33 |
34 | private IMediaPlayer mMediaPlayer;
35 | private VideoListener mVideoListener;
36 | private SurfaceTexture playSurfaceTexture;
37 | //记录上次播放器的hasCode
38 | private AtomicInteger playHasCode = new AtomicInteger(0);
39 | //视频旋转播放角度
40 | private int mRotation;
41 | //视频时长
42 | private long mDuration;
43 | //视频音量
44 | private float mVolume;
45 |
46 | //加载bilibili库成功
47 | private boolean loadIjkSucc;
48 |
49 | private SoftReference srPlayTextureView;
50 | private String mVideoUrl;
51 | private CacheMediaDataSource mMediaDataSource;
52 |
53 | private MediaPlayerTool(){
54 | try {
55 | IjkMediaPlayer.loadLibrariesOnce(null);
56 | IjkMediaPlayer.native_profileBegin("libijkplayer.so");
57 | loadIjkSucc = true;
58 | }catch (UnsatisfiedLinkError e){
59 | e.printStackTrace();
60 | loadIjkSucc = false;
61 | }
62 | }
63 |
64 | public static MediaPlayerTool mMediaPlayerTool;
65 | public synchronized static MediaPlayerTool getInstance(){
66 |
67 | if(mMediaPlayerTool == null){
68 | mMediaPlayerTool = new MediaPlayerTool();
69 | }
70 | return mMediaPlayerTool;
71 | }
72 |
73 | public void start(){
74 | if(mMediaPlayer != null){
75 | mMediaPlayer.start();
76 | }
77 | }
78 |
79 | public void pause() {
80 | if(mMediaPlayer != null){
81 | mMediaPlayer.pause();
82 | }
83 | }
84 |
85 | public int getVideoWidth(){
86 | if(mMediaPlayer != null){
87 | return mMediaPlayer.getVideoWidth();
88 | }
89 | return 0;
90 | }
91 |
92 | public int getVideoHeight(){
93 | if(mMediaPlayer != null){
94 | return mMediaPlayer.getVideoHeight();
95 | }
96 | return 0;
97 | }
98 |
99 | public void setLooping(boolean looping) {
100 | if(mMediaPlayer != null){
101 | mMediaPlayer.setLooping(looping);
102 | }
103 | }
104 |
105 | public boolean isLooping() {
106 | if(mMediaPlayer != null){
107 | return mMediaPlayer.isLooping();
108 | }
109 | return false;
110 | }
111 |
112 | public void onDestroy() {
113 | reset();
114 | IjkMediaPlayer.native_profileEnd();
115 | }
116 |
117 | /**
118 | * @param volume 0-1
119 | */
120 | public void setVolume(float volume){
121 | if(mMediaPlayer != null) {
122 | this.mVolume = volume;
123 | mMediaPlayer.setVolume(volume, volume);
124 | }
125 | }
126 |
127 | public float getVolume(){
128 | return mVolume;
129 | }
130 |
131 | public long getDuration(){
132 | return mDuration;
133 | }
134 |
135 | public long getCurrentPosition(){
136 | if(mMediaPlayer != null) {
137 | return mMediaPlayer.getCurrentPosition();
138 | }
139 | return 0;
140 | }
141 |
142 | public void setPlayTextureView(PlayTextureView playTextureView){
143 | if(srPlayTextureView != null){
144 | srPlayTextureView.clear();
145 | }
146 | srPlayTextureView = new SoftReference(playTextureView);
147 | }
148 |
149 | public void setSurfaceTexture(SurfaceTexture surfaceTexture){
150 | this.playSurfaceTexture = surfaceTexture;
151 | if(mMediaPlayer!=null && surfaceTexture!=null) {
152 | mMediaPlayer.setSurface(new Surface(surfaceTexture));
153 | }
154 | }
155 |
156 | public SurfaceTexture getAvailableSurfaceTexture(){
157 | cleanTextureViewParent();
158 | return playSurfaceTexture;
159 | }
160 |
161 | private void cleanTextureViewParent(){
162 | if(srPlayTextureView != null) {
163 | PlayTextureView playTextureView = srPlayTextureView.get();
164 | if (playTextureView != null) {
165 | playTextureView.resetTextureView();
166 | }
167 | }
168 | }
169 |
170 | public void seekTo(long msec){
171 | if(mMediaPlayer != null){
172 | mMediaPlayer.seekTo(msec);
173 | }
174 | }
175 |
176 | private void checkPath(){
177 | //初始化缓存路径
178 | File file = new File(VideoLRUCacheUtil.CACHE_DIR_PATH);
179 | if(!file.exists()){
180 | file.mkdirs();
181 | }
182 | }
183 |
184 | public void setDataSource(String url){
185 | setDataSource(url, true);
186 | }
187 |
188 | public void setDataSource(String url, boolean isCache){
189 | try {
190 |
191 | //自定义缓存架构
192 | //mMediaDataSource = new CacheMediaDataSource(url);
193 | //mMediaPlayer.setDataSource(mMediaDataSource);
194 |
195 | if(isCache){
196 | mVideoUrl = IJK_CACHE_HEAD+url;
197 | mMediaPlayer.setDataSource(mVideoUrl);
198 | if(mMediaPlayer instanceof IjkMediaPlayer) {
199 | checkPath();
200 | IjkMediaPlayer ijkMediaPlayer = (IjkMediaPlayer) mMediaPlayer;
201 | String name = Util.MD5(mVideoUrl);
202 | String videoPath = VideoLRUCacheUtil.CACHE_DIR_PATH+name+".v";
203 | String indexPath = VideoLRUCacheUtil.CACHE_DIR_PATH+name+".i";
204 | ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "cache_file_path", videoPath);
205 | ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "cache_map_path", indexPath);
206 | VideoLRUCacheUtil.updateVideoCacheBean(name, videoPath, indexPath);
207 | }
208 | }else{
209 | mVideoUrl = url;
210 | mMediaPlayer.setDataSource(mVideoUrl);
211 | }
212 | } catch (Exception e) {
213 | e.printStackTrace();
214 | onError(mMediaPlayer, 0, 0);
215 | }
216 | }
217 |
218 | public void prepare(){
219 | try {
220 | if (mMediaPlayer != null) {
221 | mMediaPlayer.prepareAsync();
222 | }
223 | }catch (Throwable e){
224 | e.printStackTrace();
225 | onError(mMediaPlayer, 0, 0);
226 | }
227 | }
228 |
229 | public int getRotation(){
230 | return mRotation;
231 | }
232 |
233 | public boolean isPlaying(){
234 | if(mMediaPlayer != null){
235 | return mMediaPlayer.isPlaying();
236 | }
237 | return false;
238 | }
239 |
240 | public void reset(){
241 |
242 | if(subscribe != null){
243 | subscribe.dispose();
244 | subscribe = null;
245 | }
246 |
247 | if(srPlayTextureView != null){
248 | PlayTextureView playTextureView = srPlayTextureView.get();
249 | if(playTextureView != null){
250 | playTextureView.resetTextureView();
251 | }
252 | srPlayTextureView.clear();
253 | srPlayTextureView = null;
254 | }
255 |
256 | if(playSurfaceTexture != null) {
257 | playSurfaceTexture.release();
258 | playSurfaceTexture = null;
259 | }
260 |
261 | if(mVideoListener != null){
262 | mVideoListener.onStop();
263 | mVideoListener = null;
264 | }
265 |
266 | if(mMediaPlayer!=null && playHasCode.get()!=mMediaPlayer.hashCode()) {
267 | playHasCode.set(mMediaPlayer.hashCode());
268 | final IMediaPlayer releaseMediaPlay = mMediaPlayer;
269 | mMediaPlayer = null;
270 | RxJavaUtil.run(new RxJavaUtil.OnRxAndroidListener