├── .gitignore
├── .idea
├── .name
├── compiler.xml
├── copyright
│ └── profiles_settings.xml
└── encodings.xml
├── README.md
├── Sample
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── djonce
│ │ └── wangj
│ │ └── stickyvideo
│ │ └── ApplicationTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── djonce
│ │ │ └── wangj
│ │ │ └── stickyvideo
│ │ │ └── MainActivity.java
│ └── res
│ │ ├── drawable-xxhdpi
│ │ ├── arrow_left.png
│ │ ├── ds_all_button_play.png
│ │ ├── ds_all_more_white.png
│ │ ├── ds_detail_fullscreen_close.png
│ │ ├── ds_detail_fullscreen_open.png
│ │ └── home_bg_top.png
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ ├── view_first_header.xml
│ │ └── view_second_header.xml
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ └── ic_launcher.png
│ │ ├── values-w820dp
│ │ └── dimens.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── djonce
│ └── wangj
│ └── stickyvideo
│ └── ExampleUnitTest.java
├── Screenshot
├── 3BAF7EB1-064E-447B-A7BA-670A20FD1ECB.png
├── 6B142384-9C3D-43CC-B140-E7CA3048AEAB.png
└── A90E928D-A009-48CE-BF8C-F8DD080C3866.png
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── media-library
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── djonce
│ │ └── wangj
│ │ └── media
│ │ └── ApplicationTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── djonce
│ │ │ └── wangj
│ │ │ └── media
│ │ │ ├── DisplayUtils.java
│ │ │ ├── IMediaController.java
│ │ │ ├── IRenderView.java
│ │ │ ├── MeasureHelper.java
│ │ │ ├── MediaPlayerCompat.java
│ │ │ ├── MediaPlayerController.java
│ │ │ ├── MediaPlayerView.java
│ │ │ ├── MediaVideoView.java
│ │ │ ├── OnMediaPlayerStateListener.java
│ │ │ └── TextureRenderView.java
│ └── res
│ │ ├── drawable-xxhdpi
│ │ ├── ds_all_button_play.png
│ │ ├── ds_detail_fullscreen_close.png
│ │ └── ds_detail_fullscreen_open.png
│ │ ├── drawable
│ │ ├── detail_process_bar.xml
│ │ └── seek_bar_empty_thumb.xml
│ │ ├── layout
│ │ ├── media_player_controller_view.xml
│ │ └── media_player_view.xml
│ │ └── values
│ │ └── strings.xml
│ └── test
│ └── java
│ └── com
│ └── djonce
│ └── wangj
│ └── media
│ └── ExampleUnitTest.java
└── 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 |
10 | .idea/gradle.xml
11 |
12 | .idea/gradle.xml
13 |
14 | .idea/inspectionProfiles/profiles_settings.xml
15 |
16 | .idea/misc.xml
17 |
18 | .idea/vcs.xml
19 |
20 | .idea/gradle.xml
21 | .idea/compiler.xml
22 | .idea/encodings.xml
23 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | android-stickyvideo
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # android-stickyvideo
2 | 仿美拍视频悬浮
3 |
4 | 先提交视频库文件,稍后 写视频悬浮部分
5 |
6 | 提交悬浮部分的代码,效果如下图
7 |
8 | 正常播放状态
9 | 
10 |
11 | 下滑缩放状态
12 | 
13 |
14 | 放大全屏状态
15 | 
16 |
17 |
18 |
--------------------------------------------------------------------------------
/Sample/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/Sample/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "23.0.2"
6 |
7 | defaultConfig {
8 | applicationId "com.djonce.wangj.stickyvideo"
9 | minSdkVersion 15
10 | targetSdkVersion 23
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | testCompile 'junit:junit:4.12'
25 | compile 'com.android.support:appcompat-v7:23.2.0'
26 | compile 'com.jakewharton:butterknife:7.0.1'
27 | compile project(':media-library')
28 | }
29 |
--------------------------------------------------------------------------------
/Sample/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in C:\Users\yuanchen\AppData\Local\Android\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/Sample/src/androidTest/java/com/djonce/wangj/stickyvideo/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.djonce.wangj.stickyvideo;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/Sample/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
15 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/Sample/src/main/java/com/djonce/wangj/stickyvideo/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.djonce.wangj.stickyvideo;
2 |
3 | import android.app.Activity;
4 | import android.content.pm.ActivityInfo;
5 | import android.content.res.Configuration;
6 | import android.os.Bundle;
7 | import android.util.Log;
8 | import android.view.View;
9 | import android.view.ViewGroup;
10 | import android.view.WindowManager;
11 | import android.view.animation.AlphaAnimation;
12 | import android.view.animation.Animation;
13 | import android.widget.AbsListView;
14 | import android.widget.ArrayAdapter;
15 | import android.widget.ImageView;
16 | import android.widget.ListView;
17 | import android.widget.RelativeLayout;
18 |
19 | import com.djonce.wangj.media.DisplayUtils;
20 | import com.djonce.wangj.media.MediaPlayerController;
21 | import com.djonce.wangj.media.MediaPlayerView;
22 | import com.djonce.wangj.media.MediaVideoView;
23 | import com.djonce.wangj.media.OnMediaPlayerStateListener;
24 |
25 | import java.util.ArrayList;
26 | import java.util.List;
27 |
28 | import butterknife.Bind;
29 | import butterknife.ButterKnife;
30 | import tv.danmaku.ijk.media.player.IMediaPlayer;
31 |
32 | public class MainActivity extends Activity {
33 |
34 | private static final String TAG = MainActivity.class.getSimpleName();
35 |
36 | @Bind(R.id.listView)
37 | ListView listView;
38 | @Bind(R.id.sticky_layout)
39 | RelativeLayout stickyLayout;
40 |
41 | @Bind(R.id.full_layout)
42 | RelativeLayout fullLayout;
43 |
44 | View secondHeader;
45 | RelativeLayout secondRootLayout;
46 | MediaPlayerView videoView;
47 |
48 | ArrayAdapter adapter;
49 | boolean isSticky = false; // 是否是悬浮状态
50 |
51 | @Override
52 | protected void onCreate(Bundle savedInstanceState) {
53 | super.onCreate(savedInstanceState);
54 | setContentView(R.layout.activity_main);
55 |
56 | ButterKnife.bind(this);
57 |
58 | DisplayUtils.init(this);
59 |
60 | initViews();
61 | setAdapter();
62 |
63 | startVideo();
64 | }
65 |
66 |
67 | private void initViews() {
68 |
69 | View firstHeader = View.inflate(this, R.layout.view_first_header, null);
70 |
71 | listView.addHeaderView(firstHeader);
72 | secondHeader = View.inflate(this, R.layout.view_second_header, null);
73 |
74 | secondRootLayout = (RelativeLayout) secondHeader.findViewById(R.id.secondRootLayout);
75 | videoView = (MediaPlayerView) secondHeader.findViewById(R.id.videoView);
76 | listView.addHeaderView(secondHeader);
77 |
78 | listView.setOnScrollListener(new AbsListView.OnScrollListener() {
79 | @Override
80 | public void onScrollStateChanged(AbsListView view, int scrollState) {
81 |
82 | }
83 |
84 | @Override
85 | public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
86 | if (isSticky && firstVisibleItem == 0) {
87 | removeStickyView();
88 | isSticky = false;
89 | }
90 |
91 | if (!isSticky && firstVisibleItem > 1) {
92 | // 将headView添加到sticky layout中
93 | addStickyView();
94 | isSticky = true;
95 | }
96 | }
97 | });
98 |
99 | videoView.setOnPlayerControllerListener(new MediaPlayerController.OnPlayerControllerListener() {
100 | @Override
101 | public void onPlay() {
102 | Log.e(TAG, " --- onPlay ---");
103 | }
104 |
105 | @Override
106 | public void onSeekPlay(int position, long duration) {
107 | Log.e(TAG, " --- onSeekPlay -- position:-" + position + " duration :" + duration);
108 | }
109 |
110 | @Override
111 | public void onPause() {
112 | Log.e(TAG, " --- onPause ---");
113 | }
114 |
115 | @Override
116 | public void onResume() {
117 | Log.e(TAG, " --- onResume ---");
118 | }
119 |
120 | @Override
121 | public void onZoomBig() {
122 | Log.e(TAG, " --- onZoomBig ---");
123 | zoomBig();
124 | videoView.onResume();
125 | }
126 |
127 | @Override
128 | public void onZoomSmall() {
129 | Log.e(TAG, " --- onZoomSmall ---");
130 | zoomSmall();
131 | }
132 |
133 | });
134 |
135 | videoView.setPlayerStateListener(new OnMediaPlayerStateListener(){
136 |
137 | @Override
138 | public void onFirstPlay() {
139 | super.onFirstPlay();
140 | Log.e(TAG, " --- onFirstPlay ---");
141 | }
142 |
143 | @Override
144 | public void onClickPlay() {
145 | super.onClickPlay();
146 | Log.e(TAG, " --- onClickPlay ---");
147 | }
148 |
149 | @Override
150 | public void onSeekPlay() {
151 | Log.e(TAG, " --- onSeekPlay ---");
152 | }
153 |
154 | @Override
155 | public void onBufferStart() {
156 | super.onBufferStart();
157 | Log.e(TAG, " --- onBufferStart ---");
158 | }
159 |
160 | @Override
161 | public void onBufferEnd() {
162 | super.onBufferEnd();
163 | Log.e(TAG, " --- onBufferEnd ---");
164 | }
165 |
166 | @Override
167 | public void onPause() {
168 | Log.e(TAG, " --- onPause ---");
169 | }
170 |
171 | @Override
172 | public void onResume() {
173 | Log.e(TAG, " --- onResume ---");
174 | }
175 |
176 | @Override
177 | public void onError(IMediaPlayer mp, int what, int extra) {
178 | Log.e(TAG, " --- onError ---" + " what :" + what + " extra :" + extra);
179 | }
180 |
181 | @Override
182 | public void onCompleted(IMediaPlayer mp) {
183 | Log.e(TAG, " --- onCompleted ---");
184 | }
185 | });
186 |
187 | videoView.setOnLoadImageListener(new MediaPlayerView.OnLoadImageListener() {
188 | @Override
189 | public void onLoadImage(ImageView imageView, String imagePath) {
190 |
191 | }
192 | });
193 |
194 | }
195 |
196 | private void zoomSmall() {
197 | setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
198 | quitFullScreen();
199 | removeFullView();
200 | }
201 |
202 | private void zoomBig() {
203 | // 旋转屏幕
204 | setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
205 | setFullScreen();
206 | addFullView();
207 | }
208 |
209 | private void startVideo() {
210 | String videoPath = "http://video2.peiyinxiu.com/2016050313476874fdeb76898f27.mp4";
211 | videoView.setVideoPath(videoPath);
212 | videoView.requestFocus();
213 | videoView.autoStartPlay();
214 | }
215 |
216 |
217 | private void addStickyView() {
218 | // 将head添加到sticky layout中
219 | secondRootLayout.removeAllViews();
220 | if (stickyLayout != null) {
221 | stickyLayout.removeAllViews();
222 |
223 | videoView.setVideoShowMode(MediaVideoView.VideoMode.SMALL);
224 | stickyLayout.addView(videoView);
225 | stickyLayout.startAnimation(showStickyAnimation());
226 | }
227 |
228 | }
229 |
230 | private void removeStickyView() {
231 | // 移除 sticky layout 中view
232 | if (stickyLayout != null) {
233 | stickyLayout.removeAllViews();
234 | stickyLayout.startAnimation(hideStickyAnimation());
235 | }
236 | // 恢复 videoView 状态
237 | videoView.setVideoShowMode(MediaVideoView.VideoMode.NORMAL);
238 | secondRootLayout.addView(videoView);
239 | }
240 |
241 | private void addFullView() {
242 | secondRootLayout.removeAllViews();
243 | if (fullLayout != null) {
244 | fullLayout.removeAllViews();
245 |
246 | videoView.setVideoShowMode(MediaVideoView.VideoMode.FULL);
247 | fullLayout.addView(videoView);
248 | fullLayout.setVisibility(View.VISIBLE);
249 | fullLayout.startAnimation(showStickyAnimation());
250 | }
251 | }
252 |
253 | private void removeFullView() {
254 | if (fullLayout != null) {
255 | fullLayout.removeAllViews();
256 | fullLayout.setVisibility(View.GONE);
257 | fullLayout.startAnimation(hideStickyAnimation());
258 | }
259 | // 恢复 videoView 状态
260 | videoView.setVideoShowMode(MediaVideoView.VideoMode.NORMAL);
261 | secondRootLayout.removeAllViews();
262 | secondRootLayout.addView(videoView);
263 | }
264 |
265 |
266 | private void setAdapter() {
267 | if (adapter == null) {
268 | adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, getData());
269 | }
270 |
271 | listView.setAdapter(adapter);
272 | }
273 |
274 | private List getData() {
275 | List list = new ArrayList<>();
276 | for (int i = 0; i < 50; i++) {
277 | list.add("item " + i);
278 | }
279 |
280 | return list;
281 | }
282 |
283 | private Animation showStickyAnimation() {
284 |
285 | Animation mAlphaAction = new AlphaAnimation(0.0f, 1.0f);
286 | mAlphaAction.setDuration(300);
287 |
288 | return mAlphaAction;
289 | }
290 |
291 | private Animation hideStickyAnimation() {
292 |
293 | Animation mHideAlphaAction = new AlphaAnimation(1.0f, 0.0f);
294 | mHideAlphaAction.setDuration(100);
295 | return mHideAlphaAction;
296 | }
297 |
298 | @Override
299 | protected void onDestroy() {
300 | super.onDestroy();
301 | if (videoView != null) {
302 | videoView.stopPlayback();
303 | }
304 | }
305 |
306 | private void setFullScreen() {
307 | getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
308 | }
309 |
310 | private void quitFullScreen() {
311 | final WindowManager.LayoutParams attrs = getWindow().getAttributes();
312 | attrs.flags &= (~WindowManager.LayoutParams.FLAG_FULLSCREEN);
313 | getWindow().setAttributes(attrs);
314 | getWindow().clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
315 | }
316 |
317 | @Override
318 | public void onConfigurationChanged(Configuration newConfig) {
319 | super.onConfigurationChanged(newConfig);
320 | }
321 |
322 | @Override
323 | public void onBackPressed() {
324 | if (videoView.getLayoutParams().height == ViewGroup.LayoutParams.MATCH_PARENT) {
325 | zoomSmall();
326 | } else {
327 | finish();
328 | }
329 | }
330 |
331 | @Override
332 | protected void onResume() {
333 | super.onResume();
334 | }
335 |
336 | @Override
337 | protected void onPause() {
338 | super.onPause();
339 | if (videoView != null) {
340 | videoView.onPause();
341 | }
342 | }
343 |
344 | }
345 |
--------------------------------------------------------------------------------
/Sample/src/main/res/drawable-xxhdpi/arrow_left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djonce/android-stickyvideo/af178bc384c9f0df2008ce322e8af05db948e0d1/Sample/src/main/res/drawable-xxhdpi/arrow_left.png
--------------------------------------------------------------------------------
/Sample/src/main/res/drawable-xxhdpi/ds_all_button_play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djonce/android-stickyvideo/af178bc384c9f0df2008ce322e8af05db948e0d1/Sample/src/main/res/drawable-xxhdpi/ds_all_button_play.png
--------------------------------------------------------------------------------
/Sample/src/main/res/drawable-xxhdpi/ds_all_more_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djonce/android-stickyvideo/af178bc384c9f0df2008ce322e8af05db948e0d1/Sample/src/main/res/drawable-xxhdpi/ds_all_more_white.png
--------------------------------------------------------------------------------
/Sample/src/main/res/drawable-xxhdpi/ds_detail_fullscreen_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djonce/android-stickyvideo/af178bc384c9f0df2008ce322e8af05db948e0d1/Sample/src/main/res/drawable-xxhdpi/ds_detail_fullscreen_close.png
--------------------------------------------------------------------------------
/Sample/src/main/res/drawable-xxhdpi/ds_detail_fullscreen_open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djonce/android-stickyvideo/af178bc384c9f0df2008ce322e8af05db948e0d1/Sample/src/main/res/drawable-xxhdpi/ds_detail_fullscreen_open.png
--------------------------------------------------------------------------------
/Sample/src/main/res/drawable-xxhdpi/home_bg_top.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djonce/android-stickyvideo/af178bc384c9f0df2008ce322e8af05db948e0d1/Sample/src/main/res/drawable-xxhdpi/home_bg_top.png
--------------------------------------------------------------------------------
/Sample/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
16 |
17 |
22 |
23 |
33 |
34 |
41 |
42 |
43 |
44 |
51 |
52 |
58 |
--------------------------------------------------------------------------------
/Sample/src/main/res/layout/view_first_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
13 |
18 |
19 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Sample/src/main/res/layout/view_second_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
--------------------------------------------------------------------------------
/Sample/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djonce/android-stickyvideo/af178bc384c9f0df2008ce322e8af05db948e0d1/Sample/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Sample/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djonce/android-stickyvideo/af178bc384c9f0df2008ce322e8af05db948e0d1/Sample/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Sample/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djonce/android-stickyvideo/af178bc384c9f0df2008ce322e8af05db948e0d1/Sample/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Sample/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djonce/android-stickyvideo/af178bc384c9f0df2008ce322e8af05db948e0d1/Sample/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djonce/android-stickyvideo/af178bc384c9f0df2008ce322e8af05db948e0d1/Sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Sample/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/Sample/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/Sample/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/Sample/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | android-stickyvideo
3 | Sample Sticky Video
4 |
5 |
--------------------------------------------------------------------------------
/Sample/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Sample/src/test/java/com/djonce/wangj/stickyvideo/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.djonce.wangj.stickyvideo;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/Screenshot/3BAF7EB1-064E-447B-A7BA-670A20FD1ECB.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djonce/android-stickyvideo/af178bc384c9f0df2008ce322e8af05db948e0d1/Screenshot/3BAF7EB1-064E-447B-A7BA-670A20FD1ECB.png
--------------------------------------------------------------------------------
/Screenshot/6B142384-9C3D-43CC-B140-E7CA3048AEAB.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djonce/android-stickyvideo/af178bc384c9f0df2008ce322e8af05db948e0d1/Screenshot/6B142384-9C3D-43CC-B140-E7CA3048AEAB.png
--------------------------------------------------------------------------------
/Screenshot/A90E928D-A009-48CE-BF8C-F8DD080C3866.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djonce/android-stickyvideo/af178bc384c9f0df2008ce322e8af05db948e0d1/Screenshot/A90E928D-A009-48CE-BF8C-F8DD080C3866.png
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:1.5.0'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | jcenter()
18 | }
19 | }
20 |
21 | task clean(type: Delete) {
22 | delete rootProject.buildDir
23 | }
24 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djonce/android-stickyvideo/af178bc384c9f0df2008ce322e8af05db948e0d1/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Oct 21 11:34:03 PDT 2015
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/media-library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/media-library/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "23.0.2"
6 |
7 | defaultConfig {
8 | minSdkVersion 15
9 | targetSdkVersion 23
10 | versionCode 1
11 | versionName "1.0"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | }
20 |
21 | dependencies {
22 | compile fileTree(dir: 'libs', include: ['*.jar'])
23 | compile 'com.android.support:appcompat-v7:23.2.0'
24 | compile 'tv.danmaku.ijk.media:ijkplayer-java:0.5.1'
25 | compile 'tv.danmaku.ijk.media:ijkplayer-armv7a:0.5.1'
26 | }
27 |
--------------------------------------------------------------------------------
/media-library/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in C:\Users\yuanchen\AppData\Local\Android\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/media-library/src/androidTest/java/com/djonce/wangj/media/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.djonce.wangj.media;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/media-library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/media-library/src/main/java/com/djonce/wangj/media/DisplayUtils.java:
--------------------------------------------------------------------------------
1 | package com.djonce.wangj.media;
2 |
3 | import android.content.Context;
4 | import android.util.DisplayMetrics;
5 | import android.view.View;
6 | import android.view.WindowManager;
7 |
8 | /**
9 | * Created by wangj on 2016/3/16.
10 | */
11 | public class DisplayUtils {
12 |
13 | public static int SCREEN_WIDTH_PIXELS;
14 | public static int SCREEN_HEIGHT_PIXELS;
15 | public static float SCREEN_DENSITY;
16 | public static int SCREEN_WIDTH_DP;
17 | public static int SCREEN_HEIGHT_DP;
18 |
19 | public static void init(Context context) {
20 | if (context == null) {
21 | return;
22 | }
23 | DisplayMetrics dm = new DisplayMetrics();
24 | WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
25 | wm.getDefaultDisplay().getMetrics(dm);
26 | SCREEN_WIDTH_PIXELS = dm.widthPixels;
27 | SCREEN_HEIGHT_PIXELS = dm.heightPixels;
28 | SCREEN_DENSITY = dm.density;
29 | SCREEN_WIDTH_DP = (int) (SCREEN_WIDTH_PIXELS / dm.density);
30 | SCREEN_HEIGHT_DP = (int) (SCREEN_HEIGHT_PIXELS / dm.density);
31 | }
32 |
33 | public static int dp2px(float dp) {
34 | final float scale = SCREEN_DENSITY;
35 | return (int) (dp * scale + 0.5f);
36 | }
37 |
38 | public static int designedDP2px(float designedDp) {
39 | if (SCREEN_WIDTH_DP != 320) {
40 | designedDp = designedDp * SCREEN_WIDTH_DP / 320f;
41 | }
42 | return dp2px(designedDp);
43 | }
44 |
45 | public static void setPadding(final View view, float left, float top, float right, float bottom) {
46 | view.setPadding(designedDP2px(left), dp2px(top), designedDP2px(right), dp2px(bottom));
47 | }
48 |
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/media-library/src/main/java/com/djonce/wangj/media/IMediaController.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Zhang Rui
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.djonce.wangj.media;
18 |
19 | import android.view.View;
20 | import android.widget.MediaController;
21 |
22 | public interface IMediaController {
23 | void hide();
24 |
25 | boolean isShowing();
26 |
27 | void setAnchorView(View view);
28 |
29 | void setEnabled(boolean enabled);
30 |
31 | void setMediaPlayer(MediaController.MediaPlayerControl player);
32 |
33 | void show(int timeout);
34 |
35 | void show();
36 |
37 | void showProgress(boolean isShow);
38 | //----------
39 | // Extends
40 | //----------
41 | void showOnce(View view);
42 | }
43 |
--------------------------------------------------------------------------------
/media-library/src/main/java/com/djonce/wangj/media/IRenderView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Zhang Rui
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.djonce.wangj.media;
18 |
19 | import android.graphics.SurfaceTexture;
20 | import android.support.annotation.NonNull;
21 | import android.support.annotation.Nullable;
22 | import android.view.Surface;
23 | import android.view.SurfaceHolder;
24 | import android.view.View;
25 |
26 | import tv.danmaku.ijk.media.player.IMediaPlayer;
27 |
28 | public interface IRenderView {
29 | int AR_ASPECT_FIT_PARENT = 0; // without clip
30 | int AR_ASPECT_FILL_PARENT = 1; // may clip
31 | int AR_ASPECT_WRAP_CONTENT = 2;
32 | int AR_MATCH_PARENT = 3;
33 | int AR_16_9_FIT_PARENT = 4;
34 | int AR_4_3_FIT_PARENT = 5;
35 |
36 | View getView();
37 |
38 | boolean shouldWaitForResize();
39 |
40 | void setVideoSize(int videoWidth, int videoHeight);
41 |
42 | void setVideoSampleAspectRatio(int videoSarNum, int videoSarDen);
43 |
44 | void setVideoRotation(int degree);
45 |
46 | void setAspectRatio(int aspectRatio);
47 |
48 | void addRenderCallback(@NonNull IRenderCallback callback);
49 |
50 | void removeRenderCallback(@NonNull IRenderCallback callback);
51 |
52 | interface ISurfaceHolder {
53 | void bindToMediaPlayer(IMediaPlayer mp);
54 |
55 | @NonNull
56 | IRenderView getRenderView();
57 |
58 | @Nullable
59 | SurfaceHolder getSurfaceHolder();
60 |
61 | @Nullable
62 | Surface openSurface();
63 |
64 | @Nullable
65 | SurfaceTexture getSurfaceTexture();
66 | }
67 |
68 | interface IRenderCallback {
69 | /**
70 | * @param holder
71 | * @param width could be 0
72 | * @param height could be 0
73 | */
74 | void onSurfaceCreated(@NonNull ISurfaceHolder holder, int width, int height);
75 |
76 | /**
77 | * @param holder
78 | * @param format could be 0
79 | * @param width
80 | * @param height
81 | */
82 | void onSurfaceChanged(@NonNull ISurfaceHolder holder, int format, int width, int height);
83 |
84 | void onSurfaceDestroyed(@NonNull ISurfaceHolder holder);
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/media-library/src/main/java/com/djonce/wangj/media/MeasureHelper.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Zhang Rui
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.djonce.wangj.media;
18 |
19 | import android.content.Context;
20 | import android.support.annotation.NonNull;
21 | import android.view.View;
22 |
23 | import java.lang.ref.WeakReference;
24 |
25 | public final class MeasureHelper {
26 | private WeakReference mWeakView;
27 |
28 | private int mVideoWidth;
29 | private int mVideoHeight;
30 | private int mVideoSarNum;
31 | private int mVideoSarDen;
32 |
33 | private int mVideoRotationDegree;
34 |
35 | private int mMeasuredWidth;
36 | private int mMeasuredHeight;
37 |
38 | private int mCurrentAspectRatio = IRenderView.AR_ASPECT_FIT_PARENT;
39 |
40 | public MeasureHelper(View view) {
41 | mWeakView = new WeakReference(view);
42 | }
43 |
44 | public View getView() {
45 | if (mWeakView == null)
46 | return null;
47 | return mWeakView.get();
48 | }
49 |
50 | public void setVideoSize(int videoWidth, int videoHeight) {
51 | mVideoWidth = videoWidth;
52 | mVideoHeight = videoHeight;
53 | }
54 |
55 | public void setVideoSampleAspectRatio(int videoSarNum, int videoSarDen) {
56 | mVideoSarNum = videoSarNum;
57 | mVideoSarDen = videoSarDen;
58 | }
59 |
60 | public void setVideoRotation(int videoRotationDegree) {
61 | mVideoRotationDegree = videoRotationDegree;
62 | }
63 |
64 | /**
65 | * Must be called by View.onMeasure(int, int)
66 | *
67 | * @param widthMeasureSpec
68 | * @param heightMeasureSpec
69 | */
70 | public void doMeasure(int widthMeasureSpec, int heightMeasureSpec) {
71 | //Log.i("@@@@", "onMeasure(" + MeasureSpec.toString(widthMeasureSpec) + ", "
72 | // + MeasureSpec.toString(heightMeasureSpec) + ")");
73 | if (mVideoRotationDegree == 90 || mVideoRotationDegree == 270) {
74 | int tempSpec = widthMeasureSpec;
75 | widthMeasureSpec = heightMeasureSpec;
76 | heightMeasureSpec = tempSpec;
77 | }
78 |
79 | int width = View.getDefaultSize(mVideoWidth, widthMeasureSpec);
80 | int height = View.getDefaultSize(mVideoHeight, heightMeasureSpec);
81 | if (mCurrentAspectRatio == IRenderView.AR_MATCH_PARENT) {
82 | width = widthMeasureSpec;
83 | height = heightMeasureSpec;
84 | } else if (mVideoWidth > 0 && mVideoHeight > 0) {
85 | int widthSpecMode = View.MeasureSpec.getMode(widthMeasureSpec);
86 | int widthSpecSize = View.MeasureSpec.getSize(widthMeasureSpec);
87 | int heightSpecMode = View.MeasureSpec.getMode(heightMeasureSpec);
88 | int heightSpecSize = View.MeasureSpec.getSize(heightMeasureSpec);
89 |
90 | if (widthSpecMode == View.MeasureSpec.AT_MOST && heightSpecMode == View.MeasureSpec.AT_MOST) {
91 | float specAspectRatio = (float) widthSpecSize / (float) heightSpecSize;
92 | float displayAspectRatio;
93 | switch (mCurrentAspectRatio) {
94 | case IRenderView.AR_16_9_FIT_PARENT:
95 | displayAspectRatio = 16.0f / 9.0f;
96 | if (mVideoRotationDegree == 90 || mVideoRotationDegree == 270)
97 | displayAspectRatio = 1.0f / displayAspectRatio;
98 | break;
99 | case IRenderView.AR_4_3_FIT_PARENT:
100 | displayAspectRatio = 4.0f / 3.0f;
101 | if (mVideoRotationDegree == 90 || mVideoRotationDegree == 270)
102 | displayAspectRatio = 1.0f / displayAspectRatio;
103 | break;
104 | case IRenderView.AR_ASPECT_FIT_PARENT:
105 | case IRenderView.AR_ASPECT_FILL_PARENT:
106 | case IRenderView.AR_ASPECT_WRAP_CONTENT:
107 | default:
108 | displayAspectRatio = (float) mVideoWidth / (float) mVideoHeight;
109 | if (mVideoSarNum > 0 && mVideoSarDen > 0)
110 | displayAspectRatio = displayAspectRatio * mVideoSarNum / mVideoSarDen;
111 | break;
112 | }
113 | boolean shouldBeWider = displayAspectRatio > specAspectRatio;
114 |
115 | switch (mCurrentAspectRatio) {
116 | case IRenderView.AR_ASPECT_FIT_PARENT:
117 | case IRenderView.AR_16_9_FIT_PARENT:
118 | case IRenderView.AR_4_3_FIT_PARENT:
119 | if (shouldBeWider) {
120 | // too wide, fix width
121 | width = widthSpecSize;
122 | height = (int) (width / displayAspectRatio);
123 | } else {
124 | // too high, fix height
125 | height = heightSpecSize;
126 | width = (int) (height * displayAspectRatio);
127 | }
128 | break;
129 | case IRenderView.AR_ASPECT_FILL_PARENT:
130 | if (shouldBeWider) {
131 | // not high enough, fix height
132 | height = heightSpecSize;
133 | width = (int) (height * displayAspectRatio);
134 | } else {
135 | // not wide enough, fix width
136 | width = widthSpecSize;
137 | height = (int) (width / displayAspectRatio);
138 | }
139 | break;
140 | case IRenderView.AR_ASPECT_WRAP_CONTENT:
141 | default:
142 | if (shouldBeWider) {
143 | // too wide, fix width
144 | width = Math.min(mVideoWidth, widthSpecSize);
145 | height = (int) (width / displayAspectRatio);
146 | } else {
147 | // too high, fix height
148 | height = Math.min(mVideoHeight, heightSpecSize);
149 | width = (int) (height * displayAspectRatio);
150 | }
151 | break;
152 | }
153 | } else if (widthSpecMode == View.MeasureSpec.EXACTLY && heightSpecMode == View.MeasureSpec.EXACTLY) {
154 | // the size is fixed
155 | width = widthSpecSize;
156 | height = heightSpecSize;
157 |
158 | // for compatibility, we adjust size based on aspect ratio
159 | if (mVideoWidth * height < width * mVideoHeight) {
160 | //Log.i("@@@", "image too wide, correcting");
161 | width = height * mVideoWidth / mVideoHeight;
162 | } else if (mVideoWidth * height > width * mVideoHeight) {
163 | //Log.i("@@@", "image too tall, correcting");
164 | height = width * mVideoHeight / mVideoWidth;
165 | }
166 | } else if (widthSpecMode == View.MeasureSpec.EXACTLY) {
167 | // only the width is fixed, adjust the height to match aspect ratio if possible
168 | width = widthSpecSize;
169 | height = width * mVideoHeight / mVideoWidth;
170 | if (heightSpecMode == View.MeasureSpec.AT_MOST && height > heightSpecSize) {
171 | // couldn't match aspect ratio within the constraints
172 | height = heightSpecSize;
173 | }
174 | } else if (heightSpecMode == View.MeasureSpec.EXACTLY) {
175 | // only the height is fixed, adjust the width to match aspect ratio if possible
176 | height = heightSpecSize;
177 | width = height * mVideoWidth / mVideoHeight;
178 | if (widthSpecMode == View.MeasureSpec.AT_MOST && width > widthSpecSize) {
179 | // couldn't match aspect ratio within the constraints
180 | width = widthSpecSize;
181 | }
182 | } else {
183 | // neither the width nor the height are fixed, try to use actual video size
184 | width = mVideoWidth;
185 | height = mVideoHeight;
186 | if (heightSpecMode == View.MeasureSpec.AT_MOST && height > heightSpecSize) {
187 | // too tall, decrease both width and height
188 | height = heightSpecSize;
189 | width = height * mVideoWidth / mVideoHeight;
190 | }
191 | if (widthSpecMode == View.MeasureSpec.AT_MOST && width > widthSpecSize) {
192 | // too wide, decrease both width and height
193 | width = widthSpecSize;
194 | height = width * mVideoHeight / mVideoWidth;
195 | }
196 | }
197 | } else {
198 | // no size yet, just adopt the given spec sizes
199 | }
200 |
201 | mMeasuredWidth = width;
202 | mMeasuredHeight = height;
203 | }
204 |
205 | public int getMeasuredWidth() {
206 | return mMeasuredWidth;
207 | }
208 |
209 | public int getMeasuredHeight() {
210 | return mMeasuredHeight;
211 | }
212 |
213 | public void setAspectRatio(int aspectRatio) {
214 | mCurrentAspectRatio = aspectRatio;
215 | }
216 |
217 | }
218 |
--------------------------------------------------------------------------------
/media-library/src/main/java/com/djonce/wangj/media/MediaPlayerCompat.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Zhang Rui
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.djonce.wangj.media;
18 |
19 | import tv.danmaku.ijk.media.player.IMediaPlayer;
20 | import tv.danmaku.ijk.media.player.IjkMediaPlayer;
21 | import tv.danmaku.ijk.media.player.MediaPlayerProxy;
22 | import tv.danmaku.ijk.media.player.TextureMediaPlayer;
23 |
24 | public class MediaPlayerCompat {
25 | public static String getName(IMediaPlayer mp) {
26 | if (mp == null) {
27 | return "null";
28 | } else if (mp instanceof TextureMediaPlayer) {
29 | StringBuilder sb = new StringBuilder("TextureMediaPlayer <");
30 | IMediaPlayer internalMediaPlayer = ((TextureMediaPlayer) mp).getInternalMediaPlayer();
31 | if (internalMediaPlayer == null) {
32 | sb.append("null>");
33 | } else {
34 | sb.append(internalMediaPlayer.getClass().getSimpleName());
35 | sb.append(">");
36 | }
37 | return sb.toString();
38 | } else {
39 | return mp.getClass().getSimpleName();
40 | }
41 | }
42 |
43 | public static IjkMediaPlayer getIjkMediaPlayer(IMediaPlayer mp) {
44 | IjkMediaPlayer ijkMediaPlayer = null;
45 | if (mp == null) {
46 | return null;
47 | } if (mp instanceof IjkMediaPlayer) {
48 | ijkMediaPlayer = (IjkMediaPlayer) mp;
49 | } else if (mp instanceof MediaPlayerProxy && ((MediaPlayerProxy) mp).getInternalMediaPlayer() instanceof IjkMediaPlayer) {
50 | ijkMediaPlayer = (IjkMediaPlayer) ((MediaPlayerProxy) mp).getInternalMediaPlayer();
51 | }
52 | return ijkMediaPlayer;
53 | }
54 |
55 | public static void selectTrack(IMediaPlayer mp, int stream) {
56 | IjkMediaPlayer ijkMediaPlayer = getIjkMediaPlayer(mp);
57 | if (ijkMediaPlayer == null)
58 | return;
59 | ijkMediaPlayer.selectTrack(stream);
60 | }
61 |
62 | public static void deselectTrack(IMediaPlayer mp, int stream) {
63 | IjkMediaPlayer ijkMediaPlayer = getIjkMediaPlayer(mp);
64 | if (ijkMediaPlayer == null)
65 | return;
66 | ijkMediaPlayer.deselectTrack(stream);
67 | }
68 |
69 | public static int getSelectedTrack(IMediaPlayer mp, int trackType) {
70 | IjkMediaPlayer ijkMediaPlayer = getIjkMediaPlayer(mp);
71 | if (ijkMediaPlayer == null)
72 | return -1;
73 | return ijkMediaPlayer.getSelectedTrack(trackType);
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/media-library/src/main/java/com/djonce/wangj/media/MediaPlayerController.java:
--------------------------------------------------------------------------------
1 | package com.djonce.wangj.media;
2 |
3 | import android.content.Context;
4 | import android.os.Handler;
5 | import android.os.Message;
6 | import android.util.AttributeSet;
7 | import android.util.Log;
8 | import android.view.LayoutInflater;
9 | import android.view.View;
10 | import android.widget.FrameLayout;
11 | import android.widget.ImageView;
12 | import android.widget.LinearLayout;
13 | import android.widget.MediaController;
14 | import android.widget.ProgressBar;
15 | import android.widget.SeekBar;
16 | import android.widget.TextView;
17 |
18 | import java.util.Formatter;
19 | import java.util.Locale;
20 |
21 | /**
22 | * 播放器控制面板
23 | *
24 | * Created by wangj on 2016/5/23.
25 | */
26 | public class MediaPlayerController extends FrameLayout implements IMediaController {
27 |
28 | private static final String TAG = MediaPlayerController.class.getSimpleName();
29 | private MediaController.MediaPlayerControl mPlayer;
30 | private Context mContext;
31 | private ImageView mVideoPlayBtn;
32 | private SeekBar mVideoSeekBar;
33 | private TextView mVideoTotalTime;
34 | private ImageView mIvFullscreenOpen;
35 | private TextView mVideoPlayTimeLeft;
36 | private ProgressBar mVideoMiniProgressbar;
37 | private ProgressBar mBufferProgressBar; // 缓冲进度
38 | private LinearLayout mVideoControlPanel;
39 |
40 | private boolean mDragging;
41 | private boolean mShowing;
42 | private static final int sDefaultTimeout = 3000;
43 | private static final int sMaxTimeout = 3600000;
44 | private static final int FADE_OUT = 1;
45 | private static final int SHOW_PROGRESS = 2;
46 | private StringBuilder mFormatBuilder;
47 | private Formatter mFormatter;
48 |
49 | private OnPlayerControllerListener listener;
50 |
51 | public MediaPlayerController(Context context) {
52 | super(context);
53 | this.mContext = context;
54 | initControllerView();
55 | }
56 |
57 | public MediaPlayerController(Context context, AttributeSet attrs) {
58 | super(context, attrs);
59 | this.mContext = context;
60 | initControllerView();
61 | }
62 |
63 | public MediaPlayerController(Context context, AttributeSet attrs, int defStyleAttr) {
64 | super(context, attrs, defStyleAttr);
65 | this.mContext = context;
66 | initControllerView();
67 | }
68 |
69 | private void initControllerView() {
70 | View mContainer = LayoutInflater.from(mContext).inflate(R.layout.media_player_controller_view, null);
71 | mVideoPlayBtn = (ImageView) mContainer.findViewById(R.id.video_play_btn);
72 | mVideoSeekBar = (SeekBar) mContainer.findViewById(R.id.video_seek_bar);
73 | mVideoPlayTimeLeft = (TextView) mContainer.findViewById(R.id.video_play_time_left);
74 | mVideoTotalTime = (TextView) mContainer.findViewById(R.id.video_total_time);
75 | mIvFullscreenOpen = (ImageView) mContainer.findViewById(R.id.iv_fullscreen_open);
76 | mVideoMiniProgressbar = (ProgressBar) mContainer.findViewById(R.id.video_mini_progressbar);
77 | mVideoControlPanel = (LinearLayout) mContainer.findViewById(R.id.video_control_panel);
78 |
79 | mBufferProgressBar = (ProgressBar) mContainer.findViewById(R.id.video_buffer_progress_bar);
80 |
81 | mFormatBuilder = new StringBuilder();
82 | mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());
83 |
84 | mVideoSeekBar.setOnSeekBarChangeListener(mSeekListener);
85 | mVideoPlayBtn.setOnClickListener(mPauseListener);
86 | mIvFullscreenOpen.setOnClickListener(mFullScreenOpen);
87 |
88 | addView(mContainer);
89 | }
90 |
91 | public void setOnPlayerControllerListener(OnPlayerControllerListener listener) {
92 | this.listener = listener;
93 | }
94 |
95 |
96 | @Override
97 | public void hide() {
98 | hideOprateViews();
99 | }
100 |
101 | public void hideOprateViews() {
102 | mShowing = false;
103 | mVideoMiniProgressbar.setVisibility(VISIBLE);
104 | mHandler.sendEmptyMessage(SHOW_PROGRESS);
105 |
106 | mVideoPlayBtn.setVisibility(GONE);
107 | // 隐藏开始按钮 显示seek bar, 总时间, 已播时长, 放大按钮
108 | mVideoControlPanel.setVisibility(GONE);
109 | }
110 |
111 | @Override
112 | public boolean isShowing() {
113 | return mShowing;
114 | }
115 |
116 | @Override
117 | public void setAnchorView(View view) {
118 |
119 | }
120 |
121 | @Override
122 | public void setEnabled(boolean enabled) {
123 | // 当前控件是否可用
124 | if (enabled) {
125 |
126 | }else {
127 |
128 | }
129 |
130 | }
131 |
132 | @Override
133 | public void setMediaPlayer(MediaController.MediaPlayerControl player) {
134 | mPlayer = player;
135 | }
136 |
137 | @Override
138 | public void show() {
139 | show(sDefaultTimeout);
140 | }
141 |
142 | @Override
143 | public void show(int timeout) {
144 | mShowing = true;
145 |
146 | if (mPlayer != null && mPlayer.isPlaying()) {
147 | mVideoPlayBtn.setVisibility(GONE);
148 | }
149 |
150 | if(timeout > 30000) {
151 | mVideoPlayBtn.setVisibility(VISIBLE);
152 | try {
153 | mHandler.removeMessages(SHOW_PROGRESS);
154 | } catch (IllegalArgumentException ex) {
155 | Log.w("MediaController", "already removed");
156 | }
157 | }
158 | if (mPlayer != null && mPlayer instanceof MediaVideoView) {
159 | switch (((MediaVideoView)mPlayer).getVideoMode()) {
160 | case SMALL:
161 | Log.e(TAG, "-- SMALL --");
162 |
163 | if (!mPlayer.isPlaying()) {
164 | mVideoPlayBtn.setVisibility(VISIBLE);
165 | }
166 |
167 | // 隐藏 mini bar , 停止更新mini bar
168 | mVideoMiniProgressbar.setVisibility(VISIBLE);
169 | mVideoControlPanel.setVisibility(GONE);
170 |
171 | break;
172 | case NORMAL:
173 | Log.e(TAG, "-- NORMAL --");
174 | // 当前播放的状态
175 | mIvFullscreenOpen.setImageResource(R.drawable.ds_detail_fullscreen_open);
176 | // 隐藏 mini bar , 停止更新mini bar
177 | mVideoMiniProgressbar.setVisibility(GONE);
178 | mVideoControlPanel.setVisibility(VISIBLE);
179 | break;
180 | case FULL:
181 | Log.e(TAG, "-- FULL --");
182 |
183 | // 隐藏 mini bar , 停止更新mini bar
184 | mVideoMiniProgressbar.setVisibility(GONE);
185 | mVideoControlPanel.setVisibility(VISIBLE);
186 | mIvFullscreenOpen.setImageResource(R.drawable.ds_detail_fullscreen_close);
187 | break;
188 | }
189 | }
190 |
191 | if (timeout != 0) {
192 | mHandler.removeMessages(FADE_OUT);
193 | Message msg = mHandler.obtainMessage(FADE_OUT);
194 | mHandler.sendMessageDelayed(msg, timeout);
195 | }
196 | }
197 |
198 |
199 | @Override
200 | public void showProgress(boolean isShow) {
201 | if(isShow) {
202 | // 显示加载进度
203 | mBufferProgressBar.setVisibility(VISIBLE);
204 | }else {
205 | // 关闭进度框
206 | mBufferProgressBar.setVisibility(GONE);
207 | }
208 | }
209 |
210 | @Override
211 | public void showOnce(View view) {
212 |
213 | }
214 |
215 | private final Handler mHandler = new Handler() {
216 | @Override
217 | public void handleMessage(Message msg) {
218 | int pos;
219 | switch (msg.what) {
220 | case FADE_OUT:
221 | hide();
222 | break;
223 | case SHOW_PROGRESS:
224 | pos = setProgress();
225 | setSeekBarProgress();
226 | if (!mDragging && mPlayer.isPlaying()) {
227 | msg = obtainMessage(SHOW_PROGRESS);
228 | sendMessageDelayed(msg, 1000 - (pos % 1000));
229 | }
230 | break;
231 | }
232 | }
233 | };
234 |
235 | /**
236 | * 格式化时长
237 | * @param timeMs
238 | * @return
239 | */
240 | private String stringForTime(int timeMs) {
241 | int totalSeconds = timeMs / 1000;
242 |
243 | int seconds = totalSeconds % 60;
244 | int minutes = (totalSeconds / 60) % 60;
245 | int hours = totalSeconds / 3600;
246 |
247 | mFormatBuilder.setLength(0);
248 | if (hours > 0) {
249 | return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString();
250 | } else {
251 | return mFormatter.format("%02d:%02d", minutes, seconds).toString();
252 | }
253 | }
254 |
255 | private int setProgress() {
256 | if (mPlayer == null || mDragging) {
257 | return 0;
258 | }
259 | int position = mPlayer.getCurrentPosition();
260 | int duration = mPlayer.getDuration();
261 | if (mVideoMiniProgressbar != null) {
262 | if (duration > 0) {
263 | // use long to avoid overflow
264 | long pos = 1000L * position / duration;
265 | mVideoMiniProgressbar.setProgress((int) pos);
266 | }
267 | int percent = mPlayer.getBufferPercentage();
268 | mVideoMiniProgressbar.setSecondaryProgress(percent * 10);
269 | }
270 |
271 | return position;
272 | }
273 |
274 | /**
275 | * 暂停 设置seek bar
276 | *
277 | * @return position
278 | */
279 | private int setSeekBarProgress() {
280 | if (mPlayer == null || mDragging) {
281 | return 0;
282 | }
283 | int position = mPlayer.getCurrentPosition();
284 | int duration = mPlayer.getDuration();
285 | if (mVideoSeekBar != null) {
286 | if (duration > 0) {
287 | // use long to avoid overflow
288 | long pos = 1000L * position / duration;
289 | mVideoSeekBar.setProgress((int) pos);
290 | }
291 | int percent = mPlayer.getBufferPercentage();
292 | mVideoSeekBar.setSecondaryProgress(percent * 10);
293 | }
294 |
295 | if (mVideoTotalTime != null)
296 | mVideoTotalTime.setText(stringForTime(duration));
297 | if (mVideoPlayTimeLeft != null)
298 | mVideoPlayTimeLeft.setText(stringForTime(position));
299 |
300 | return position;
301 | }
302 |
303 | private final OnClickListener mPauseListener = new OnClickListener() {
304 | @Override
305 | public void onClick(View v) {
306 | autoStartPlay();
307 | }
308 | };
309 |
310 | private final OnClickListener mFullScreenOpen = new OnClickListener() {
311 | @Override
312 | public void onClick(View v) {
313 | // 执行 放大 缩小的功能即可, 显示的功能放在 controller show的时候处理
314 |
315 | if (mPlayer != null && mPlayer instanceof MediaVideoView) {
316 | switch (((MediaVideoView)mPlayer).getVideoMode()) {
317 | case SMALL:
318 |
319 | break;
320 | case NORMAL:
321 | if(listener != null) {
322 | listener.onZoomBig();
323 | }
324 | break;
325 | case FULL:
326 | if(listener != null) {
327 | listener.onZoomSmall();
328 | }
329 | break;
330 | }
331 | }
332 |
333 | }
334 | };
335 |
336 | private final SeekBar.OnSeekBarChangeListener mSeekListener = new SeekBar.OnSeekBarChangeListener() {
337 | @Override
338 | public void onStartTrackingTouch(SeekBar bar) {
339 | show(3600000);
340 |
341 | mDragging = true;
342 |
343 | // By removing these pending progress messages we make sure
344 | // that a) we won't update the progress while the user adjusts
345 | // the seekbar and b) once the user is done dragging the thumb
346 | // we will post one of these messages to the queue again and
347 | // this ensures that there will be exactly one message queued up.
348 | mHandler.removeMessages(SHOW_PROGRESS);
349 | }
350 |
351 | @Override
352 | public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) {
353 | if (!fromuser) {
354 | // We're not interested in programmatically generated changes to
355 | // the progress bar's position.
356 | return;
357 | }
358 |
359 | Log.e(TAG, "progress :" + progress + "fromuser:" + fromuser);
360 | long duration = mPlayer.getDuration();
361 | long newposition = (duration * progress) / mVideoSeekBar.getMax();
362 | mPlayer.seekTo((int) newposition);
363 | if (mVideoPlayTimeLeft != null){
364 | mVideoPlayTimeLeft.setText(stringForTime((int) newposition));
365 | }
366 | }
367 |
368 | @Override
369 | public void onStopTrackingTouch(SeekBar bar) {
370 | mDragging = false;
371 | setSeekBarProgress();
372 |
373 | show(mPlayer.isPlaying() ? sDefaultTimeout: sMaxTimeout);
374 |
375 | if (listener != null) {
376 | listener.onSeekPlay(mVideoSeekBar.getProgress(), mPlayer.getCurrentPosition());
377 | }
378 |
379 | autoStartPlay();
380 | }
381 | };
382 |
383 |
384 | private void doVideoResume() {
385 |
386 | if(mPlayer != null) {
387 | mPlayer.start();
388 | // 隐藏播放按钮
389 |
390 | show();
391 |
392 | mVideoPlayBtn.setVisibility(GONE);
393 | mVideoSeekBar.invalidate();
394 | mVideoMiniProgressbar.invalidate();
395 | setSeekBarProgress();
396 | }
397 | }
398 |
399 | public void autoStartPlay() {
400 | doVideoResume();
401 | if (listener != null) {
402 | listener.onPlay();
403 | }
404 | }
405 |
406 | public void onPause() {
407 | if (mPlayer != null && mPlayer.isPlaying()) {
408 | mPlayer.pause();
409 | // 显示播放按钮
410 | show(sMaxTimeout);
411 | }
412 |
413 | if(listener != null) {
414 | listener.onPause();
415 | }
416 | }
417 |
418 | public void onResume() {
419 | if (mPlayer != null && !mPlayer.isPlaying()) {
420 | autoStartPlay();
421 | }
422 |
423 | if(listener != null) {
424 | listener.onResume();
425 | }
426 | }
427 |
428 | public interface OnPlayerControllerListener {
429 |
430 | void onPlay(); // 播放
431 |
432 | void onSeekPlay(int position, long duration); // 调整进度
433 |
434 | void onPause(); // 暂停
435 |
436 | void onResume(); // 开始
437 |
438 | void onZoomBig();
439 |
440 | void onZoomSmall();
441 |
442 | }
443 |
444 | }
445 |
--------------------------------------------------------------------------------
/media-library/src/main/java/com/djonce/wangj/media/MediaPlayerView.java:
--------------------------------------------------------------------------------
1 | package com.djonce.wangj.media;
2 |
3 | import android.annotation.TargetApi;
4 | import android.content.Context;
5 | import android.os.Build;
6 | import android.text.TextUtils;
7 | import android.util.AttributeSet;
8 | import android.util.Log;
9 | import android.view.LayoutInflater;
10 | import android.view.View;
11 | import android.widget.ImageView;
12 | import android.widget.RelativeLayout;
13 | import android.widget.Toast;
14 |
15 | import tv.danmaku.ijk.media.player.IMediaPlayer;
16 |
17 |
18 | /**
19 | * 媒体播放view
20 | * 由MediaViewView 与 MediaPlayerController组成
21 | *
22 | * Created by wangj on 2016/5/23.
23 | */
24 | public class MediaPlayerView extends RelativeLayout {
25 | private static final String TAG = MediaPlayerView.class.getSimpleName();
26 | private Context mContext;
27 | private ImageView mImageView; // 现在视频首帧图片,暂停时显示当前暂停的图片
28 | private MediaVideoView mediaVideoView;
29 | private MediaPlayerController mediaPlayerController;
30 |
31 | private OnLoadImageListener loadImageListener;
32 | private OnMediaPlayerStateListener playerStateListener;
33 |
34 | public MediaPlayerView(Context context) {
35 | super(context);
36 | initView(context);
37 | }
38 |
39 | public MediaPlayerView(Context context, AttributeSet attrs) {
40 | super(context, attrs);
41 | initView(context);
42 | }
43 |
44 | public MediaPlayerView(Context context, AttributeSet attrs, int defStyleAttr) {
45 | super(context, attrs, defStyleAttr);
46 | initView(context);
47 | }
48 |
49 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
50 | public MediaPlayerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
51 | super(context, attrs, defStyleAttr, defStyleRes);
52 | initView(context);
53 | }
54 |
55 | /**
56 | * 设置控制面板 监听事件
57 | * @param playerControllerListener
58 | */
59 | public void setOnPlayerControllerListener(MediaPlayerController.OnPlayerControllerListener playerControllerListener) {
60 | if (playerControllerListener != null) {
61 | mediaPlayerController.setOnPlayerControllerListener(playerControllerListener);
62 | }
63 | }
64 |
65 | /**
66 | * 设置播放器播放状态监听
67 | * @param playerStateListener
68 | */
69 | public void setPlayerStateListener(OnMediaPlayerStateListener playerStateListener) {
70 | this.playerStateListener = playerStateListener;
71 | }
72 |
73 | private void initView(Context context) {
74 | this.mContext = context;
75 |
76 | View mContainer = LayoutInflater.from(context).inflate(R.layout.media_player_view, null);
77 | mediaVideoView = (MediaVideoView) mContainer.findViewById(R.id.media_video_view);
78 | mediaPlayerController = (MediaPlayerController) mContainer.findViewById(R.id.media_player_controller);
79 | mImageView = (ImageView) mContainer.findViewById(R.id.media_video_img);
80 | mediaPlayerController.setMediaPlayer(mediaVideoView);
81 | mediaVideoView.setMediaController(mediaPlayerController);
82 |
83 | addView(mContainer);
84 | setListener();
85 | }
86 |
87 | private void setListener() {
88 | mediaVideoView.setOnPreparedListener(new IMediaPlayer.OnPreparedListener() {
89 | @Override
90 | public void onPrepared(IMediaPlayer mp) {
91 |
92 | }
93 | });
94 |
95 | mediaVideoView.setOnCompletionListener(new IMediaPlayer.OnCompletionListener() {
96 | @Override
97 | public void onCompletion(IMediaPlayer mp) {
98 | if (mediaPlayerController != null) {
99 | mediaPlayerController.show(360000);
100 | }
101 | if (playerStateListener != null) {
102 | playerStateListener.onCompleted(mp);
103 | }
104 | }
105 | });
106 |
107 | mediaVideoView.setOnInfoListener(new IMediaPlayer.OnInfoListener() {
108 | @Override
109 | public boolean onInfo(IMediaPlayer mp, int what, int extra) {
110 |
111 | switch (what) {
112 | case IMediaPlayer.MEDIA_ERROR_TIMED_OUT:
113 | Toast.makeText(mContext, "亲,请检查下您的网络状况~", Toast.LENGTH_SHORT).show();
114 | break;
115 | }
116 |
117 | switch (what) {
118 | case IMediaPlayer.MEDIA_INFO_VIDEO_TRACK_LAGGING:
119 | Log.d(TAG, "MEDIA_INFO_VIDEO_TRACK_LAGGING:");
120 | break;
121 | case IMediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START:
122 | Log.d(TAG, "MEDIA_INFO_VIDEO_RENDERING_START:");
123 | // 第一次播放 开始播放
124 | if (playerStateListener != null) {
125 | playerStateListener.onFirstPlay();
126 | }
127 | break;
128 | case IMediaPlayer.MEDIA_INFO_BUFFERING_START:
129 | Log.d(TAG, "MEDIA_INFO_BUFFERING_START:");
130 | // 在数据缓冲
131 | if (playerStateListener != null) {
132 | playerStateListener.onBufferStart();
133 | }
134 | break;
135 | case IMediaPlayer.MEDIA_INFO_BUFFERING_END:
136 | Log.d(TAG, "MEDIA_INFO_BUFFERING_END:");
137 |
138 | if (playerStateListener != null) {
139 | playerStateListener.onBufferEnd();
140 | }
141 | break;
142 | case IMediaPlayer.MEDIA_INFO_NETWORK_BANDWIDTH:
143 | Log.d(TAG, "MEDIA_INFO_NETWORK_BANDWIDTH: " + extra);
144 | break;
145 | case IMediaPlayer.MEDIA_INFO_BAD_INTERLEAVING:
146 | Log.d(TAG, "MEDIA_INFO_BAD_INTERLEAVING:");
147 | break;
148 | case IMediaPlayer.MEDIA_INFO_NOT_SEEKABLE:
149 | Log.d(TAG, "MEDIA_INFO_NOT_SEEKABLE:");
150 | // 不可seek
151 | break;
152 | case IMediaPlayer.MEDIA_INFO_METADATA_UPDATE:
153 | Log.d(TAG, "MEDIA_INFO_METADATA_UPDATE:");
154 | break;
155 | case IMediaPlayer.MEDIA_INFO_UNSUPPORTED_SUBTITLE:
156 | Log.d(TAG, "MEDIA_INFO_UNSUPPORTED_SUBTITLE:");
157 | break;
158 | case IMediaPlayer.MEDIA_INFO_SUBTITLE_TIMED_OUT:
159 | Log.d(TAG, "MEDIA_INFO_SUBTITLE_TIMED_OUT:");
160 | break;
161 | case IMediaPlayer.MEDIA_INFO_VIDEO_ROTATION_CHANGED:
162 |
163 | Log.d(TAG, "MEDIA_INFO_VIDEO_ROTATION_CHANGED: " + extra);
164 |
165 | break;
166 | case IMediaPlayer.MEDIA_INFO_AUDIO_RENDERING_START:
167 | Log.d(TAG, "MEDIA_INFO_AUDIO_RENDERING_START:");
168 | break;
169 | }
170 | return true;
171 | }
172 |
173 | });
174 |
175 | mediaVideoView.setOnErrorListener(new IMediaPlayer.OnErrorListener() {
176 | @Override
177 | public boolean onError(IMediaPlayer mp, int what, int extra) {
178 | // 显示播放按钮
179 |
180 | // 处理异常
181 | if (playerStateListener != null) {
182 | playerStateListener.onError(mp, what, extra);
183 | }
184 | return false;
185 | }
186 | });
187 |
188 | }
189 |
190 |
191 | public void setVideoShowMode(MediaVideoView.VideoMode mode) {
192 | switch (mode) {
193 | case NORMAL:
194 | LayoutParams normal = (LayoutParams) getLayoutParams();
195 | normal.width = DisplayUtils.SCREEN_WIDTH_PIXELS;
196 | normal.height = DisplayUtils.SCREEN_WIDTH_PIXELS * 9 / 16;
197 |
198 | if (mediaVideoView != null) {
199 | mediaVideoView.setVideoMode(MediaVideoView.VideoMode.NORMAL);
200 | }
201 |
202 | if (mediaPlayerController != null) {
203 | mediaPlayerController.show();
204 | }
205 |
206 | break;
207 | case SMALL:
208 | LayoutParams small = (LayoutParams) getLayoutParams();
209 | small.width = DisplayUtils.SCREEN_WIDTH_PIXELS / 2;
210 | small.height = (DisplayUtils.SCREEN_WIDTH_PIXELS / 2) * 9 / 16;
211 | small.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
212 | small.addRule(RelativeLayout.ALIGN_PARENT_TOP);
213 |
214 | if (mediaVideoView != null) {
215 | mediaVideoView.setVideoMode(MediaVideoView.VideoMode.SMALL);
216 | }
217 |
218 | // 不用显示control panel,只显示progress mini bar
219 | if (mediaPlayerController != null) {
220 | mediaPlayerController.show();
221 | }
222 |
223 | break;
224 | case FULL:
225 | LayoutParams full = (LayoutParams) getLayoutParams();
226 | full.width = LayoutParams.MATCH_PARENT;
227 | full.height = LayoutParams.MATCH_PARENT;
228 |
229 | if (mediaVideoView != null) {
230 | mediaVideoView.setVideoMode(MediaVideoView.VideoMode.FULL);
231 | }
232 |
233 | if (mediaPlayerController != null) {
234 | mediaPlayerController.hide();
235 | }
236 | break;
237 |
238 | }
239 | }
240 |
241 | public int getCurrentPlayPosition() {
242 | return mediaVideoView.getCurrentPosition();
243 | }
244 |
245 | public void stopPlayback() {
246 | mediaVideoView.stopPlayback();
247 | }
248 |
249 | public void setVideoPath(String videoPath) {
250 | mediaVideoView.setVideoPath(videoPath);
251 | }
252 |
253 | /**
254 | * 加载视频首页图
255 | * @param videoPath
256 | * @param imagePath
257 | */
258 | public void setVideoAndImagePath(String videoPath, String imagePath) {
259 | mediaVideoView.setVideoPath(videoPath);
260 | handleImagePath(imagePath);
261 | }
262 |
263 | public void setOnLoadImageListener(OnLoadImageListener loadImageListener) {
264 | this.loadImageListener = loadImageListener;
265 | }
266 |
267 | private void handleImagePath(String imagePath) {
268 | if (mImageView != null && !TextUtils.isEmpty(imagePath)) {
269 | if (loadImageListener != null) {
270 | loadImageListener.onLoadImage(mImageView, imagePath);
271 | }
272 | }
273 | }
274 |
275 | public void autoStartPlay() {
276 | mediaPlayerController.autoStartPlay();
277 | }
278 |
279 | public void onPause() {
280 | mediaPlayerController.onPause();
281 | if (playerStateListener != null) {
282 | playerStateListener.onPause();
283 | }
284 | // 截取一张视频图片
285 | Log.d(TAG, "onPause: 暂停");
286 | }
287 |
288 | public void onResume() {
289 | mediaPlayerController.onResume();
290 | if (playerStateListener != null) {
291 | playerStateListener.onResume();
292 | }
293 | Log.d(TAG, "onResume: 活动");
294 | }
295 |
296 | public interface OnLoadImageListener {
297 | void onLoadImage(ImageView imageView, String imagePath);
298 | }
299 |
300 | }
301 |
--------------------------------------------------------------------------------
/media-library/src/main/java/com/djonce/wangj/media/MediaVideoView.java:
--------------------------------------------------------------------------------
1 | package com.djonce.wangj.media;
2 |
3 | import android.annotation.TargetApi;
4 | import android.content.Context;
5 | import android.content.DialogInterface;
6 | import android.content.res.Resources;
7 | import android.media.AudioManager;
8 | import android.media.MediaPlayer;
9 | import android.net.Uri;
10 | import android.os.Build;
11 | import android.support.annotation.NonNull;
12 | import android.support.v7.app.AlertDialog;
13 | import android.util.AttributeSet;
14 | import android.util.Log;
15 | import android.view.KeyEvent;
16 | import android.view.MotionEvent;
17 | import android.view.View;
18 | import android.widget.MediaController;
19 | import android.widget.RelativeLayout;
20 |
21 | import java.io.IOException;
22 | import java.util.ArrayList;
23 | import java.util.List;
24 | import java.util.Locale;
25 | import java.util.Map;
26 |
27 | import tv.danmaku.ijk.media.player.IMediaPlayer;
28 | import tv.danmaku.ijk.media.player.IjkMediaPlayer;
29 | import tv.danmaku.ijk.media.player.misc.ITrackInfo;
30 |
31 | /**
32 | * 自定义的媒体播放器
33 | *
34 | * Created by wangj on 2016/5/23.
35 | */
36 | public class MediaVideoView extends RelativeLayout implements MediaController.MediaPlayerControl {
37 |
38 | private String TAG = "MediaPlayerView";
39 | // settable by the client
40 | private Uri mUri;
41 | private Map mHeaders;
42 |
43 | // all possible internal states
44 | private static final int STATE_ERROR = -1;
45 | private static final int STATE_IDLE = 0;
46 | private static final int STATE_PREPARING = 1;
47 | private static final int STATE_PREPARED = 2;
48 | private static final int STATE_PLAYING = 3;
49 | private static final int STATE_PAUSED = 4;
50 | private static final int STATE_PLAYBACK_COMPLETED = 5;
51 |
52 | // mCurrentState is a VideoView object's current state.
53 | // mTargetState is the state that a method caller intends to reach.
54 | // For instance, regardless the VideoView object's current state,
55 | // calling onPause() intends to bring the object to a target state
56 | // of STATE_PAUSED.
57 | private int mCurrentState = STATE_IDLE;
58 | private int mTargetState = STATE_IDLE;
59 |
60 | // All the stuff we need for playing and showing a video
61 | private IRenderView.ISurfaceHolder mSurfaceHolder = null;
62 | private IMediaPlayer mMediaPlayer = null;
63 | // private int mAudioSession;
64 | private int mVideoWidth;
65 | private int mVideoHeight;
66 | private int mSurfaceWidth;
67 | private int mSurfaceHeight;
68 | private int mVideoRotationDegree;
69 | private IMediaController mMediaController;
70 | private IMediaPlayer.OnCompletionListener mOnCompletionListener;
71 | private IMediaPlayer.OnPreparedListener mOnPreparedListener;
72 | private int mCurrentBufferPercentage;
73 | private IMediaPlayer.OnErrorListener mOnErrorListener;
74 | private IMediaPlayer.OnInfoListener mOnInfoListener;
75 | private int mSeekWhenPrepared; // recording the seek position while preparing
76 | private boolean mCanPause = true;
77 | private boolean mCanSeekBack = true;
78 | private boolean mCanSeekForward = true;
79 |
80 | private VideoMode videoMode = VideoMode.NORMAL;
81 | /**
82 | * Subtitle rendering widget overlaid on top of the video.
83 | */
84 |
85 | private Context mAppContext;
86 | private IRenderView mRenderView;
87 | private int mVideoSarNum;
88 | private int mVideoSarDen;
89 |
90 | public enum VideoMode {
91 | NORMAL,
92 | SMALL,
93 | FULL
94 | }
95 |
96 | public MediaVideoView(Context context) {
97 | super(context);
98 | initVideoView(context);
99 | }
100 |
101 | public MediaVideoView(Context context, AttributeSet attrs) {
102 | super(context, attrs);
103 | initVideoView(context);
104 | }
105 |
106 | public MediaVideoView(Context context, AttributeSet attrs, int defStyleAttr) {
107 | super(context, attrs, defStyleAttr);
108 | initVideoView(context);
109 | }
110 |
111 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
112 | public MediaVideoView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
113 | super(context, attrs, defStyleAttr, defStyleRes);
114 | initVideoView(context);
115 | }
116 |
117 | // REMOVED: onMeasure
118 | // REMOVED: onInitializeAccessibilityEvent
119 | // REMOVED: onInitializeAccessibilityNodeInfo
120 | // REMOVED: resolveAdjustedSize
121 |
122 | private void initVideoView(Context context) {
123 | mAppContext = context.getApplicationContext();
124 |
125 | initRenders();
126 |
127 | mVideoWidth = 0;
128 | mVideoHeight = 0;
129 | // REMOVED: getHolder().addCallback(mSHCallback);
130 | // REMOVED: getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
131 | setFocusable(true);
132 | setFocusableInTouchMode(true);
133 | requestFocus();
134 | // REMOVED: mPendingSubtitleTracks = new Vector>();
135 | mCurrentState = STATE_IDLE;
136 | mTargetState = STATE_IDLE;
137 | }
138 |
139 | public void setRenderView(IRenderView renderView) {
140 | if (mRenderView != null) {
141 | if (mMediaPlayer != null)
142 | mMediaPlayer.setDisplay(null);
143 |
144 | View renderUIView = mRenderView.getView();
145 | mRenderView.removeRenderCallback(mSHCallback);
146 | mRenderView = null;
147 | removeView(renderUIView);
148 | }
149 |
150 | if (renderView == null)
151 | return;
152 |
153 | mRenderView = renderView;
154 | renderView.setAspectRatio(mCurrentAspectRatio);
155 | if (mVideoWidth > 0 && mVideoHeight > 0)
156 | renderView.setVideoSize(mVideoWidth, mVideoHeight);
157 | if (mVideoSarNum > 0 && mVideoSarDen > 0)
158 | renderView.setVideoSampleAspectRatio(mVideoSarNum, mVideoSarDen);
159 |
160 | View renderUIView = mRenderView.getView();
161 | LayoutParams lp = new LayoutParams(
162 | LayoutParams.WRAP_CONTENT,
163 | LayoutParams.WRAP_CONTENT);
164 | lp.addRule(RelativeLayout.ALIGN_PARENT_TOP);
165 | renderUIView.setLayoutParams(lp);
166 | addView(renderUIView);
167 |
168 | mRenderView.addRenderCallback(mSHCallback);
169 | mRenderView.setVideoRotation(mVideoRotationDegree);
170 | }
171 |
172 | public void setRender(int render) {
173 | switch (render) {
174 | case RENDER_NONE:
175 | setRenderView(null);
176 | break;
177 | case RENDER_TEXTURE_VIEW: {
178 | TextureRenderView renderView = new TextureRenderView(getContext());
179 | if (mMediaPlayer != null) {
180 | renderView.getSurfaceHolder().bindToMediaPlayer(mMediaPlayer);
181 | renderView.setVideoSize(mMediaPlayer.getVideoWidth(), mMediaPlayer.getVideoHeight());
182 | renderView.setVideoSampleAspectRatio(mMediaPlayer.getVideoSarNum(), mMediaPlayer.getVideoSarDen());
183 | renderView.setAspectRatio(mCurrentAspectRatio);
184 | }
185 | setRenderView(renderView);
186 | break;
187 | }
188 |
189 | default:
190 | Log.e(TAG, String.format(Locale.getDefault(), "invalid render %d\n", render));
191 | break;
192 | }
193 | }
194 |
195 | /**
196 | * Sets video path.
197 | *
198 | * @param path the path of the video.
199 | */
200 | public void setVideoPath(String path) {
201 | setVideoURI(Uri.parse(path));
202 | }
203 |
204 | /**
205 | * Sets video URI.
206 | *
207 | * @param uri the URI of the video.
208 | */
209 | public void setVideoURI(Uri uri) {
210 | setVideoURI(uri, null);
211 | }
212 |
213 | /**
214 | * Sets video URI using specific headers.
215 | *
216 | * @param uri the URI of the video.
217 | * @param headers the headers for the URI request.
218 | * Note that the cross domain redirection is allowed by default, but that can be
219 | * changed with key/value pairs through the headers parameter with
220 | * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value
221 | * to disallow or allow cross domain redirection.
222 | */
223 | private void setVideoURI(Uri uri, Map headers) {
224 | mUri = uri;
225 | mHeaders = headers;
226 | mSeekWhenPrepared = 0;
227 | openVideo();
228 | requestLayout();
229 | invalidate();
230 | }
231 |
232 | public VideoMode getVideoMode() {
233 | return videoMode;
234 | }
235 |
236 | public void setVideoMode(VideoMode videoMode) {
237 | this.videoMode = videoMode;
238 | }
239 |
240 | // REMOVED: addSubtitleSource
241 | // REMOVED: mPendingSubtitleTracks
242 |
243 | public void stopPlayback() {
244 | if (mMediaPlayer != null) {
245 | mMediaPlayer.stop();
246 | mMediaPlayer.release();
247 | mMediaPlayer = null;
248 | mCurrentState = STATE_IDLE;
249 | mTargetState = STATE_IDLE;
250 | AudioManager am = (AudioManager) mAppContext.getSystemService(Context.AUDIO_SERVICE);
251 | am.abandonAudioFocus(null);
252 | }
253 | }
254 |
255 | @TargetApi(Build.VERSION_CODES.M)
256 | private void openVideo() {
257 | if (mUri == null || mSurfaceHolder == null) {
258 | // not ready for playback just yet, will try again later
259 | return;
260 | }
261 | // we shouldn't clear the target state, because somebody might have
262 | // called start() previously
263 | release(false);
264 |
265 | AudioManager am = (AudioManager) mAppContext.getSystemService(Context.AUDIO_SERVICE);
266 | am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
267 |
268 | try {
269 | mMediaPlayer = createPlayer();
270 |
271 | // TODO: create SubtitleController in MediaPlayer, but we need
272 | // a context for the subtitle renderers
273 | final Context context = getContext();
274 | // REMOVED: SubtitleController
275 |
276 | // REMOVED: mAudioSession
277 | mMediaPlayer.setOnPreparedListener(mPreparedListener);
278 | mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
279 | mMediaPlayer.setOnCompletionListener(mCompletionListener);
280 | mMediaPlayer.setOnErrorListener(mErrorListener);
281 | mMediaPlayer.setOnInfoListener(mInfoListener);
282 | mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);
283 | mCurrentBufferPercentage = 0;
284 | if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
285 | mMediaPlayer.setDataSource(mAppContext, mUri, mHeaders);
286 | } else {
287 | mMediaPlayer.setDataSource(mUri.toString());
288 | }
289 | bindSurfaceHolder(mMediaPlayer, mSurfaceHolder);
290 | mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
291 | mMediaPlayer.setScreenOnWhilePlaying(true);
292 | mMediaPlayer.prepareAsync();
293 |
294 | // REMOVED: mPendingSubtitleTracks
295 |
296 | // we don't set the target state here either, but preserve the
297 | // target state that was there before.
298 | mCurrentState = STATE_PREPARING;
299 | attachMediaController();
300 | } catch (IOException ex) {
301 | Log.w(TAG, "Unable to open content: " + mUri, ex);
302 | mCurrentState = STATE_ERROR;
303 | mTargetState = STATE_ERROR;
304 | mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
305 | } catch (IllegalArgumentException ex) {
306 | Log.w(TAG, "Unable to open content: " + mUri, ex);
307 | mCurrentState = STATE_ERROR;
308 | mTargetState = STATE_ERROR;
309 | mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
310 | } finally {
311 | // REMOVED: mPendingSubtitleTracks.clear();
312 | }
313 | }
314 |
315 | public void setMediaController(IMediaController controller) {
316 | if (mMediaController != null) {
317 | mMediaController.hide();
318 | }
319 | mMediaController = controller;
320 | attachMediaController();
321 | }
322 |
323 | private void attachMediaController() {
324 | if (mMediaPlayer != null && mMediaController != null) {
325 | mMediaController.setMediaPlayer(this);
326 | View anchorView = this.getParent() instanceof View ?
327 | (View) this.getParent() : this;
328 | mMediaController.setAnchorView(anchorView);
329 | mMediaController.setEnabled(isInPlaybackState());
330 | }
331 | }
332 |
333 | IMediaPlayer.OnVideoSizeChangedListener mSizeChangedListener =
334 | new IMediaPlayer.OnVideoSizeChangedListener() {
335 | public void onVideoSizeChanged(IMediaPlayer mp, int width, int height, int sarNum, int sarDen) {
336 | mVideoWidth = mp.getVideoWidth();
337 | mVideoHeight = mp.getVideoHeight();
338 | mVideoSarNum = mp.getVideoSarNum();
339 | mVideoSarDen = mp.getVideoSarDen();
340 | if (mVideoWidth != 0 && mVideoHeight != 0) {
341 | if (mRenderView != null) {
342 | mRenderView.setVideoSize(mVideoWidth, mVideoHeight);
343 | mRenderView.setVideoSampleAspectRatio(mVideoSarNum, mVideoSarDen);
344 | }
345 | // REMOVED: getHolder().setFixedSize(mVideoWidth, mVideoHeight);
346 | requestLayout();
347 | }
348 | }
349 | };
350 |
351 | IMediaPlayer.OnPreparedListener mPreparedListener = new IMediaPlayer.OnPreparedListener() {
352 | public void onPrepared(IMediaPlayer mp) {
353 | mCurrentState = STATE_PREPARED;
354 |
355 | // Get the capabilities of the player for this stream
356 | // REMOVED: Metadata
357 |
358 | if (mOnPreparedListener != null) {
359 | mOnPreparedListener.onPrepared(mMediaPlayer);
360 | }
361 | if (mMediaController != null) {
362 | mMediaController.setEnabled(true);
363 | }
364 | mVideoWidth = mp.getVideoWidth();
365 | mVideoHeight = mp.getVideoHeight();
366 |
367 | int seekToPosition = mSeekWhenPrepared; // mSeekWhenPrepared may be changed after seekTo() call
368 | if (seekToPosition != 0) {
369 | seekTo(seekToPosition);
370 | }
371 | if (mVideoWidth != 0 && mVideoHeight != 0) {
372 | //Log.i("@@@@", "video size: " + mVideoWidth +"/"+ mVideoHeight);
373 | // REMOVED: getHolder().setFixedSize(mVideoWidth, mVideoHeight);
374 | if (mRenderView != null) {
375 | mRenderView.setVideoSize(mVideoWidth, mVideoHeight);
376 | mRenderView.setVideoSampleAspectRatio(mVideoSarNum, mVideoSarDen);
377 | if (!mRenderView.shouldWaitForResize() || mSurfaceWidth == mVideoWidth && mSurfaceHeight == mVideoHeight) {
378 | // We didn't actually change the size (it was already at the size
379 | // we need), so we won't get a "surface changed" callback, so
380 | // start the video here instead of in the callback.
381 | if (mTargetState == STATE_PLAYING) {
382 | start();
383 | if (mMediaController != null) {
384 | mMediaController.show();
385 | }
386 | } else if (!isPlaying() &&
387 | (seekToPosition != 0 || getCurrentPosition() > 0)) {
388 | if (mMediaController != null) {
389 | // Show the media controls when we're paused into a video and make 'em stick.
390 | mMediaController.show(0);
391 | }
392 | }
393 | }
394 | }
395 | } else {
396 | // We don't know the video size yet, but should start anyway.
397 | // The video size might be reported to us later.
398 | if (mTargetState == STATE_PLAYING) {
399 | start();
400 | }
401 | }
402 | }
403 | };
404 |
405 | private IMediaPlayer.OnCompletionListener mCompletionListener =
406 | new IMediaPlayer.OnCompletionListener() {
407 | public void onCompletion(IMediaPlayer mp) {
408 | mCurrentState = STATE_PLAYBACK_COMPLETED;
409 | mTargetState = STATE_PLAYBACK_COMPLETED;
410 | if (mMediaController != null) {
411 | mMediaController.show(360000);
412 | }
413 | if (mOnCompletionListener != null) {
414 | mOnCompletionListener.onCompletion(mMediaPlayer);
415 | }
416 | }
417 | };
418 |
419 | private IMediaPlayer.OnInfoListener mInfoListener =
420 | new IMediaPlayer.OnInfoListener() {
421 | public boolean onInfo(IMediaPlayer mp, int arg1, int arg2) {
422 | if (mOnInfoListener != null) {
423 | mOnInfoListener.onInfo(mp, arg1, arg2);
424 | }
425 | switch (arg1) {
426 | case IMediaPlayer.MEDIA_INFO_VIDEO_TRACK_LAGGING:
427 | Log.d(TAG, "MEDIA_INFO_VIDEO_TRACK_LAGGING:");
428 | break;
429 | case IMediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START:
430 | Log.d(TAG, "MEDIA_INFO_VIDEO_RENDERING_START:");
431 | // 开始播放
432 | break;
433 | case IMediaPlayer.MEDIA_INFO_BUFFERING_START:
434 | Log.d(TAG, "MEDIA_INFO_BUFFERING_START:");
435 | // 在数据缓冲
436 | if (mMediaController != null) {
437 | mMediaController.showProgress(true);
438 | }
439 | break;
440 | case IMediaPlayer.MEDIA_INFO_BUFFERING_END:
441 |
442 | if (mMediaController != null) {
443 | mMediaController.showProgress(false);
444 | }
445 | Log.d(TAG, "MEDIA_INFO_BUFFERING_END:");
446 | break;
447 | case IMediaPlayer.MEDIA_INFO_NETWORK_BANDWIDTH:
448 | Log.d(TAG, "MEDIA_INFO_NETWORK_BANDWIDTH: " + arg2);
449 | break;
450 | case IMediaPlayer.MEDIA_INFO_BAD_INTERLEAVING:
451 | Log.d(TAG, "MEDIA_INFO_BAD_INTERLEAVING:");
452 | break;
453 | case IMediaPlayer.MEDIA_INFO_NOT_SEEKABLE:
454 | Log.d(TAG, "MEDIA_INFO_NOT_SEEKABLE:");
455 | break;
456 | case IMediaPlayer.MEDIA_INFO_METADATA_UPDATE:
457 | Log.d(TAG, "MEDIA_INFO_METADATA_UPDATE:");
458 | break;
459 | case IMediaPlayer.MEDIA_INFO_UNSUPPORTED_SUBTITLE:
460 | Log.d(TAG, "MEDIA_INFO_UNSUPPORTED_SUBTITLE:");
461 | break;
462 | case IMediaPlayer.MEDIA_INFO_SUBTITLE_TIMED_OUT:
463 | Log.d(TAG, "MEDIA_INFO_SUBTITLE_TIMED_OUT:");
464 | break;
465 | case IMediaPlayer.MEDIA_INFO_VIDEO_ROTATION_CHANGED:
466 | mVideoRotationDegree = arg2;
467 | Log.d(TAG, "MEDIA_INFO_VIDEO_ROTATION_CHANGED: " + arg2);
468 | if (mRenderView != null)
469 | mRenderView.setVideoRotation(arg2);
470 | break;
471 | case IMediaPlayer.MEDIA_INFO_AUDIO_RENDERING_START:
472 | Log.d(TAG, "MEDIA_INFO_AUDIO_RENDERING_START:");
473 | break;
474 | }
475 | return true;
476 | }
477 | };
478 |
479 | private IMediaPlayer.OnErrorListener mErrorListener =
480 | new IMediaPlayer.OnErrorListener() {
481 | public boolean onError(IMediaPlayer mp, int framework_err, int impl_err) {
482 | Log.d(TAG, "Error: " + framework_err + "," + impl_err);
483 | mCurrentState = STATE_ERROR;
484 | mTargetState = STATE_ERROR;
485 |
486 | /* If an error handler has been supplied, use it and finish. */
487 | if (mOnErrorListener != null) {
488 | if (mOnErrorListener.onError(mMediaPlayer, framework_err, impl_err)) {
489 | return true;
490 | }
491 | }
492 |
493 | /* Otherwise, pop up an error dialog so the user knows that
494 | * something bad has happened. Only try and pop up the dialog
495 | * if we're attached to a window. When we're going away and no
496 | * longer have a window, don't bother showing the user an error.
497 | */
498 | String message;
499 |
500 | if (framework_err == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) {
501 | message = "Invalid progressive playback";
502 | } else {
503 | message = "Unknown";
504 | }
505 |
506 | new AlertDialog.Builder(getContext())
507 | .setMessage(message)
508 | .setPositiveButton("OK",
509 | new DialogInterface.OnClickListener() {
510 | public void onClick(DialogInterface dialog, int whichButton) {
511 | /* If we get here, there is no onError listener, so
512 | * at least inform them that the video is over.
513 | */
514 | if (mOnCompletionListener != null) {
515 | mOnCompletionListener.onCompletion(mMediaPlayer);
516 | }
517 | }
518 | })
519 | .setCancelable(false)
520 | .show();
521 | return true;
522 | }
523 | };
524 |
525 | private IMediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener =
526 | new IMediaPlayer.OnBufferingUpdateListener() {
527 | public void onBufferingUpdate(IMediaPlayer mp, int percent) {
528 | mCurrentBufferPercentage = percent;
529 | }
530 | };
531 |
532 | /**
533 | * Register a callback to be invoked when the media file
534 | * is loaded and ready to go.
535 | *
536 | * @param l The callback that will be run
537 | */
538 | public void setOnPreparedListener(IMediaPlayer.OnPreparedListener l) {
539 | mOnPreparedListener = l;
540 | }
541 |
542 | /**
543 | * Register a callback to be invoked when the end of a media file
544 | * has been reached during playback.
545 | *
546 | * @param l The callback that will be run
547 | */
548 | public void setOnCompletionListener(IMediaPlayer.OnCompletionListener l) {
549 | mOnCompletionListener = l;
550 | }
551 |
552 | /**
553 | * Register a callback to be invoked when an error occurs
554 | * during playback or setup. If no listener is specified,
555 | * or if the listener returned false, VideoView will inform
556 | * the user of any errors.
557 | *
558 | * @param l The callback that will be run
559 | */
560 | public void setOnErrorListener(IMediaPlayer.OnErrorListener l) {
561 | mOnErrorListener = l;
562 | }
563 |
564 | /**
565 | * Register a callback to be invoked when an informational event
566 | * occurs during playback or setup.
567 | *
568 | * @param l The callback that will be run
569 | */
570 | public void setOnInfoListener(IMediaPlayer.OnInfoListener l) {
571 | mOnInfoListener = l;
572 | }
573 |
574 | // REMOVED: mSHCallback
575 | private void bindSurfaceHolder(IMediaPlayer mp, IRenderView.ISurfaceHolder holder) {
576 | if (mp == null)
577 | return;
578 |
579 | if (holder == null) {
580 | mp.setDisplay(null);
581 | return;
582 | }
583 |
584 | holder.bindToMediaPlayer(mp);
585 | }
586 |
587 | IRenderView.IRenderCallback mSHCallback = new IRenderView.IRenderCallback() {
588 | @Override
589 | public void onSurfaceChanged(@NonNull IRenderView.ISurfaceHolder holder, int format, int w, int h) {
590 | if (holder.getRenderView() != mRenderView) {
591 | Log.e(TAG, "onSurfaceChanged: unmatched render callback\n");
592 | return;
593 | }
594 |
595 | mSurfaceWidth = w;
596 | mSurfaceHeight = h;
597 | boolean isValidState = (mTargetState == STATE_PLAYING);
598 | boolean hasValidSize = !mRenderView.shouldWaitForResize() || (mVideoWidth == w && mVideoHeight == h);
599 | if (mMediaPlayer != null && isValidState && hasValidSize) {
600 | if (mSeekWhenPrepared != 0) {
601 | seekTo(mSeekWhenPrepared);
602 | }
603 | start();
604 | }
605 | }
606 |
607 | @Override
608 | public void onSurfaceCreated(@NonNull IRenderView.ISurfaceHolder holder, int width, int height) {
609 | if (holder.getRenderView() != mRenderView) {
610 | Log.e(TAG, "onSurfaceCreated: unmatched render callback\n");
611 | return;
612 | }
613 |
614 | mSurfaceHolder = holder;
615 | if (mMediaPlayer != null)
616 | bindSurfaceHolder(mMediaPlayer, holder);
617 | else
618 | openVideo();
619 | }
620 |
621 | @Override
622 | public void onSurfaceDestroyed(@NonNull IRenderView.ISurfaceHolder holder) {
623 | if (holder.getRenderView() != mRenderView) {
624 | Log.e(TAG, "onSurfaceDestroyed: unmatched render callback\n");
625 | return;
626 | }
627 |
628 | // after we return from this we can't use the surface any more
629 | mSurfaceHolder = null;
630 | // REMOVED: if (mMediaController != null) mMediaController.hide();
631 | // REMOVED: release(true);
632 | releaseWithoutStop();
633 | }
634 | };
635 |
636 | public void releaseWithoutStop() {
637 | if (mMediaPlayer != null)
638 | mMediaPlayer.setDisplay(null);
639 | }
640 |
641 | /*
642 | * release the media player in any state
643 | */
644 | public void release(boolean clearTargetState) {
645 | if (mMediaPlayer != null) {
646 | mMediaPlayer.reset();
647 | mMediaPlayer.release();
648 | mMediaPlayer = null;
649 | // REMOVED: mPendingSubtitleTracks.clear();
650 | mCurrentState = STATE_IDLE;
651 | if (clearTargetState) {
652 | mTargetState = STATE_IDLE;
653 | }
654 | AudioManager am = (AudioManager) mAppContext.getSystemService(Context.AUDIO_SERVICE);
655 | am.abandonAudioFocus(null);
656 | }
657 | }
658 |
659 | @Override
660 | public boolean onTouchEvent(MotionEvent ev) {
661 |
662 | if (isInPlaybackState() && mMediaController != null) {
663 |
664 | Log.e(TAG, "onTouchEvent: " + getVideoMode().name());
665 | switch (getVideoMode()) {
666 | case NORMAL:
667 | if (mMediaPlayer.isPlaying()) {
668 | pause();
669 | mMediaController.show(3600000);
670 | }
671 |
672 | break;
673 | case FULL:
674 | // 暂停
675 | if (mMediaPlayer.isPlaying()) {
676 | pause();
677 | mMediaController.show(3600000);
678 | } else {
679 | resume();
680 | mMediaController.hide();
681 | }
682 |
683 | break;
684 | case SMALL:
685 | //
686 | if (mMediaPlayer.isPlaying()) {
687 | //
688 | pause();
689 | mMediaController.show(3600000);
690 | }
691 | return true;
692 | default:
693 | toggleMediaControlsVisiblity();
694 | break;
695 | }
696 | }
697 |
698 | return false;
699 | }
700 |
701 | @Override
702 | public boolean onTrackballEvent(MotionEvent ev) {
703 | if (isInPlaybackState() && mMediaController != null) {
704 | toggleMediaControlsVisiblity();
705 | }
706 | return false;
707 | }
708 |
709 | @Override
710 | public boolean onKeyDown(int keyCode, KeyEvent event) {
711 | boolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK &&
712 | keyCode != KeyEvent.KEYCODE_VOLUME_UP &&
713 | keyCode != KeyEvent.KEYCODE_VOLUME_DOWN &&
714 | keyCode != KeyEvent.KEYCODE_VOLUME_MUTE &&
715 | keyCode != KeyEvent.KEYCODE_MENU &&
716 | keyCode != KeyEvent.KEYCODE_CALL &&
717 | keyCode != KeyEvent.KEYCODE_ENDCALL;
718 | if (isInPlaybackState() && isKeyCodeSupported && mMediaController != null) {
719 | if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK ||
720 | keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
721 | if (mMediaPlayer.isPlaying()) {
722 | pause();
723 | mMediaController.show();
724 | } else {
725 | start();
726 | mMediaController.hide();
727 | }
728 | return true;
729 | } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {
730 | if (!mMediaPlayer.isPlaying()) {
731 | start();
732 | mMediaController.hide();
733 | }
734 | return true;
735 | } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
736 | || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
737 | if (mMediaPlayer.isPlaying()) {
738 | pause();
739 | mMediaController.show();
740 | }
741 | return true;
742 | } else {
743 | toggleMediaControlsVisiblity();
744 | }
745 | }
746 |
747 | return super.onKeyDown(keyCode, event);
748 | }
749 |
750 | private void toggleMediaControlsVisiblity() {
751 | if (mMediaController.isShowing()) {
752 | mMediaController.hide();
753 | } else {
754 | mMediaController.show();
755 | }
756 | }
757 |
758 | @Override
759 | public void start() {
760 | if (isInPlaybackState()) {
761 | mMediaPlayer.start();
762 | mCurrentState = STATE_PLAYING;
763 | }
764 | mTargetState = STATE_PLAYING;
765 | }
766 |
767 | @Override
768 | public void pause() {
769 | if (isInPlaybackState()) {
770 | if (mMediaPlayer.isPlaying()) {
771 | mMediaPlayer.pause();
772 | mCurrentState = STATE_PAUSED;
773 | }
774 | }
775 | mTargetState = STATE_PAUSED;
776 | }
777 |
778 | /**
779 | * 挂起
780 | */
781 | public void suspend() {
782 | release(false);
783 | }
784 |
785 | public void resume() {
786 | openVideo();
787 | }
788 |
789 | @Override
790 | public int getDuration() {
791 | if (isInPlaybackState()) {
792 | return (int) mMediaPlayer.getDuration();
793 | }
794 |
795 | return -1;
796 | }
797 |
798 | @Override
799 | public int getCurrentPosition() {
800 | if (isInPlaybackState()) {
801 | return (int) mMediaPlayer.getCurrentPosition();
802 | }
803 | return 0;
804 | }
805 |
806 | @Override
807 | public void seekTo(int msec) {
808 | if (isInPlaybackState()) {
809 | mMediaPlayer.seekTo(msec);
810 | mSeekWhenPrepared = 0;
811 | } else {
812 | mSeekWhenPrepared = msec;
813 | }
814 | }
815 |
816 | @Override
817 | public boolean isPlaying() {
818 | return isInPlaybackState() && mMediaPlayer.isPlaying();
819 | }
820 |
821 | @Override
822 | public int getBufferPercentage() {
823 | if (mMediaPlayer != null) {
824 | return mCurrentBufferPercentage;
825 | }
826 | return 0;
827 | }
828 |
829 | private boolean isInPlaybackState() {
830 | return (mMediaPlayer != null &&
831 | mCurrentState != STATE_ERROR &&
832 | mCurrentState != STATE_IDLE &&
833 | mCurrentState != STATE_PREPARING);
834 | }
835 |
836 | @Override
837 | public boolean canPause() {
838 | return mCanPause;
839 | }
840 |
841 | @Override
842 | public boolean canSeekBackward() {
843 | return mCanSeekBack;
844 | }
845 |
846 | @Override
847 | public boolean canSeekForward() {
848 | return mCanSeekForward;
849 | }
850 |
851 | @Override
852 | public int getAudioSessionId() {
853 | return 0;
854 | }
855 |
856 | // REMOVED: getAudioSessionId();
857 | // REMOVED: onAttachedToWindow();
858 | // REMOVED: onDetachedFromWindow();
859 | // REMOVED: onLayout();
860 | // REMOVED: draw();
861 | // REMOVED: measureAndLayoutSubtitleWidget();
862 | // REMOVED: setSubtitleWidget();
863 | // REMOVED: getSubtitleLooper();
864 |
865 | //-------------------------
866 | // Extend: Aspect Ratio
867 | //-------------------------
868 |
869 | private static final int[] s_allAspectRatio = {
870 | IRenderView.AR_ASPECT_FILL_PARENT,
871 | IRenderView.AR_ASPECT_WRAP_CONTENT,
872 | IRenderView.AR_16_9_FIT_PARENT,
873 | IRenderView.AR_4_3_FIT_PARENT};
874 | private int mCurrentAspectRatioIndex = 0;
875 | private int mCurrentAspectRatio = s_allAspectRatio[0];
876 |
877 | //-------------------------
878 | // Extend: Render
879 | //-------------------------
880 | public static final int RENDER_NONE = 0;
881 | public static final int RENDER_SURFACE_VIEW = 1;
882 | public static final int RENDER_TEXTURE_VIEW = 2;
883 |
884 | private List mAllRenders = new ArrayList();
885 | private int mCurrentRenderIndex = 0;
886 | private int mCurrentRender = RENDER_NONE;
887 |
888 | private void initRenders() {
889 | mAllRenders.clear();
890 |
891 | mAllRenders.add(RENDER_TEXTURE_VIEW);
892 |
893 | if (mAllRenders.isEmpty())
894 | mAllRenders.add(RENDER_SURFACE_VIEW);
895 | mCurrentRender = mAllRenders.get(mCurrentRenderIndex);
896 | setRender(mCurrentRender);
897 | }
898 |
899 | public IMediaPlayer createPlayer() {
900 | IMediaPlayer mediaPlayer = null;
901 |
902 | IjkMediaPlayer ijkMediaPlayer = new IjkMediaPlayer();
903 | if (mUri != null) {
904 |
905 | ijkMediaPlayer.native_setLogLevel(IjkMediaPlayer.IJK_LOG_DEBUG);
906 |
907 | ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-auto-rotate", 1);
908 |
909 | ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "overlay-format", IjkMediaPlayer.SDL_FCC_RV32);
910 |
911 | ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", 1);
912 | ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", 0);
913 |
914 | ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "http-detect-range-support", 0);
915 |
916 | ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "skip_loop_filter", 48);
917 | }
918 | mediaPlayer = ijkMediaPlayer;
919 |
920 |
921 | return mediaPlayer;
922 | }
923 |
924 | public ITrackInfo[] getTrackInfo() {
925 | if (mMediaPlayer == null)
926 | return null;
927 |
928 | return mMediaPlayer.getTrackInfo();
929 | }
930 |
931 | public void selectTrack(int stream) {
932 | MediaPlayerCompat.selectTrack(mMediaPlayer, stream);
933 | }
934 |
935 | public void deselectTrack(int stream) {
936 | MediaPlayerCompat.deselectTrack(mMediaPlayer, stream);
937 | }
938 |
939 | public int getSelectedTrack(int trackType) {
940 | return MediaPlayerCompat.getSelectedTrack(mMediaPlayer, trackType);
941 | }
942 | }
943 |
--------------------------------------------------------------------------------
/media-library/src/main/java/com/djonce/wangj/media/OnMediaPlayerStateListener.java:
--------------------------------------------------------------------------------
1 | package com.djonce.wangj.media;
2 |
3 | import tv.danmaku.ijk.media.player.IMediaPlayer;
4 |
5 | /**
6 | *
7 | * Created by wangj on 2016/6/16 0016.
8 | */
9 | public abstract class OnMediaPlayerStateListener {
10 |
11 | public void onFirstPlay(){} // 首次播放
12 |
13 | public void onClickPlay(){} // 点击播放
14 |
15 | public abstract void onSeekPlay(); // 滑动进度播放
16 |
17 | public void onBufferStart() {} // 视频缓冲
18 |
19 | public void onBufferEnd() {} // 视频缓冲完成
20 |
21 | public abstract void onPause(); // 暂停了播放
22 |
23 | public abstract void onResume(); // 恢复播放状态
24 |
25 | public abstract void onError(IMediaPlayer mp, int what, int extra); // 播放错误
26 |
27 | public abstract void onCompleted(IMediaPlayer mp); // 播放完毕
28 | }
29 |
--------------------------------------------------------------------------------
/media-library/src/main/java/com/djonce/wangj/media/TextureRenderView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Zhang Rui
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.djonce.wangj.media;
18 |
19 | import android.annotation.TargetApi;
20 | import android.content.Context;
21 | import android.graphics.SurfaceTexture;
22 | import android.os.Build;
23 | import android.support.annotation.NonNull;
24 | import android.support.annotation.Nullable;
25 | import android.util.AttributeSet;
26 | import android.util.Log;
27 | import android.view.Surface;
28 | import android.view.SurfaceHolder;
29 | import android.view.TextureView;
30 | import android.view.View;
31 | import android.view.accessibility.AccessibilityEvent;
32 | import android.view.accessibility.AccessibilityNodeInfo;
33 |
34 | import java.lang.ref.WeakReference;
35 | import java.util.Map;
36 | import java.util.concurrent.ConcurrentHashMap;
37 |
38 | import tv.danmaku.ijk.media.player.IMediaPlayer;
39 | import tv.danmaku.ijk.media.player.ISurfaceTextureHolder;
40 | import tv.danmaku.ijk.media.player.ISurfaceTextureHost;
41 |
42 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
43 | public class TextureRenderView extends TextureView implements IRenderView {
44 | private static final String TAG = "TextureRenderView";
45 | private MeasureHelper mMeasureHelper;
46 |
47 | public TextureRenderView(Context context) {
48 | super(context);
49 | initView(context);
50 | }
51 |
52 | public TextureRenderView(Context context, AttributeSet attrs) {
53 | super(context, attrs);
54 | initView(context);
55 | }
56 |
57 | public TextureRenderView(Context context, AttributeSet attrs, int defStyleAttr) {
58 | super(context, attrs, defStyleAttr);
59 | initView(context);
60 | }
61 |
62 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
63 | public TextureRenderView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
64 | super(context, attrs, defStyleAttr, defStyleRes);
65 | initView(context);
66 | }
67 |
68 | private void initView(Context context) {
69 | mMeasureHelper = new MeasureHelper(this);
70 | mSurfaceCallback = new SurfaceCallback(this);
71 | setSurfaceTextureListener(mSurfaceCallback);
72 | }
73 |
74 | @Override
75 | public View getView() {
76 | return this;
77 | }
78 |
79 | @Override
80 | public boolean shouldWaitForResize() {
81 | return false;
82 | }
83 |
84 | @Override
85 | protected void onDetachedFromWindow() {
86 | mSurfaceCallback.willDetachFromWindow();
87 | super.onDetachedFromWindow();
88 | mSurfaceCallback.didDetachFromWindow();
89 | }
90 |
91 | //--------------------
92 | // Layout & Measure
93 | //--------------------
94 | @Override
95 | public void setVideoSize(int videoWidth, int videoHeight) {
96 | if (videoWidth > 0 && videoHeight > 0) {
97 | mMeasureHelper.setVideoSize(videoWidth, videoHeight);
98 | requestLayout();
99 | }
100 | }
101 |
102 | @Override
103 | public void setVideoSampleAspectRatio(int videoSarNum, int videoSarDen) {
104 | if (videoSarNum > 0 && videoSarDen > 0) {
105 | mMeasureHelper.setVideoSampleAspectRatio(videoSarNum, videoSarDen);
106 | requestLayout();
107 | }
108 | }
109 |
110 | @Override
111 | public void setVideoRotation(int degree) {
112 | mMeasureHelper.setVideoRotation(degree);
113 | setRotation(degree);
114 | }
115 |
116 | @Override
117 | public void setAspectRatio(int aspectRatio) {
118 | mMeasureHelper.setAspectRatio(aspectRatio);
119 | requestLayout();
120 | }
121 |
122 | @Override
123 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
124 | mMeasureHelper.doMeasure(widthMeasureSpec, heightMeasureSpec);
125 | setMeasuredDimension(mMeasureHelper.getMeasuredWidth(), mMeasureHelper.getMeasuredHeight());
126 | }
127 |
128 | //--------------------
129 | // TextureViewHolder
130 | //--------------------
131 |
132 | public IRenderView.ISurfaceHolder getSurfaceHolder() {
133 | return new InternalSurfaceHolder(this, mSurfaceCallback.mSurfaceTexture, mSurfaceCallback);
134 | }
135 |
136 | private static final class InternalSurfaceHolder implements IRenderView.ISurfaceHolder {
137 | private TextureRenderView mTextureView;
138 | private SurfaceTexture mSurfaceTexture;
139 | private ISurfaceTextureHost mSurfaceTextureHost;
140 |
141 | public InternalSurfaceHolder(@NonNull TextureRenderView textureView,
142 | @Nullable SurfaceTexture surfaceTexture,
143 | @NonNull ISurfaceTextureHost surfaceTextureHost) {
144 | mTextureView = textureView;
145 | mSurfaceTexture = surfaceTexture;
146 | mSurfaceTextureHost = surfaceTextureHost;
147 | }
148 |
149 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
150 | public void bindToMediaPlayer(IMediaPlayer mp) {
151 | if (mp == null)
152 | return;
153 |
154 | if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) &&
155 | (mp instanceof ISurfaceTextureHolder)) {
156 | ISurfaceTextureHolder textureHolder = (ISurfaceTextureHolder) mp;
157 | mTextureView.mSurfaceCallback.setOwnSurfaceTexture(false);
158 |
159 | SurfaceTexture surfaceTexture = textureHolder.getSurfaceTexture();
160 | if (surfaceTexture != null) {
161 | mTextureView.setSurfaceTexture(surfaceTexture);
162 | } else {
163 | textureHolder.setSurfaceTexture(mSurfaceTexture);
164 | textureHolder.setSurfaceTextureHost(mTextureView.mSurfaceCallback);
165 | }
166 | } else {
167 | mp.setSurface(openSurface());
168 | }
169 | }
170 |
171 | @NonNull
172 | @Override
173 | public IRenderView getRenderView() {
174 | return mTextureView;
175 | }
176 |
177 | @Nullable
178 | @Override
179 | public SurfaceHolder getSurfaceHolder() {
180 | return null;
181 | }
182 |
183 | @Nullable
184 | @Override
185 | public SurfaceTexture getSurfaceTexture() {
186 | return mSurfaceTexture;
187 | }
188 |
189 | @Nullable
190 | @Override
191 | public Surface openSurface() {
192 | if (mSurfaceTexture == null)
193 | return null;
194 | return new Surface(mSurfaceTexture);
195 | }
196 | }
197 |
198 | //-------------------------
199 | // SurfaceHolder.Callback
200 | //-------------------------
201 |
202 | @Override
203 | public void addRenderCallback(IRenderCallback callback) {
204 | mSurfaceCallback.addRenderCallback(callback);
205 | }
206 |
207 | @Override
208 | public void removeRenderCallback(IRenderCallback callback) {
209 | mSurfaceCallback.removeRenderCallback(callback);
210 | }
211 |
212 | private SurfaceCallback mSurfaceCallback;
213 |
214 | private static final class SurfaceCallback implements SurfaceTextureListener, ISurfaceTextureHost {
215 | private SurfaceTexture mSurfaceTexture;
216 | private boolean mIsFormatChanged;
217 | private int mWidth;
218 | private int mHeight;
219 |
220 | private boolean mOwnSurfaceTexture = true;
221 | private boolean mWillDetachFromWindow = false;
222 | private boolean mDidDetachFromWindow = false;
223 |
224 | private WeakReference mWeakRenderView;
225 | private Map mRenderCallbackMap = new ConcurrentHashMap();
226 |
227 | public SurfaceCallback(@NonNull TextureRenderView renderView) {
228 | mWeakRenderView = new WeakReference(renderView);
229 | }
230 |
231 | public void setOwnSurfaceTexture(boolean ownSurfaceTexture) {
232 | mOwnSurfaceTexture = ownSurfaceTexture;
233 | }
234 |
235 | public void addRenderCallback(@NonNull IRenderCallback callback) {
236 | mRenderCallbackMap.put(callback, callback);
237 |
238 | ISurfaceHolder surfaceHolder = null;
239 | if (mSurfaceTexture != null) {
240 | if (surfaceHolder == null)
241 | surfaceHolder = new InternalSurfaceHolder(mWeakRenderView.get(), mSurfaceTexture, this);
242 | callback.onSurfaceCreated(surfaceHolder, mWidth, mHeight);
243 | }
244 |
245 | if (mIsFormatChanged) {
246 | if (surfaceHolder == null)
247 | surfaceHolder = new InternalSurfaceHolder(mWeakRenderView.get(), mSurfaceTexture, this);
248 | callback.onSurfaceChanged(surfaceHolder, 0, mWidth, mHeight);
249 | }
250 | }
251 |
252 | public void removeRenderCallback(@NonNull IRenderCallback callback) {
253 | mRenderCallbackMap.remove(callback);
254 | }
255 |
256 | @Override
257 | public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
258 | mSurfaceTexture = surface;
259 | mIsFormatChanged = false;
260 | mWidth = 0;
261 | mHeight = 0;
262 |
263 | ISurfaceHolder surfaceHolder = new InternalSurfaceHolder(mWeakRenderView.get(), surface, this);
264 | for (IRenderCallback renderCallback : mRenderCallbackMap.keySet()) {
265 | renderCallback.onSurfaceCreated(surfaceHolder, 0, 0);
266 | }
267 | }
268 |
269 | @Override
270 | public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
271 | mSurfaceTexture = surface;
272 | mIsFormatChanged = true;
273 | mWidth = width;
274 | mHeight = height;
275 |
276 | ISurfaceHolder surfaceHolder = new InternalSurfaceHolder(mWeakRenderView.get(), surface, this);
277 | for (IRenderCallback renderCallback : mRenderCallbackMap.keySet()) {
278 | renderCallback.onSurfaceChanged(surfaceHolder, 0, width, height);
279 | }
280 | }
281 |
282 | @Override
283 | public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
284 | mSurfaceTexture = surface;
285 | mIsFormatChanged = false;
286 | mWidth = 0;
287 | mHeight = 0;
288 |
289 | ISurfaceHolder surfaceHolder = new InternalSurfaceHolder(mWeakRenderView.get(), surface, this);
290 | for (IRenderCallback renderCallback : mRenderCallbackMap.keySet()) {
291 | renderCallback.onSurfaceDestroyed(surfaceHolder);
292 | }
293 |
294 | Log.d(TAG, "onSurfaceTextureDestroyed: destroy: " + mOwnSurfaceTexture);
295 | return mOwnSurfaceTexture;
296 | }
297 |
298 | @Override
299 | public void onSurfaceTextureUpdated(SurfaceTexture surface) {
300 | }
301 |
302 | //-------------------------
303 | // ISurfaceTextureHost
304 | //-------------------------
305 |
306 | @Override
307 | public void releaseSurfaceTexture(SurfaceTexture surfaceTexture) {
308 | if (surfaceTexture == null) {
309 | Log.d(TAG, "releaseSurfaceTexture: null");
310 | } else if (mDidDetachFromWindow) {
311 | if (surfaceTexture != mSurfaceTexture) {
312 | Log.d(TAG, "releaseSurfaceTexture: didDetachFromWindow(): release different SurfaceTexture");
313 | surfaceTexture.release();
314 | } else if (!mOwnSurfaceTexture) {
315 | Log.d(TAG, "releaseSurfaceTexture: didDetachFromWindow(): release detached SurfaceTexture");
316 | surfaceTexture.release();
317 | } else {
318 | Log.d(TAG, "releaseSurfaceTexture: didDetachFromWindow(): already released by TextureView");
319 | }
320 | } else if (mWillDetachFromWindow) {
321 | if (surfaceTexture != mSurfaceTexture) {
322 | Log.d(TAG, "releaseSurfaceTexture: willDetachFromWindow(): release different SurfaceTexture");
323 | surfaceTexture.release();
324 | } else if (!mOwnSurfaceTexture) {
325 | Log.d(TAG, "releaseSurfaceTexture: willDetachFromWindow(): re-attach SurfaceTexture to TextureView");
326 | setOwnSurfaceTexture(true);
327 | } else {
328 | Log.d(TAG, "releaseSurfaceTexture: willDetachFromWindow(): will released by TextureView");
329 | }
330 | } else {
331 | if (surfaceTexture != mSurfaceTexture) {
332 | Log.d(TAG, "releaseSurfaceTexture: alive: release different SurfaceTexture");
333 | surfaceTexture.release();
334 | } else if (!mOwnSurfaceTexture) {
335 | Log.d(TAG, "releaseSurfaceTexture: alive: re-attach SurfaceTexture to TextureView");
336 | setOwnSurfaceTexture(true);
337 | } else {
338 | Log.d(TAG, "releaseSurfaceTexture: alive: will released by TextureView");
339 | }
340 | }
341 | }
342 |
343 | public void willDetachFromWindow() {
344 | Log.d(TAG, "willDetachFromWindow()");
345 | mWillDetachFromWindow = true;
346 | }
347 |
348 | public void didDetachFromWindow() {
349 | Log.d(TAG, "didDetachFromWindow()");
350 | mDidDetachFromWindow = true;
351 | }
352 | }
353 |
354 | //--------------------
355 | // Accessibility
356 | //--------------------
357 |
358 | @Override
359 | public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
360 | super.onInitializeAccessibilityEvent(event);
361 | event.setClassName(TextureRenderView.class.getName());
362 | }
363 |
364 | @Override
365 | public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
366 | super.onInitializeAccessibilityNodeInfo(info);
367 | info.setClassName(TextureRenderView.class.getName());
368 | }
369 | }
370 |
--------------------------------------------------------------------------------
/media-library/src/main/res/drawable-xxhdpi/ds_all_button_play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djonce/android-stickyvideo/af178bc384c9f0df2008ce322e8af05db948e0d1/media-library/src/main/res/drawable-xxhdpi/ds_all_button_play.png
--------------------------------------------------------------------------------
/media-library/src/main/res/drawable-xxhdpi/ds_detail_fullscreen_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djonce/android-stickyvideo/af178bc384c9f0df2008ce322e8af05db948e0d1/media-library/src/main/res/drawable-xxhdpi/ds_detail_fullscreen_close.png
--------------------------------------------------------------------------------
/media-library/src/main/res/drawable-xxhdpi/ds_detail_fullscreen_open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/djonce/android-stickyvideo/af178bc384c9f0df2008ce322e8af05db948e0d1/media-library/src/main/res/drawable-xxhdpi/ds_detail_fullscreen_open.png
--------------------------------------------------------------------------------
/media-library/src/main/res/drawable/detail_process_bar.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | -
5 |
6 |
7 |
8 |
9 | -
10 |
11 |
12 |
13 |
14 |
15 |
16 | -
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/media-library/src/main/res/drawable/seek_bar_empty_thumb.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
8 |
--------------------------------------------------------------------------------
/media-library/src/main/res/layout/media_player_controller_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
12 |
21 |
22 |
33 |
34 |
50 |
51 |
60 |
61 |
70 |
71 |
72 |
73 |
74 |
75 |
86 |
87 |
96 |
97 |
98 |
111 |
--------------------------------------------------------------------------------
/media-library/src/main/res/layout/media_player_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
19 |
20 |
25 |
26 |
--------------------------------------------------------------------------------
/media-library/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | MediaLibrary
3 |
4 |
--------------------------------------------------------------------------------
/media-library/src/test/java/com/djonce/wangj/media/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.djonce.wangj.media;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':Sample', ':media-library'
2 |
--------------------------------------------------------------------------------