├── .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 | ![image](https://github.com/djonce/android-stickyvideo/blob/master/Screenshot/6B142384-9C3D-43CC-B140-E7CA3048AEAB.png) 10 | 11 | 下滑缩放状态 12 | ![image](https://github.com/djonce/android-stickyvideo/blob/master/Screenshot/A90E928D-A009-48CE-BF8C-F8DD080C3866.png) 13 | 14 | 放大全屏状态 15 | ![image](https://github.com/djonce/android-stickyvideo/blob/master/Screenshot/3BAF7EB1-064E-447B-A7BA-670A20FD1ECB.png) 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 | --------------------------------------------------------------------------------