├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── dictionaries │ └── JUSFOUN.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── cn │ │ └── demo │ │ └── videolist │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── cn │ │ │ └── demo │ │ │ └── videolist │ │ │ ├── CustomMediaContoller.java │ │ │ ├── MainActivity.java │ │ │ ├── VSeekBar.java │ │ │ ├── VideoItemData.java │ │ │ ├── VideoListData.java │ │ │ ├── VideoListLayout.java │ │ │ ├── VideoPlayView.java │ │ │ └── media │ │ │ ├── IMediaController.java │ │ │ ├── IRenderView.java │ │ │ ├── IjkVideoView.java │ │ │ ├── InfoHudViewHolder.java │ │ │ ├── MeasureHelper.java │ │ │ ├── MediaPlayerCompat.java │ │ │ ├── MediaPlayerService.java │ │ │ ├── Settings.java │ │ │ ├── SurfaceRenderView.java │ │ │ ├── TableLayoutBinder.java │ │ │ ├── TextureRenderView.java │ │ │ └── VideoAdapter.java │ ├── jniLibs │ │ ├── arm64-v8a │ │ │ ├── libijkffmpeg.so │ │ │ ├── libijkplayer.so │ │ │ └── libijksdl.so │ │ └── x86_64 │ │ │ ├── libijkffmpeg.so │ │ │ ├── libijkplayer.so │ │ │ └── libijksdl.so │ └── res │ │ ├── drawable │ │ ├── app_video_center_bg.xml │ │ ├── bg_seek.xml │ │ └── bg_thumb.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── item_video.xml │ │ ├── layout_video_list.xml │ │ ├── media_contoller.xml │ │ ├── table_media_info.xml │ │ ├── table_media_info_row1.xml │ │ ├── table_media_info_row2.xml │ │ ├── table_media_info_section.xml │ │ └── view_video_item.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ ├── full_screen_icon.png │ │ ├── ic_launcher.png │ │ ├── sound_mult_icon.png │ │ ├── sound_open_icon.png │ │ ├── video_play_btn.png │ │ └── video_stop_btn.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── raw │ │ └── video_list.json │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ ├── strings_pref.xml │ │ └── styles.xml │ └── test │ └── java │ └── cn │ └── demo │ └── videolist │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenshots └── vidiolist.gif └── 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 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | VideoListDemo -------------------------------------------------------------------------------- /.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/dictionaries/JUSFOUN.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | 24 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | Android Lint 39 | 40 | 41 | Java 42 | 43 | 44 | Java language level migration aidsJava 45 | 46 | 47 | Performance issuesJava 48 | 49 | 50 | Portability issuesJava 51 | 52 | 53 | Probable bugsJava 54 | 55 | 56 | 57 | 58 | Android 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 80 | 81 | 82 | 83 | 84 | 1.8 85 | 86 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VideoListDemo 2 | 一个视频播放列表demo,视频播放使用的ijkplayer,正在播放视频拖动到不可见区域小窗口播放,可全屏切换 3 | ![](https://github.com/w1123440793/VideoListDemo/blob/master/screenshots/vidiolist.gif) 4 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.3" 6 | 7 | defaultConfig { 8 | applicationId "cn.demo.videolist" 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(include: ['*.jar'], dir: 'libs') 24 | testCompile 'junit:junit:4.12' 25 | compile 'com.android.support:appcompat-v7:23.3.0' 26 | compile 'com.android.support:recyclerview-v7:23.+' 27 | compile 'com.google.code.gson:gson:2.6.2' 28 | 29 | compile 'tv.danmaku.ijk.media:ijkplayer-java:0.5.1' 30 | compile 'tv.danmaku.ijk.media:ijkplayer-armv7a:0.5.1' 31 | compile 'tv.danmaku.ijk.media:ijkplayer-exo:0.5.1' 32 | compile 'tv.danmaku.ijk.media:ijkplayer-x86:0.5.1' 33 | compile 'tv.danmaku.ijk.media:ijkplayer-armv5:0.5.1' 34 | } 35 | -------------------------------------------------------------------------------- /app/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\JUSFOUN\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 | -------------------------------------------------------------------------------- /app/src/androidTest/java/cn/demo/videolist/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package cn.demo.videolist; 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 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 12 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/cn/demo/videolist/CustomMediaContoller.java: -------------------------------------------------------------------------------- 1 | package cn.demo.videolist; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.pm.ActivityInfo; 6 | import android.graphics.Bitmap; 7 | import android.graphics.PointF; 8 | import android.graphics.Rect; 9 | import android.media.AudioManager; 10 | import android.os.Handler; 11 | import android.os.Message; 12 | import android.util.DisplayMetrics; 13 | import android.util.Log; 14 | import android.view.GestureDetector; 15 | import android.view.MotionEvent; 16 | import android.view.Surface; 17 | import android.view.View; 18 | import android.view.WindowManager; 19 | import android.widget.ImageView; 20 | import android.widget.LinearLayout; 21 | import android.widget.MediaController; 22 | import android.widget.ProgressBar; 23 | import android.widget.RelativeLayout; 24 | import android.widget.SeekBar; 25 | import android.widget.TextView; 26 | 27 | import cn.demo.videolist.media.IMediaController; 28 | import cn.demo.videolist.media.IjkVideoView; 29 | import tv.danmaku.ijk.media.player.IMediaPlayer; 30 | 31 | /** 32 | * @Description ${控制器} 33 | */ 34 | public class CustomMediaContoller implements IMediaController { 35 | 36 | private static final int SET_VIEW_HIDE = 1; 37 | private static final int TIME_OUT = 5000; 38 | private static final int MESSAGE_SHOW_PROGRESS = 2; 39 | private static final int PAUSE_IMAGE_HIDE = 3; 40 | private static final int MESSAGE_SEEK_NEW_POSITION = 4; 41 | private static final int MESSAGE_HIDE_CONTOLL = 5; 42 | private View itemView; 43 | private View view; 44 | private boolean isShow; 45 | private IjkVideoView videoView; 46 | private boolean isScroll; 47 | 48 | private SeekBar seekBar; 49 | AudioManager audioManager; 50 | private ProgressBar progressBar; 51 | 52 | private boolean isSound; 53 | private boolean isDragging; 54 | 55 | private boolean isPause; 56 | 57 | private boolean isShowContoller; 58 | private ImageView sound, full, play; 59 | private TextView time, allTime,seekTxt; 60 | private PointF lastPoint; 61 | private Context context; 62 | private ImageView pauseImage; 63 | private Bitmap bitmap; 64 | private GestureDetector detector; 65 | 66 | private RelativeLayout show; 67 | private VSeekBar brightness_seek, sound_seek; 68 | private LinearLayout brightness_layout, sound_layout; 69 | private Handler handler = new Handler() { 70 | @Override 71 | public void handleMessage(Message msg) { 72 | super.handleMessage(msg); 73 | switch (msg.what) { 74 | case SET_VIEW_HIDE: 75 | isShow = false; 76 | itemView.setVisibility(View.GONE); 77 | break; 78 | case MESSAGE_SHOW_PROGRESS: 79 | setProgress(); 80 | if (!isDragging && isShow) { 81 | msg = obtainMessage(MESSAGE_SHOW_PROGRESS); 82 | sendMessageDelayed(msg, 1000); 83 | } 84 | break; 85 | case PAUSE_IMAGE_HIDE: 86 | pauseImage.setVisibility(View.GONE); 87 | break; 88 | case MESSAGE_SEEK_NEW_POSITION: 89 | if (newPosition >= 0) { 90 | videoView.seekTo((int) newPosition); 91 | newPosition = -1; 92 | } 93 | break; 94 | case MESSAGE_HIDE_CONTOLL: 95 | seekTxt.setVisibility(View.GONE); 96 | brightness_layout.setVisibility(View.GONE); 97 | sound_layout.setVisibility(View.GONE); 98 | break; 99 | } 100 | } 101 | }; 102 | 103 | public CustomMediaContoller(Context context, View view) { 104 | this.view = view; 105 | itemView = view.findViewById(R.id.media_contoller); 106 | this.videoView = (IjkVideoView) view.findViewById(R.id.main_video); 107 | itemView.setVisibility(View.GONE); 108 | isShow = false; 109 | isDragging = false; 110 | 111 | isShowContoller = true; 112 | this.context = context; 113 | audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 114 | initView(); 115 | initAction(); 116 | } 117 | 118 | public void initView() { 119 | progressBar = (ProgressBar) view.findViewById(R.id.loading); 120 | seekBar = (SeekBar) itemView.findViewById(R.id.seekbar); 121 | allTime = (TextView) itemView.findViewById(R.id.all_time); 122 | time = (TextView) itemView.findViewById(R.id.time); 123 | full = (ImageView) itemView.findViewById(R.id.full); 124 | sound = (ImageView) itemView.findViewById(R.id.sound); 125 | play = (ImageView) itemView.findViewById(R.id.player_btn); 126 | pauseImage = (ImageView) view.findViewById(R.id.pause_image); 127 | 128 | brightness_layout = (LinearLayout) view.findViewById(R.id.brightness_layout); 129 | brightness_seek = (VSeekBar) view.findViewById(R.id.brightness_seek); 130 | sound_layout = (LinearLayout) view.findViewById(R.id.sound_layout); 131 | sound_seek = (VSeekBar) view.findViewById(R.id.sound_seek); 132 | show = (RelativeLayout) view.findViewById(R.id.show); 133 | seekTxt= (TextView) view.findViewById(R.id.seekTxt); 134 | } 135 | 136 | private void initAction() { 137 | 138 | sound_seek.setEnabled(false); 139 | brightness_seek.setEnabled(false); 140 | isSound = false; 141 | detector = new GestureDetector(context, new PlayGestureListener()); 142 | mMaxVolume = ((AudioManager) context.getSystemService(Context.AUDIO_SERVICE)) 143 | .getStreamMaxVolume(AudioManager.STREAM_MUSIC); 144 | seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 145 | @Override 146 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 147 | String string = generateTime((long) (duration * progress * 1.0f / 100)); 148 | time.setText(string); 149 | } 150 | 151 | @Override 152 | public void onStartTrackingTouch(SeekBar seekBar) { 153 | setProgress(); 154 | isDragging = true; 155 | audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true); 156 | handler.removeMessages(MESSAGE_SHOW_PROGRESS); 157 | show(); 158 | handler.removeMessages(SET_VIEW_HIDE); 159 | } 160 | 161 | @Override 162 | public void onStopTrackingTouch(SeekBar seekBar) { 163 | isDragging = false; 164 | videoView.seekTo((int) (duration * seekBar.getProgress() * 1.0f / 100)); 165 | handler.removeMessages(MESSAGE_SHOW_PROGRESS); 166 | audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false); 167 | isDragging = false; 168 | handler.sendEmptyMessageDelayed(MESSAGE_SHOW_PROGRESS, 1000); 169 | show(); 170 | } 171 | }); 172 | 173 | view.setOnTouchListener(new View.OnTouchListener() { 174 | @Override 175 | public boolean onTouch(View v, MotionEvent event) { 176 | if (detector.onTouchEvent(event)) 177 | return true; 178 | 179 | // 处理手势结束 180 | switch (event.getAction() & MotionEvent.ACTION_MASK) { 181 | case MotionEvent.ACTION_UP: 182 | endGesture(); 183 | break; 184 | } 185 | return false; 186 | } 187 | }); 188 | 189 | itemView.setOnTouchListener(new View.OnTouchListener() { 190 | @Override 191 | public boolean onTouch(View v, MotionEvent event) { 192 | Log.e("custommedia", "event"); 193 | 194 | Rect seekRect = new Rect(); 195 | seekBar.getHitRect(seekRect); 196 | 197 | if ((event.getY() >= (seekRect.top - 50)) && (event.getY() <= (seekRect.bottom + 50))) { 198 | 199 | float y = seekRect.top + seekRect.height() / 2; 200 | //seekBar only accept relative x 201 | float x = event.getX() - seekRect.left; 202 | if (x < 0) { 203 | x = 0; 204 | } else if (x > seekRect.width()) { 205 | x = seekRect.width(); 206 | } 207 | MotionEvent me = MotionEvent.obtain(event.getDownTime(), event.getEventTime(), 208 | event.getAction(), x, y, event.getMetaState()); 209 | return seekBar.onTouchEvent(me); 210 | 211 | } 212 | return false; 213 | } 214 | }); 215 | 216 | videoView.setOnInfoListener(new IMediaPlayer.OnInfoListener() { 217 | @Override 218 | public boolean onInfo(IMediaPlayer mp, int what, int extra) { 219 | 220 | Log.e("setOnInfoListener", what + ""); 221 | switch (what) { 222 | case IMediaPlayer.MEDIA_INFO_BUFFERING_START: 223 | //开始缓冲 224 | if (progressBar.getVisibility() == View.GONE) 225 | progressBar.setVisibility(View.VISIBLE); 226 | break; 227 | case IMediaPlayer.MEDIA_INFO_BUFFERING_END: 228 | //开始播放 229 | progressBar.setVisibility(View.GONE); 230 | break; 231 | 232 | case IMediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START: 233 | // statusChange(STATUS_PLAYING); 234 | progressBar.setVisibility(View.GONE); 235 | break; 236 | 237 | case IMediaPlayer.MEDIA_INFO_AUDIO_RENDERING_START: 238 | progressBar.setVisibility(View.GONE); 239 | break; 240 | } 241 | return false; 242 | } 243 | }); 244 | 245 | sound.setOnClickListener(new View.OnClickListener() { 246 | @Override 247 | public void onClick(View v) { 248 | if (isSound) { 249 | //静音 250 | sound.setImageResource(R.mipmap.sound_mult_icon); 251 | audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true); 252 | } else { 253 | //取消静音 254 | sound.setImageResource(R.mipmap.sound_open_icon); 255 | audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false); 256 | } 257 | isSound = !isSound; 258 | } 259 | }); 260 | 261 | 262 | play.setOnClickListener(new View.OnClickListener() { 263 | @Override 264 | public void onClick(View v) { 265 | if (videoView.isPlaying()) { 266 | pause(); 267 | } else { 268 | reStart(); 269 | } 270 | } 271 | }); 272 | 273 | full.setOnClickListener(new View.OnClickListener() { 274 | @Override 275 | public void onClick(View v) { 276 | Log.e("full", "full"); 277 | if (getScreenOrientation((Activity) context) == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) { 278 | ((Activity) context).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); 279 | } else { 280 | ((Activity) context).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); 281 | } 282 | } 283 | 284 | }); 285 | } 286 | 287 | public void start() { 288 | pauseImage.setVisibility(View.GONE); 289 | itemView.setVisibility(View.GONE); 290 | play.setImageResource(R.mipmap.video_stop_btn); 291 | progressBar.setVisibility(View.VISIBLE); 292 | } 293 | 294 | public void pause() { 295 | play.setImageResource(R.mipmap.video_play_btn); 296 | videoView.pause(); 297 | bitmap = videoView.getBitmap(); 298 | if (bitmap != null) { 299 | pauseImage.setImageBitmap(bitmap); 300 | pauseImage.setVisibility(View.VISIBLE); 301 | } 302 | } 303 | 304 | public void reStart() { 305 | play.setImageResource(R.mipmap.video_stop_btn); 306 | videoView.start(); 307 | if (bitmap != null) { 308 | handler.sendEmptyMessageDelayed(PAUSE_IMAGE_HIDE, 100); 309 | bitmap.recycle(); 310 | bitmap = null; 311 | // pauseImage.setVisibility(View.GONE); 312 | } 313 | } 314 | 315 | private long duration; 316 | 317 | 318 | public void setShowContoller(boolean isShowContoller) { 319 | this.isShowContoller = isShowContoller; 320 | handler.removeMessages(SET_VIEW_HIDE); 321 | itemView.setVisibility(View.GONE); 322 | } 323 | 324 | public int getScreenOrientation(Activity activity) { 325 | int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); 326 | DisplayMetrics dm = new DisplayMetrics(); 327 | activity.getWindowManager().getDefaultDisplay().getMetrics(dm); 328 | int width = dm.widthPixels; 329 | int height = dm.heightPixels; 330 | int orientation; 331 | // if the device's natural orientation is portrait: 332 | if ((rotation == Surface.ROTATION_0 333 | || rotation == Surface.ROTATION_180) && height > width || 334 | (rotation == Surface.ROTATION_90 335 | || rotation == Surface.ROTATION_270) && width > height) { 336 | switch (rotation) { 337 | case Surface.ROTATION_0: 338 | orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; 339 | break; 340 | case Surface.ROTATION_90: 341 | orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; 342 | break; 343 | case Surface.ROTATION_180: 344 | orientation = 345 | ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; 346 | break; 347 | case Surface.ROTATION_270: 348 | orientation = 349 | ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; 350 | break; 351 | default: 352 | orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; 353 | break; 354 | } 355 | } 356 | // if the device's natural orientation is landscape or if the device 357 | // is square: 358 | else { 359 | switch (rotation) { 360 | case Surface.ROTATION_0: 361 | orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; 362 | break; 363 | case Surface.ROTATION_90: 364 | orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; 365 | break; 366 | case Surface.ROTATION_180: 367 | orientation = 368 | ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; 369 | break; 370 | case Surface.ROTATION_270: 371 | orientation = 372 | ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; 373 | break; 374 | default: 375 | orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; 376 | break; 377 | } 378 | } 379 | 380 | return orientation; 381 | } 382 | 383 | @Override 384 | public void hide() { 385 | if (isShow) { 386 | handler.removeMessages(MESSAGE_SHOW_PROGRESS); 387 | isShow = false; 388 | handler.removeMessages(SET_VIEW_HIDE); 389 | itemView.setVisibility(View.GONE); 390 | } 391 | } 392 | 393 | @Override 394 | public boolean isShowing() { 395 | return isShow; 396 | } 397 | 398 | @Override 399 | public void setAnchorView(View view) { 400 | } 401 | 402 | @Override 403 | public void setEnabled(boolean enabled) { 404 | } 405 | 406 | @Override 407 | public void setMediaPlayer(MediaController.MediaPlayerControl player) { 408 | } 409 | 410 | @Override 411 | public void show(int timeout) { 412 | handler.sendEmptyMessageDelayed(SET_VIEW_HIDE, timeout); 413 | } 414 | 415 | @Override 416 | public void show() { 417 | if (!isShowContoller) 418 | return; 419 | isShow = true; 420 | progressBar.setVisibility(View.GONE); 421 | itemView.setVisibility(View.VISIBLE); 422 | handler.sendEmptyMessage(MESSAGE_SHOW_PROGRESS); 423 | show(TIME_OUT); 424 | } 425 | 426 | @Override 427 | public void showOnce(View view) { 428 | } 429 | 430 | private String generateTime(long time) { 431 | int totalSeconds = (int) (time / 1000); 432 | int seconds = totalSeconds % 60; 433 | int minutes = (totalSeconds / 60) % 60; 434 | int hours = totalSeconds / 3600; 435 | return hours > 0 ? String.format("%02d:%02d:%02d", hours, minutes, seconds) : String.format("%02d:%02d", minutes, seconds); 436 | } 437 | 438 | public void setVisiable() { 439 | show(); 440 | } 441 | 442 | private long setProgress() { 443 | if (isDragging) { 444 | return 0; 445 | } 446 | 447 | long position = videoView.getCurrentPosition(); 448 | long duration = videoView.getDuration(); 449 | this.duration = duration; 450 | if (!generateTime(duration).equals(allTime.getText().toString())) 451 | allTime.setText(generateTime(duration)); 452 | if (seekBar != null) { 453 | if (duration > 0) { 454 | long pos = 100L * position / duration; 455 | seekBar.setProgress((int) pos); 456 | } 457 | int percent = videoView.getBufferPercentage(); 458 | seekBar.setSecondaryProgress(percent); 459 | } 460 | String string = generateTime((long) (duration * seekBar.getProgress() * 1.0f / 100)); 461 | time.setText(string); 462 | return position; 463 | } 464 | 465 | private VedioIsPause vedioIsPause; 466 | 467 | public interface VedioIsPause { 468 | void pause(boolean pause); 469 | } 470 | 471 | public void setPauseImageHide() { 472 | pauseImage.setVisibility(View.GONE); 473 | } 474 | 475 | public class PlayGestureListener extends GestureDetector.SimpleOnGestureListener { 476 | 477 | private boolean firstTouch; 478 | private boolean volumeControl; 479 | private boolean seek; 480 | 481 | @Override 482 | public boolean onDown(MotionEvent e) { 483 | firstTouch = true; 484 | handler.removeMessages(SET_VIEW_HIDE); 485 | //横屏下拦截事件 486 | if (getScreenOrientation((Activity) context) == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) { 487 | return true; 488 | }else { 489 | return super.onDown(e); 490 | } 491 | } 492 | 493 | @Override 494 | public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 495 | float x = e1.getX() - e2.getX(); 496 | float y = e1.getY() - e2.getY(); 497 | if (firstTouch) { 498 | seek = Math.abs(distanceX) >= Math.abs(distanceY); 499 | volumeControl = e1.getX() < view.getMeasuredWidth() * 0.5; 500 | firstTouch = false; 501 | } 502 | 503 | if (seek) { 504 | onProgressSlide(-x / view.getWidth(),e1.getX()/view.getWidth()); 505 | } else { 506 | float percent = y / view.getHeight(); 507 | if (volumeControl) { 508 | onVolumeSlide(percent); 509 | } else { 510 | onBrightnessSlide(percent); 511 | } 512 | } 513 | return super.onScroll(e1, e2, distanceX, distanceY); 514 | } 515 | } 516 | 517 | private int volume = -1; 518 | private float brightness = -1; 519 | private long newPosition = 1; 520 | private int mMaxVolume; 521 | 522 | /** 523 | * 手势结束 524 | */ 525 | private void endGesture() { 526 | volume = -1; 527 | brightness = -1f; 528 | if (newPosition >= 0) { 529 | handler.removeMessages(MESSAGE_SEEK_NEW_POSITION); 530 | handler.sendEmptyMessage(MESSAGE_SEEK_NEW_POSITION); 531 | } 532 | handler.removeMessages(MESSAGE_HIDE_CONTOLL); 533 | handler.sendEmptyMessageDelayed(MESSAGE_HIDE_CONTOLL, 500); 534 | 535 | } 536 | 537 | /** 538 | * 滑动改变声音大小 539 | * 540 | * @param percent 541 | */ 542 | private void onVolumeSlide(float percent) { 543 | if (volume == -1) { 544 | volume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC); 545 | if (volume < 0) 546 | volume = 0; 547 | } 548 | // hide(); 549 | 550 | int index = (int) (percent * mMaxVolume) + volume; 551 | if (index > mMaxVolume) 552 | index = mMaxVolume; 553 | else if (index < 0) 554 | index = 0; 555 | 556 | // 变更声音 557 | audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, index, 0); 558 | 559 | if (sound_layout.getVisibility()==View.GONE) 560 | sound_layout.setVisibility(View.VISIBLE); 561 | // 变更进度条 562 | int i = (int) (index * 1.0f / mMaxVolume * 100); 563 | if (i == 0) { 564 | sound.setImageResource(R.mipmap.sound_mult_icon); 565 | } else { 566 | sound.setImageResource(R.mipmap.sound_open_icon); 567 | } 568 | sound_seek.setProgress(i); 569 | } 570 | 571 | /** 572 | * 滑动跳转 573 | * 574 | * @param percent 移动比例 575 | * @param downPer 按下比例 576 | */ 577 | private void onProgressSlide(float percent,float downPer) { 578 | long position = videoView.getCurrentPosition(); 579 | long duration = videoView.getDuration(); 580 | long deltaMax = Math.min(100 * 1000, duration - position); 581 | long delta = (long) (deltaMax * percent); 582 | 583 | newPosition = delta + position; 584 | if (newPosition > duration) { 585 | newPosition = duration; 586 | } else if (newPosition <= 0) { 587 | newPosition = 0; 588 | delta = -position; 589 | } 590 | int showDelta = (int) delta / 1000; 591 | Log.e("showdelta", ((downPer +percent)*100) + ""); 592 | if (showDelta != 0) { 593 | if (seekTxt.getVisibility()==View.GONE) 594 | seekTxt.setVisibility(View.VISIBLE); 595 | String current=generateTime(newPosition); 596 | seekTxt.setText(current+"/"+allTime.getText()); 597 | } 598 | } 599 | 600 | /** 601 | * 滑动改变亮度 602 | * 603 | * @param percent 604 | */ 605 | private void onBrightnessSlide(float percent) { 606 | if (brightness < 0) { 607 | brightness = ((Activity) context).getWindow().getAttributes().screenBrightness; 608 | if (brightness <= 0.00f) { 609 | brightness = 0.50f; 610 | } else if (brightness < 0.01f) { 611 | brightness = 0.01f; 612 | } 613 | } 614 | Log.d(this.getClass().getSimpleName(), "brightness:" + brightness + ",percent:" + percent); 615 | WindowManager.LayoutParams lpa = ((Activity) context).getWindow().getAttributes(); 616 | lpa.screenBrightness = brightness + percent; 617 | if (lpa.screenBrightness > 1.0f) { 618 | lpa.screenBrightness = 1.0f; 619 | } else if (lpa.screenBrightness < 0.01f) { 620 | lpa.screenBrightness = 0.01f; 621 | } 622 | 623 | if (brightness_layout.getVisibility()==View.GONE) 624 | brightness_layout.setVisibility(View.VISIBLE); 625 | 626 | brightness_seek.setProgress((int) (lpa.screenBrightness * 100)); 627 | ((Activity) context).getWindow().setAttributes(lpa); 628 | 629 | } 630 | } 631 | -------------------------------------------------------------------------------- /app/src/main/java/cn/demo/videolist/MainActivity.java: -------------------------------------------------------------------------------- 1 | package cn.demo.videolist; 2 | 3 | import android.content.pm.ActivityInfo; 4 | import android.content.res.Configuration; 5 | import android.os.Bundle; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.view.KeyEvent; 8 | import android.view.Window; 9 | 10 | public class MainActivity extends AppCompatActivity { 11 | 12 | @Override 13 | protected void onCreate(Bundle savedInstanceState) { 14 | requestWindowFeature(Window.FEATURE_NO_TITLE); 15 | super.onCreate(savedInstanceState); 16 | setContentView(R.layout.activity_main); 17 | initActions(); 18 | } 19 | 20 | private void initActions() { 21 | 22 | } 23 | 24 | @Override 25 | public boolean onKeyUp(int keyCode, KeyEvent event) { 26 | if (keyCode == KeyEvent.KEYCODE_BACK) { 27 | if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { 28 | setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); 29 | return true; 30 | } 31 | } 32 | return super.onKeyUp(keyCode, event); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/cn/demo/videolist/VSeekBar.java: -------------------------------------------------------------------------------- 1 | package cn.demo.videolist; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.graphics.Canvas; 6 | import android.os.Build; 7 | import android.util.AttributeSet; 8 | import android.widget.SeekBar; 9 | 10 | /** 11 | * Author wangchenchen 12 | * CreateDate 2016/8/9. 13 | * Email wcc@jusfoun.com 14 | * Description 垂直seekbar 15 | */ 16 | public class VSeekBar extends SeekBar { 17 | public VSeekBar(Context context, AttributeSet attrs) { 18 | super(context, attrs); 19 | } 20 | 21 | public VSeekBar(Context context) { 22 | super(context); 23 | } 24 | 25 | public VSeekBar(Context context, AttributeSet attrs, int defStyleAttr) { 26 | super(context, attrs, defStyleAttr); 27 | } 28 | 29 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 30 | public VSeekBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 31 | super(context, attrs, defStyleAttr, defStyleRes); 32 | } 33 | 34 | @Override 35 | protected synchronized void onDraw(Canvas canvas) { 36 | canvas.rotate(-90); 37 | canvas.translate(-getHeight(),0); 38 | super.onDraw(canvas); 39 | } 40 | 41 | @Override 42 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 43 | super.onSizeChanged(h, w, oldw, oldh); 44 | } 45 | 46 | @Override 47 | protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 48 | super.onMeasure(heightMeasureSpec, widthMeasureSpec); 49 | setMeasuredDimension(getMeasuredHeight(),getMeasuredWidth()); 50 | } 51 | 52 | @Override 53 | public synchronized void setProgress(int progress) { 54 | super.setProgress(progress); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/cn/demo/videolist/VideoItemData.java: -------------------------------------------------------------------------------- 1 | package cn.demo.videolist; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * Author wangchenchen 7 | * Description video model 8 | */ 9 | public class VideoItemData implements Serializable { 10 | int type; 11 | String cover; 12 | int playCount; 13 | String title; 14 | String mp4_url; 15 | String ptime; 16 | String vid; 17 | int length; 18 | String videosource; 19 | 20 | public int getType() { 21 | return type; 22 | } 23 | 24 | public void setType(int type) { 25 | this.type = type; 26 | } 27 | 28 | public String getCover() { 29 | return cover; 30 | } 31 | 32 | public void setCover(String cover) { 33 | this.cover = cover; 34 | } 35 | 36 | public int getPlayCount() { 37 | return playCount; 38 | } 39 | 40 | public void setPlayCount(int playCount) { 41 | this.playCount = playCount; 42 | } 43 | 44 | public String getTitle() { 45 | return title; 46 | } 47 | 48 | public void setTitle(String title) { 49 | this.title = title; 50 | } 51 | 52 | public String getMp4_url() { 53 | return mp4_url; 54 | } 55 | 56 | public void setMp4_url(String mp4_url) { 57 | this.mp4_url = mp4_url; 58 | } 59 | 60 | public String getPtime() { 61 | return ptime; 62 | } 63 | 64 | public void setPtime(String ptime) { 65 | this.ptime = ptime; 66 | } 67 | 68 | public String getVid() { 69 | return vid; 70 | } 71 | 72 | public void setVid(String vid) { 73 | this.vid = vid; 74 | } 75 | 76 | public int getLength() { 77 | return length; 78 | } 79 | 80 | public void setLength(int length) { 81 | this.length = length; 82 | } 83 | 84 | public String getVideosource() { 85 | return videosource; 86 | } 87 | 88 | public void setVideosource(String videosource) { 89 | this.videosource = videosource; 90 | } 91 | 92 | boolean isPlaying; 93 | 94 | public boolean isPlaying() { 95 | return isPlaying; 96 | } 97 | 98 | public void setPlaying(boolean playing) { 99 | isPlaying = playing; 100 | } 101 | 102 | int currentPositon; 103 | 104 | public int getCurrentPositon() { 105 | return currentPositon; 106 | } 107 | 108 | public void setCurrentPositon(int currentPositon) { 109 | this.currentPositon = currentPositon; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /app/src/main/java/cn/demo/videolist/VideoListData.java: -------------------------------------------------------------------------------- 1 | package cn.demo.videolist; 2 | 3 | import java.io.Serializable; 4 | import java.util.List; 5 | 6 | /** 7 | * Author wangchenchen 8 | * Description 9 | */ 10 | public class VideoListData implements Serializable { 11 | 12 | private List list; 13 | 14 | public List getList() { 15 | return list; 16 | } 17 | 18 | public void setList(List list) { 19 | this.list = list; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/cn/demo/videolist/VideoListLayout.java: -------------------------------------------------------------------------------- 1 | package cn.demo.videolist; 2 | 3 | import android.annotation.TargetApi; 4 | import android.app.Activity; 5 | import android.content.Context; 6 | import android.content.pm.ActivityInfo; 7 | import android.content.res.Configuration; 8 | import android.os.Build; 9 | import android.support.v7.widget.LinearLayoutManager; 10 | import android.support.v7.widget.RecyclerView; 11 | import android.util.AttributeSet; 12 | import android.util.Log; 13 | import android.view.LayoutInflater; 14 | import android.view.View; 15 | import android.view.ViewGroup; 16 | import android.widget.FrameLayout; 17 | import android.widget.ImageView; 18 | import android.widget.RelativeLayout; 19 | 20 | import com.google.gson.Gson; 21 | 22 | import java.io.BufferedReader; 23 | import java.io.InputStreamReader; 24 | 25 | import cn.demo.videolist.media.IjkVideoView; 26 | import cn.demo.videolist.media.VideoAdapter; 27 | import tv.danmaku.ijk.media.player.IMediaPlayer; 28 | 29 | /** 30 | * Author wangchenchen 31 | * CreateDate 2016/8/23. 32 | * Email wcc@jusfoun.com 33 | * Description 34 | */ 35 | public class VideoListLayout extends RelativeLayout { 36 | 37 | private RecyclerView videoList; 38 | private LinearLayoutManager mLayoutManager; 39 | private VideoAdapter adapter; 40 | private FrameLayout videoLayout; 41 | 42 | private int postion = -1; 43 | private int lastPostion = -1; 44 | private Context context; 45 | private VideoPlayView videoItemView; 46 | 47 | private FrameLayout fullScreen; 48 | private VideoListData listData; 49 | private RelativeLayout smallLayout; 50 | private ImageView close; 51 | 52 | public VideoListLayout(Context context) { 53 | super(context); 54 | initView(context); 55 | initActions(); 56 | } 57 | 58 | public VideoListLayout(Context context, AttributeSet attrs) { 59 | super(context, attrs); 60 | initView(context); 61 | initActions(); 62 | } 63 | 64 | public VideoListLayout(Context context, AttributeSet attrs, int defStyleAttr) { 65 | super(context, attrs, defStyleAttr); 66 | initView(context); 67 | initActions(); 68 | } 69 | 70 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 71 | public VideoListLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 72 | super(context, attrs, defStyleAttr, defStyleRes); 73 | initView(context); 74 | initActions(); 75 | } 76 | 77 | private void initView(Context context){ 78 | LayoutInflater.from(context).inflate(R.layout.layout_video_list,this,true); 79 | this.context = context; 80 | mLayoutManager = new LinearLayoutManager(context); 81 | videoList = (RecyclerView) findViewById(R.id.video_list); 82 | videoList.setLayoutManager(mLayoutManager); 83 | 84 | adapter = new VideoAdapter(context); 85 | videoList.setAdapter(adapter); 86 | fullScreen = (FrameLayout) findViewById(R.id.full_screen); 87 | videoLayout = (FrameLayout) findViewById(R.id.layout_video); 88 | videoItemView = new VideoPlayView(context); 89 | 90 | String data = readTextFileFromRawResourceId(context, R.raw.video_list); 91 | listData = new Gson().fromJson(data, VideoListData.class); 92 | adapter.refresh(listData.getList()); 93 | smallLayout = (RelativeLayout) findViewById(R.id.small_layout); 94 | close = (ImageView) findViewById(R.id.close); 95 | 96 | } 97 | 98 | private void initActions() { 99 | 100 | close.setOnClickListener(new View.OnClickListener() { 101 | @Override 102 | public void onClick(View v) { 103 | if (videoItemView.isPlay()) { 104 | videoItemView.stop(); 105 | postion = -1; 106 | lastPostion = -1; 107 | videoLayout.removeAllViews(); 108 | smallLayout.setVisibility(View.GONE); 109 | } 110 | } 111 | }); 112 | 113 | smallLayout.setOnClickListener(new View.OnClickListener() { 114 | @Override 115 | public void onClick(View v) { 116 | smallLayout.setVisibility(View.GONE); 117 | ((Activity)context).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); 118 | } 119 | }); 120 | 121 | videoItemView.setCompletionListener(new VideoPlayView.CompletionListener() { 122 | @Override 123 | public void completion(IMediaPlayer mp) { 124 | 125 | //播放完还原播放界面 126 | if (smallLayout.getVisibility() == View.VISIBLE) { 127 | videoLayout.removeAllViews(); 128 | smallLayout.setVisibility(View.GONE); 129 | videoItemView.setShowContoller(true); 130 | } 131 | 132 | FrameLayout frameLayout = (FrameLayout) videoItemView.getParent(); 133 | videoItemView.release(); 134 | if (frameLayout != null && frameLayout.getChildCount() > 0) { 135 | frameLayout.removeAllViews(); 136 | View itemView = (View) frameLayout.getParent(); 137 | 138 | if (itemView != null) { 139 | itemView.findViewById(R.id.showview).setVisibility(View.VISIBLE); 140 | } 141 | } 142 | 143 | lastPostion = -1; 144 | } 145 | }); 146 | 147 | adapter.setClick(new VideoAdapter.onClick() { 148 | @Override 149 | public void onclick(int position) { 150 | VideoListLayout.this.postion = position; 151 | 152 | if (videoItemView.VideoStatus() == IjkVideoView.STATE_PAUSED) { 153 | if (position != lastPostion) { 154 | 155 | videoItemView.stop(); 156 | videoItemView.release(); 157 | } 158 | } 159 | 160 | if (smallLayout.getVisibility() == View.VISIBLE) 161 | 162 | { 163 | smallLayout.setVisibility(View.GONE); 164 | videoLayout.removeAllViews(); 165 | videoItemView.setShowContoller(true); 166 | } 167 | 168 | if (lastPostion != -1) 169 | 170 | { 171 | ViewGroup last = (ViewGroup) videoItemView.getParent();//找到videoitemview的父类,然后remove 172 | if (last != null) { 173 | last.removeAllViews(); 174 | View itemView = (View) last.getParent(); 175 | if (itemView != null) { 176 | itemView.findViewById(R.id.showview).setVisibility(View.VISIBLE); 177 | } 178 | } 179 | } 180 | 181 | if (videoItemView.getParent() != null) { 182 | ((ViewGroup) videoItemView.getParent()).removeAllViews(); 183 | } 184 | 185 | View view = videoList.findViewHolderForAdapterPosition(postion).itemView; 186 | FrameLayout frameLayout = (FrameLayout) view.findViewById(R.id.layout_video); 187 | frameLayout.removeAllViews(); 188 | frameLayout.addView(videoItemView); 189 | videoItemView.start(listData.getList().get(position).getMp4_url()); 190 | lastPostion = position; 191 | } 192 | }); 193 | 194 | videoList.addOnChildAttachStateChangeListener(new RecyclerView.OnChildAttachStateChangeListener() { 195 | @Override 196 | public void onChildViewAttachedToWindow(View view) { 197 | int index = videoList.getChildAdapterPosition(view); 198 | view.findViewById(R.id.showview).setVisibility(View.VISIBLE); 199 | if (index == postion) { 200 | FrameLayout frameLayout = (FrameLayout) view.findViewById(R.id.layout_video); 201 | frameLayout.removeAllViews(); 202 | if (videoItemView != null && 203 | ((videoItemView.isPlay()) || videoItemView.VideoStatus() == IjkVideoView.STATE_PAUSED)) { 204 | view.findViewById(R.id.showview).setVisibility(View.GONE); 205 | } 206 | 207 | if (videoItemView.VideoStatus() == IjkVideoView.STATE_PAUSED) { 208 | if (videoItemView.getParent() != null) 209 | ((ViewGroup) videoItemView.getParent()).removeAllViews(); 210 | frameLayout.addView(videoItemView); 211 | return; 212 | } 213 | 214 | if (smallLayout.getVisibility() == View.VISIBLE && videoItemView != null && videoItemView.isPlay()) { 215 | smallLayout.setVisibility(View.GONE); 216 | videoLayout.removeAllViews(); 217 | videoItemView.setShowContoller(true); 218 | frameLayout.addView(videoItemView); 219 | } 220 | } 221 | } 222 | 223 | @Override 224 | public void onChildViewDetachedFromWindow(View view) { 225 | int index = videoList.getChildAdapterPosition(view); 226 | if (index == postion) { 227 | FrameLayout frameLayout = (FrameLayout) view.findViewById(R.id.layout_video); 228 | frameLayout.removeAllViews(); 229 | if (smallLayout.getVisibility() == View.GONE && videoItemView != null 230 | && videoItemView.isPlay()) { 231 | videoLayout.removeAllViews(); 232 | videoItemView.setShowContoller(false); 233 | videoLayout.addView(videoItemView); 234 | smallLayout.setVisibility(View.VISIBLE); 235 | } 236 | } 237 | } 238 | }); 239 | } 240 | 241 | @Override 242 | protected void onConfigurationChanged(Configuration newConfig) { 243 | super.onConfigurationChanged(newConfig); 244 | 245 | if (videoItemView != null) { 246 | videoItemView.onChanged(newConfig); 247 | if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { 248 | fullScreen.setVisibility(View.GONE); 249 | videoList.setVisibility(View.VISIBLE); 250 | fullScreen.removeAllViews(); 251 | if (postion <= mLayoutManager.findLastVisibleItemPosition() 252 | && postion >= mLayoutManager.findFirstVisibleItemPosition()) { 253 | View view = videoList.findViewHolderForAdapterPosition(postion).itemView; 254 | FrameLayout frameLayout = (FrameLayout) view.findViewById(R.id.layout_video); 255 | frameLayout.removeAllViews(); 256 | frameLayout.addView(videoItemView); 257 | videoItemView.setShowContoller(true); 258 | } else { 259 | videoLayout.removeAllViews(); 260 | videoLayout.addView(videoItemView); 261 | videoItemView.setShowContoller(false); 262 | smallLayout.setVisibility(View.VISIBLE); 263 | } 264 | videoItemView.setContorllerVisiable(); 265 | } else { 266 | ViewGroup viewGroup = (ViewGroup) videoItemView.getParent(); 267 | if (viewGroup == null) 268 | return; 269 | viewGroup.removeAllViews(); 270 | fullScreen.addView(videoItemView); 271 | smallLayout.setVisibility(View.GONE); 272 | videoList.setVisibility(View.GONE); 273 | fullScreen.setVisibility(View.VISIBLE); 274 | } 275 | } else { 276 | adapter.notifyDataSetChanged(); 277 | videoList.setVisibility(View.VISIBLE); 278 | fullScreen.setVisibility(View.GONE); 279 | } 280 | } 281 | 282 | @Override 283 | protected void onAttachedToWindow() { 284 | super.onAttachedToWindow(); 285 | if (videoItemView==null) 286 | videoItemView=new VideoPlayView(context); 287 | } 288 | 289 | @Override 290 | protected void onDetachedFromWindow() { 291 | super.onDetachedFromWindow(); 292 | if (videoLayout == null) 293 | return; 294 | if (smallLayout.getVisibility() == View.VISIBLE) { 295 | smallLayout.setVisibility(View.GONE); 296 | videoLayout.removeAllViews(); 297 | } 298 | 299 | if (postion != -1) { 300 | ViewGroup view = (ViewGroup) videoItemView.getParent(); 301 | if (view != null) { 302 | view.removeAllViews(); 303 | } 304 | } 305 | videoItemView.stop(); 306 | videoItemView.release(); 307 | videoItemView.onDestroy(); 308 | videoItemView = null; 309 | } 310 | 311 | public String readTextFileFromRawResourceId(Context context, int resourceId) { 312 | StringBuilder builder = new StringBuilder(); 313 | 314 | BufferedReader reader = new BufferedReader(new InputStreamReader(context.getResources().openRawResource( 315 | resourceId))); 316 | 317 | try { 318 | for (String line = reader.readLine(); line != null; line = reader.readLine()) { 319 | builder.append(line).append("\n"); 320 | } 321 | } catch (Exception e) { 322 | throw new RuntimeException(e); 323 | } 324 | 325 | return builder.toString(); 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /app/src/main/java/cn/demo/videolist/VideoPlayView.java: -------------------------------------------------------------------------------- 1 | package cn.demo.videolist; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.pm.ActivityInfo; 6 | import android.content.res.Configuration; 7 | import android.media.MediaPlayer; 8 | import android.net.Uri; 9 | import android.os.Handler; 10 | import android.util.AttributeSet; 11 | import android.util.Log; 12 | import android.view.LayoutInflater; 13 | import android.view.OrientationEventListener; 14 | import android.view.View; 15 | import android.view.ViewGroup; 16 | import android.view.WindowManager; 17 | import android.widget.RelativeLayout; 18 | 19 | import cn.demo.videolist.media.IjkVideoView; 20 | import tv.danmaku.ijk.media.player.IMediaPlayer; 21 | 22 | /** 23 | * Description 播放view 24 | */ 25 | public class VideoPlayView extends RelativeLayout implements MediaPlayer.OnInfoListener, MediaPlayer.OnBufferingUpdateListener { 26 | 27 | 28 | private CustomMediaContoller mediaController; 29 | private View player_btn, view; 30 | private IjkVideoView mVideoView; 31 | private Handler handler = new Handler(); 32 | private boolean isPause; 33 | 34 | private View rView; 35 | private Context mContext; 36 | private boolean portrait; 37 | // private OrientationEventListener orientationEventListener; 38 | 39 | public VideoPlayView(Context context) { 40 | super(context); 41 | mContext = context; 42 | initData(); 43 | initViews(); 44 | initActions(); 45 | } 46 | 47 | private void initData() { 48 | 49 | } 50 | 51 | private void initViews() { 52 | 53 | rView = LayoutInflater.from(mContext).inflate(R.layout.view_video_item, this, true); 54 | view = findViewById(R.id.media_contoller); 55 | mVideoView = (IjkVideoView) findViewById(R.id.main_video); 56 | mediaController = new CustomMediaContoller(mContext, rView); 57 | mVideoView.setMediaController(mediaController); 58 | 59 | mVideoView.setOnCompletionListener(new IMediaPlayer.OnCompletionListener() { 60 | @Override 61 | public void onCompletion(IMediaPlayer mp) { 62 | view.setVisibility(View.GONE); 63 | if (mediaController.getScreenOrientation((Activity) mContext) 64 | == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) { 65 | //横屏播放完毕,重置 66 | ((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); 67 | ViewGroup.LayoutParams layoutParams = getLayoutParams(); 68 | layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT; 69 | layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; 70 | setLayoutParams(layoutParams); 71 | } 72 | if (completionListener != null) 73 | completionListener.completion(mp); 74 | } 75 | }); 76 | 77 | } 78 | 79 | private void initActions() { 80 | 81 | /*orientationEventListener = new OrientationEventListener(mContext) { 82 | @Override 83 | public void onOrientationChanged(int orientation) { 84 | Log.e("onOrientationChanged", "orientation"); 85 | if (orientation >= 0 && orientation <= 30 || orientation >= 330 || (orientation >= 150 && orientation <= 210)) { 86 | //竖屏 87 | if (portrait) { 88 | ((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR); 89 | orientationEventListener.disable(); 90 | } 91 | } else if ((orientation >= 90 && orientation <= 120) || (orientation >= 240 && orientation <= 300)) { 92 | if (!portrait) { 93 | ((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR); 94 | orientationEventListener.disable(); 95 | } 96 | } 97 | } 98 | };*/ 99 | } 100 | 101 | public boolean isPlay() { 102 | return mVideoView.isPlaying(); 103 | } 104 | 105 | public void pause() { 106 | if (mVideoView.isPlaying()) { 107 | mVideoView.pause(); 108 | } else { 109 | mVideoView.start(); 110 | } 111 | } 112 | 113 | public void start(String path) { 114 | Uri uri = Uri.parse(path); 115 | if (mediaController != null) 116 | mediaController.start(); 117 | if (!mVideoView.isPlaying()) { 118 | mVideoView.setVideoURI(uri); 119 | mVideoView.start(); 120 | } else { 121 | mVideoView.stopPlayback(); 122 | mVideoView.setVideoURI(uri); 123 | mVideoView.start(); 124 | } 125 | } 126 | 127 | public void start(){ 128 | if (mVideoView.isPlaying()){ 129 | mVideoView.start(); 130 | } 131 | } 132 | 133 | public void setContorllerVisiable(){ 134 | mediaController.setVisiable(); 135 | } 136 | 137 | public void seekTo(int msec){ 138 | mVideoView.seekTo(msec); 139 | } 140 | 141 | public void onChanged(Configuration configuration) { 142 | portrait = configuration.orientation == Configuration.ORIENTATION_PORTRAIT; 143 | doOnConfigurationChanged(portrait); 144 | } 145 | 146 | public void doOnConfigurationChanged(final boolean portrait) { 147 | if (mVideoView != null) { 148 | handler.post(new Runnable() { 149 | @Override 150 | public void run() { 151 | setFullScreen(!portrait); 152 | if (portrait) { 153 | ViewGroup.LayoutParams layoutParams = getLayoutParams(); 154 | layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT; 155 | layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; 156 | Log.e("handler", "400"); 157 | setLayoutParams(layoutParams); 158 | requestLayout(); 159 | } else { 160 | int heightPixels = ((Activity) mContext).getResources().getDisplayMetrics().heightPixels; 161 | int widthPixels = ((Activity) mContext).getResources().getDisplayMetrics().widthPixels; 162 | ViewGroup.LayoutParams layoutParams = getLayoutParams(); 163 | layoutParams.height = heightPixels; 164 | layoutParams.width = widthPixels; 165 | Log.e("handler", "height==" + heightPixels + "\nwidth==" + widthPixels); 166 | setLayoutParams(layoutParams); 167 | } 168 | } 169 | }); 170 | // orientationEventListener.enable(); 171 | } 172 | } 173 | 174 | public void stop() { 175 | if (mVideoView.isPlaying()) { 176 | mVideoView.stopPlayback(); 177 | } 178 | } 179 | 180 | public void onDestroy() { 181 | handler.removeCallbacksAndMessages(null); 182 | // orientationEventListener.disable(); 183 | } 184 | 185 | private void setFullScreen(boolean fullScreen) { 186 | if (mContext != null && mContext instanceof Activity) { 187 | WindowManager.LayoutParams attrs = ((Activity) mContext).getWindow().getAttributes(); 188 | if (fullScreen) { 189 | attrs.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN; 190 | ((Activity) mContext).getWindow().setAttributes(attrs); 191 | ((Activity) mContext).getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); 192 | } else { 193 | attrs.flags &= (~WindowManager.LayoutParams.FLAG_FULLSCREEN); 194 | ((Activity) mContext).getWindow().setAttributes(attrs); 195 | ((Activity) mContext).getWindow().clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); 196 | } 197 | } 198 | 199 | } 200 | 201 | public void setShowContoller(boolean isShowContoller) { 202 | mediaController.setShowContoller(isShowContoller); 203 | } 204 | 205 | @Override 206 | public void onBufferingUpdate(MediaPlayer mp, int percent) { 207 | 208 | } 209 | 210 | @Override 211 | public boolean onInfo(MediaPlayer mp, int what, int extra) { 212 | return false; 213 | } 214 | 215 | public long getPalyPostion() { 216 | return mVideoView.getCurrentPosition(); 217 | } 218 | 219 | public void release() { 220 | mVideoView.release(true); 221 | } 222 | 223 | public int VideoStatus() { 224 | return mVideoView.getCurrentStatue(); 225 | } 226 | 227 | private CompletionListener completionListener; 228 | 229 | public void setCompletionListener(CompletionListener completionListener) { 230 | this.completionListener = completionListener; 231 | } 232 | 233 | public interface CompletionListener { 234 | void completion(IMediaPlayer mp); 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /app/src/main/java/cn/demo/videolist/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 cn.demo.videolist.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 | //---------- 38 | // Extends 39 | //---------- 40 | void showOnce(View view); 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/cn/demo/videolist/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 cn.demo.videolist.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 | -------------------------------------------------------------------------------- /app/src/main/java/cn/demo/videolist/media/InfoHudViewHolder.java: -------------------------------------------------------------------------------- 1 | package cn.demo.videolist.media; 2 | 3 | import android.content.Context; 4 | import android.os.Handler; 5 | import android.os.Message; 6 | import android.util.SparseArray; 7 | import android.view.View; 8 | import android.widget.TableLayout; 9 | 10 | import java.util.Locale; 11 | 12 | import cn.demo.videolist.R; 13 | import tv.danmaku.ijk.media.player.IMediaPlayer; 14 | import tv.danmaku.ijk.media.player.IjkMediaPlayer; 15 | import tv.danmaku.ijk.media.player.MediaPlayerProxy; 16 | 17 | public class InfoHudViewHolder { 18 | private TableLayoutBinder mTableLayoutBinder; 19 | private SparseArray mRowMap = new SparseArray(); 20 | private IMediaPlayer mMediaPlayer; 21 | 22 | public InfoHudViewHolder(Context context, TableLayout tableLayout) { 23 | mTableLayoutBinder = new TableLayoutBinder(context, tableLayout); 24 | } 25 | 26 | private void appendSection(int nameId) { 27 | mTableLayoutBinder.appendSection(nameId); 28 | } 29 | 30 | private void appendRow(int nameId) { 31 | View rowView = mTableLayoutBinder.appendRow2(nameId, null); 32 | mRowMap.put(nameId, rowView); 33 | } 34 | 35 | private void setRowValue(int id, String value) { 36 | View rowView = mRowMap.get(id); 37 | if (rowView == null) { 38 | rowView = mTableLayoutBinder.appendRow2(id, value); 39 | mRowMap.put(id, rowView); 40 | } else { 41 | mTableLayoutBinder.setValueText(rowView, value); 42 | } 43 | } 44 | 45 | public void setMediaPlayer(IMediaPlayer mp) { 46 | mMediaPlayer = mp; 47 | if (mMediaPlayer != null) { 48 | mHandler.sendEmptyMessageDelayed(MSG_UPDATE_HUD, 500); 49 | } else { 50 | mHandler.removeMessages(MSG_UPDATE_HUD); 51 | } 52 | } 53 | 54 | private static String formatedDurationMilli(long duration) { 55 | if (duration >= 1000) { 56 | return String.format(Locale.US, "%.2f sec", ((float)duration) / 1000); 57 | } else { 58 | return String.format(Locale.US, "%d msec", duration); 59 | } 60 | } 61 | 62 | private static String formatedSize(long bytes) { 63 | if (bytes >= 100 * 1000) { 64 | return String.format(Locale.US, "%.2f MB", ((float)bytes) / 1000 / 1000); 65 | } else if (bytes >= 100) { 66 | return String.format(Locale.US, "%.1f KB", ((float)bytes) / 1000); 67 | } else { 68 | return String.format(Locale.US, "%d B", bytes); 69 | } 70 | } 71 | 72 | private static final int MSG_UPDATE_HUD = 1; 73 | private Handler mHandler = new Handler() { 74 | @Override 75 | public void handleMessage(Message msg) { 76 | switch (msg.what) { 77 | case MSG_UPDATE_HUD: { 78 | InfoHudViewHolder holder = InfoHudViewHolder.this; 79 | IjkMediaPlayer mp = null; 80 | if (mMediaPlayer == null) 81 | break; 82 | if (mMediaPlayer instanceof IjkMediaPlayer) { 83 | mp = (IjkMediaPlayer) mMediaPlayer; 84 | } else if (mMediaPlayer instanceof MediaPlayerProxy) { 85 | MediaPlayerProxy proxy = (MediaPlayerProxy) mMediaPlayer; 86 | IMediaPlayer internal = proxy.getInternalMediaPlayer(); 87 | if (internal != null && internal instanceof IjkMediaPlayer) 88 | mp = (IjkMediaPlayer) internal; 89 | } 90 | if (mp == null) 91 | break; 92 | 93 | int vdec = mp.getVideoDecoder(); 94 | switch (vdec) { 95 | case IjkMediaPlayer.FFP_PROPV_DECODER_AVCODEC: 96 | setRowValue(R.string.vdec, "avcodec"); 97 | break; 98 | case IjkMediaPlayer.FFP_PROPV_DECODER_MEDIACODEC: 99 | setRowValue(R.string.vdec, "MediaCodec"); 100 | break; 101 | default: 102 | setRowValue(R.string.vdec, ""); 103 | break; 104 | } 105 | 106 | float fpsOutput = mp.getVideoOutputFramesPerSecond(); 107 | float fpsDecode = mp.getVideoDecodeFramesPerSecond(); 108 | setRowValue(R.string.fps, String.format(Locale.US, "%.2f / %.2f", fpsDecode, fpsOutput)); 109 | 110 | long videoCachedDuration = mp.getVideoCachedDuration(); 111 | long audioCachedDuration = mp.getAudioCachedDuration(); 112 | long videoCachedBytes = mp.getVideoCachedBytes(); 113 | long audioCachedBytes = mp.getAudioCachedBytes(); 114 | 115 | setRowValue(R.string.v_cache, String.format(Locale.US, "%s, %s", formatedDurationMilli(videoCachedDuration), formatedSize(videoCachedBytes))); 116 | setRowValue(R.string.a_cache, String.format(Locale.US, "%s, %s", formatedDurationMilli(audioCachedDuration), formatedSize(audioCachedBytes))); 117 | 118 | mHandler.removeMessages(MSG_UPDATE_HUD); 119 | mHandler.sendEmptyMessageDelayed(MSG_UPDATE_HUD, 500); 120 | } 121 | } 122 | } 123 | }; 124 | } 125 | -------------------------------------------------------------------------------- /app/src/main/java/cn/demo/videolist/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 cn.demo.videolist.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 | import cn.demo.videolist.R; 26 | 27 | 28 | public final class MeasureHelper { 29 | private WeakReference mWeakView; 30 | 31 | private int mVideoWidth; 32 | private int mVideoHeight; 33 | private int mVideoSarNum; 34 | private int mVideoSarDen; 35 | 36 | private int mVideoRotationDegree; 37 | 38 | private int mMeasuredWidth; 39 | private int mMeasuredHeight; 40 | 41 | private int mCurrentAspectRatio = IRenderView.AR_ASPECT_FIT_PARENT; 42 | 43 | public MeasureHelper(View view) { 44 | mWeakView = new WeakReference(view); 45 | } 46 | 47 | public View getView() { 48 | if (mWeakView == null) 49 | return null; 50 | return mWeakView.get(); 51 | } 52 | 53 | public void setVideoSize(int videoWidth, int videoHeight) { 54 | mVideoWidth = videoWidth; 55 | mVideoHeight = videoHeight; 56 | } 57 | 58 | public void setVideoSampleAspectRatio(int videoSarNum, int videoSarDen) { 59 | mVideoSarNum = videoSarNum; 60 | mVideoSarDen = videoSarDen; 61 | } 62 | 63 | public void setVideoRotation(int videoRotationDegree) { 64 | mVideoRotationDegree = videoRotationDegree; 65 | } 66 | 67 | /** 68 | * Must be called by View.onMeasure(int, int) 69 | * 70 | * @param widthMeasureSpec 71 | * @param heightMeasureSpec 72 | */ 73 | public void doMeasure(int widthMeasureSpec, int heightMeasureSpec) { 74 | //Log.i("@@@@", "onMeasure(" + MeasureSpec.toString(widthMeasureSpec) + ", " 75 | // + MeasureSpec.toString(heightMeasureSpec) + ")"); 76 | if (mVideoRotationDegree == 90 || mVideoRotationDegree == 270) { 77 | int tempSpec = widthMeasureSpec; 78 | widthMeasureSpec = heightMeasureSpec; 79 | heightMeasureSpec = tempSpec; 80 | } 81 | 82 | int width = View.getDefaultSize(mVideoWidth, widthMeasureSpec); 83 | int height = View.getDefaultSize(mVideoHeight, heightMeasureSpec); 84 | if (mCurrentAspectRatio == IRenderView.AR_MATCH_PARENT) { 85 | width = widthMeasureSpec; 86 | height = heightMeasureSpec; 87 | } else if (mVideoWidth > 0 && mVideoHeight > 0) { 88 | int widthSpecMode = View.MeasureSpec.getMode(widthMeasureSpec); 89 | int widthSpecSize = View.MeasureSpec.getSize(widthMeasureSpec); 90 | int heightSpecMode = View.MeasureSpec.getMode(heightMeasureSpec); 91 | int heightSpecSize = View.MeasureSpec.getSize(heightMeasureSpec); 92 | 93 | if (widthSpecMode == View.MeasureSpec.AT_MOST && heightSpecMode == View.MeasureSpec.AT_MOST) { 94 | float specAspectRatio = (float) widthSpecSize / (float) heightSpecSize; 95 | float displayAspectRatio; 96 | switch (mCurrentAspectRatio) { 97 | case IRenderView.AR_16_9_FIT_PARENT: 98 | displayAspectRatio = 16.0f / 9.0f; 99 | if (mVideoRotationDegree == 90 || mVideoRotationDegree == 270) 100 | displayAspectRatio = 1.0f / displayAspectRatio; 101 | break; 102 | case IRenderView.AR_4_3_FIT_PARENT: 103 | displayAspectRatio = 4.0f / 3.0f; 104 | if (mVideoRotationDegree == 90 || mVideoRotationDegree == 270) 105 | displayAspectRatio = 1.0f / displayAspectRatio; 106 | break; 107 | case IRenderView.AR_ASPECT_FIT_PARENT: 108 | case IRenderView.AR_ASPECT_FILL_PARENT: 109 | case IRenderView.AR_ASPECT_WRAP_CONTENT: 110 | default: 111 | displayAspectRatio = (float) mVideoWidth / (float) mVideoHeight; 112 | if (mVideoSarNum > 0 && mVideoSarDen > 0) 113 | displayAspectRatio = displayAspectRatio * mVideoSarNum / mVideoSarDen; 114 | break; 115 | } 116 | boolean shouldBeWider = displayAspectRatio > specAspectRatio; 117 | 118 | switch (mCurrentAspectRatio) { 119 | case IRenderView.AR_ASPECT_FIT_PARENT: 120 | case IRenderView.AR_16_9_FIT_PARENT: 121 | case IRenderView.AR_4_3_FIT_PARENT: 122 | if (shouldBeWider) { 123 | // too wide, fix width 124 | width = widthSpecSize; 125 | height = (int) (width / displayAspectRatio); 126 | } else { 127 | // too high, fix height 128 | height = heightSpecSize; 129 | width = (int) (height * displayAspectRatio); 130 | } 131 | break; 132 | case IRenderView.AR_ASPECT_FILL_PARENT: 133 | if (shouldBeWider) { 134 | // not high enough, fix height 135 | height = heightSpecSize; 136 | width = (int) (height * displayAspectRatio); 137 | } else { 138 | // not wide enough, fix width 139 | width = widthSpecSize; 140 | height = (int) (width / displayAspectRatio); 141 | } 142 | break; 143 | case IRenderView.AR_ASPECT_WRAP_CONTENT: 144 | default: 145 | if (shouldBeWider) { 146 | // too wide, fix width 147 | width = Math.min(mVideoWidth, widthSpecSize); 148 | height = (int) (width / displayAspectRatio); 149 | } else { 150 | // too high, fix height 151 | height = Math.min(mVideoHeight, heightSpecSize); 152 | width = (int) (height * displayAspectRatio); 153 | } 154 | break; 155 | } 156 | } else if (widthSpecMode == View.MeasureSpec.EXACTLY && heightSpecMode == View.MeasureSpec.EXACTLY) { 157 | // the size is fixed 158 | width = widthSpecSize; 159 | height = heightSpecSize; 160 | 161 | // for compatibility, we adjust size based on aspect ratio 162 | if (mVideoWidth * height < width * mVideoHeight) { 163 | //Log.i("@@@", "image too wide, correcting"); 164 | width = height * mVideoWidth / mVideoHeight; 165 | } else if (mVideoWidth * height > width * mVideoHeight) { 166 | //Log.i("@@@", "image too tall, correcting"); 167 | height = width * mVideoHeight / mVideoWidth; 168 | } 169 | } else if (widthSpecMode == View.MeasureSpec.EXACTLY) { 170 | // only the width is fixed, adjust the height to match aspect ratio if possible 171 | width = widthSpecSize; 172 | height = width * mVideoHeight / mVideoWidth; 173 | if (heightSpecMode == View.MeasureSpec.AT_MOST && height > heightSpecSize) { 174 | // couldn't match aspect ratio within the constraints 175 | height = heightSpecSize; 176 | } 177 | } else if (heightSpecMode == View.MeasureSpec.EXACTLY) { 178 | // only the height is fixed, adjust the width to match aspect ratio if possible 179 | height = heightSpecSize; 180 | width = height * mVideoWidth / mVideoHeight; 181 | if (widthSpecMode == View.MeasureSpec.AT_MOST && width > widthSpecSize) { 182 | // couldn't match aspect ratio within the constraints 183 | width = widthSpecSize; 184 | } 185 | } else { 186 | // neither the width nor the height are fixed, try to use actual video size 187 | width = mVideoWidth; 188 | height = mVideoHeight; 189 | if (heightSpecMode == View.MeasureSpec.AT_MOST && height > heightSpecSize) { 190 | // too tall, decrease both width and height 191 | height = heightSpecSize; 192 | width = height * mVideoWidth / mVideoHeight; 193 | } 194 | if (widthSpecMode == View.MeasureSpec.AT_MOST && width > widthSpecSize) { 195 | // too wide, decrease both width and height 196 | width = widthSpecSize; 197 | height = width * mVideoHeight / mVideoWidth; 198 | } 199 | } 200 | } else { 201 | // no size yet, just adopt the given spec sizes 202 | } 203 | 204 | mMeasuredWidth = width; 205 | mMeasuredHeight = height; 206 | } 207 | 208 | public int getMeasuredWidth() { 209 | return mMeasuredWidth; 210 | } 211 | 212 | public int getMeasuredHeight() { 213 | return mMeasuredHeight; 214 | } 215 | 216 | public void setAspectRatio(int aspectRatio) { 217 | mCurrentAspectRatio = aspectRatio; 218 | } 219 | 220 | @NonNull 221 | public static String getAspectRatioText(Context context, int aspectRatio) { 222 | String text; 223 | switch (aspectRatio) { 224 | case IRenderView.AR_ASPECT_FIT_PARENT: 225 | text = context.getString(R.string.VideoView_ar_aspect_fit_parent); 226 | break; 227 | case IRenderView.AR_ASPECT_FILL_PARENT: 228 | text = context.getString(R.string.VideoView_ar_aspect_fill_parent); 229 | break; 230 | case IRenderView.AR_ASPECT_WRAP_CONTENT: 231 | text = context.getString(R.string.VideoView_ar_aspect_wrap_content); 232 | break; 233 | case IRenderView.AR_MATCH_PARENT: 234 | text = context.getString(R.string.VideoView_ar_match_parent); 235 | break; 236 | case IRenderView.AR_16_9_FIT_PARENT: 237 | text = context.getString(R.string.VideoView_ar_16_9_fit_parent); 238 | break; 239 | case IRenderView.AR_4_3_FIT_PARENT: 240 | text = context.getString(R.string.VideoView_ar_4_3_fit_parent); 241 | break; 242 | default: 243 | text = context.getString(R.string.N_A); 244 | break; 245 | } 246 | return text; 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /app/src/main/java/cn/demo/videolist/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 cn.demo.videolist.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 | -------------------------------------------------------------------------------- /app/src/main/java/cn/demo/videolist/media/MediaPlayerService.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 cn.demo.videolist.media; 18 | 19 | import android.app.Service; 20 | import android.content.Context; 21 | import android.content.Intent; 22 | import android.os.IBinder; 23 | import android.support.annotation.Nullable; 24 | 25 | import tv.danmaku.ijk.media.player.IMediaPlayer; 26 | 27 | public class MediaPlayerService extends Service { 28 | private static IMediaPlayer sMediaPlayer; 29 | 30 | public static Intent newIntent(Context context) { 31 | Intent intent = new Intent(context, MediaPlayerService.class); 32 | return intent; 33 | } 34 | 35 | public static void intentToStart(Context context) { 36 | context.startService(newIntent(context)); 37 | } 38 | 39 | public static void intentToStop(Context context) { 40 | context.stopService(newIntent(context)); 41 | } 42 | 43 | @Nullable 44 | @Override 45 | public IBinder onBind(Intent intent) { 46 | return null; 47 | } 48 | 49 | public static void setMediaPlayer(IMediaPlayer mp) { 50 | if (sMediaPlayer != null && sMediaPlayer != mp) { 51 | if (sMediaPlayer.isPlaying()) 52 | sMediaPlayer.stop(); 53 | sMediaPlayer.release(); 54 | sMediaPlayer = null; 55 | } 56 | sMediaPlayer = mp; 57 | } 58 | 59 | public static IMediaPlayer getMediaPlayer() { 60 | return sMediaPlayer; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/java/cn/demo/videolist/media/Settings.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 cn.demo.videolist.media; 18 | 19 | import android.content.Context; 20 | import android.content.SharedPreferences; 21 | import android.preference.PreferenceManager; 22 | 23 | import cn.demo.videolist.R; 24 | 25 | 26 | public class Settings { 27 | private Context mAppContext; 28 | private SharedPreferences mSharedPreferences; 29 | 30 | public static final int PV_PLAYER__Auto = 0; 31 | public static final int PV_PLAYER__AndroidMediaPlayer = 1; 32 | public static final int PV_PLAYER__IjkMediaPlayer = 2; 33 | public static final int PV_PLAYER__IjkExoMediaPlayer = 3; 34 | 35 | public Settings(Context context) { 36 | mAppContext = context.getApplicationContext(); 37 | mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(mAppContext); 38 | } 39 | 40 | public boolean getEnableBackgroundPlay() { 41 | String key = mAppContext.getString(R.string.pref_key_enable_background_play); 42 | return mSharedPreferences.getBoolean(key, false); 43 | } 44 | 45 | public int getPlayer() { 46 | String key = mAppContext.getString(R.string.pref_key_player); 47 | String value = mSharedPreferences.getString(key, ""); 48 | try { 49 | return Integer.valueOf(value).intValue(); 50 | } catch (NumberFormatException e) { 51 | return 0; 52 | } 53 | } 54 | 55 | public boolean getUsingMediaCodec() { 56 | String key = mAppContext.getString(R.string.pref_key_using_media_codec); 57 | return mSharedPreferences.getBoolean(key, false); 58 | } 59 | 60 | public boolean getUsingMediaCodecAutoRotate() { 61 | String key = mAppContext.getString(R.string.pref_key_using_media_codec_auto_rotate); 62 | return mSharedPreferences.getBoolean(key, false); 63 | } 64 | 65 | public boolean getUsingOpenSLES() { 66 | String key = mAppContext.getString(R.string.pref_key_using_opensl_es); 67 | return mSharedPreferences.getBoolean(key, false); 68 | } 69 | 70 | public String getPixelFormat() { 71 | String key = mAppContext.getString(R.string.pref_key_pixel_format); 72 | return mSharedPreferences.getString(key, ""); 73 | } 74 | 75 | public boolean getEnableNoView() { 76 | String key = mAppContext.getString(R.string.pref_key_enable_no_view); 77 | return mSharedPreferences.getBoolean(key, false); 78 | } 79 | 80 | public boolean getEnableSurfaceView() { 81 | String key = mAppContext.getString(R.string.pref_key_enable_surface_view); 82 | return mSharedPreferences.getBoolean(key, false); 83 | } 84 | 85 | public boolean getEnableTextureView() { 86 | String key = mAppContext.getString(R.string.pref_key_enable_texture_view); 87 | return mSharedPreferences.getBoolean(key, false); 88 | } 89 | 90 | public boolean getEnableDetachedSurfaceTextureView() { 91 | String key = mAppContext.getString(R.string.pref_key_enable_detached_surface_texture); 92 | return mSharedPreferences.getBoolean(key, false); 93 | } 94 | 95 | public String getLastDirectory() { 96 | String key = mAppContext.getString(R.string.pref_key_last_directory); 97 | return mSharedPreferences.getString(key, "/"); 98 | } 99 | 100 | public void setLastDirectory(String path) { 101 | String key = mAppContext.getString(R.string.pref_key_last_directory); 102 | mSharedPreferences.edit().putString(key, path).apply(); 103 | } 104 | 105 | public void setEnableTextureView(boolean enable){ 106 | String key = mAppContext.getString(R.string.pref_key_enable_texture_view); 107 | SharedPreferences.Editor editor=mSharedPreferences.edit(); 108 | editor.putBoolean(key,enable); 109 | editor.commit(); 110 | } 111 | 112 | public void setEnableSurfaceView(boolean enable){ 113 | String key = mAppContext.getString(R.string.pref_key_enable_surface_view); 114 | SharedPreferences.Editor editor=mSharedPreferences.edit(); 115 | editor.putBoolean(key,enable); 116 | editor.commit(); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /app/src/main/java/cn/demo/videolist/media/SurfaceRenderView.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 cn.demo.videolist.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.SurfaceView; 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 | 41 | public class SurfaceRenderView extends SurfaceView implements IRenderView { 42 | private MeasureHelper mMeasureHelper; 43 | 44 | public SurfaceRenderView(Context context) { 45 | super(context); 46 | initView(context); 47 | } 48 | 49 | public SurfaceRenderView(Context context, AttributeSet attrs) { 50 | super(context, attrs); 51 | initView(context); 52 | } 53 | 54 | public SurfaceRenderView(Context context, AttributeSet attrs, int defStyleAttr) { 55 | super(context, attrs, defStyleAttr); 56 | initView(context); 57 | } 58 | 59 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 60 | public SurfaceRenderView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 61 | super(context, attrs, defStyleAttr, defStyleRes); 62 | initView(context); 63 | } 64 | 65 | private void initView(Context context) { 66 | mMeasureHelper = new MeasureHelper(this); 67 | mSurfaceCallback = new SurfaceCallback(this); 68 | getHolder().addCallback(mSurfaceCallback); 69 | //noinspection deprecation 70 | getHolder().setType(SurfaceHolder.SURFACE_TYPE_NORMAL); 71 | } 72 | 73 | @Override 74 | public View getView() { 75 | return this; 76 | } 77 | 78 | @Override 79 | public boolean shouldWaitForResize() { 80 | return true; 81 | } 82 | 83 | //-------------------- 84 | // Layout & Measure 85 | //-------------------- 86 | @Override 87 | public void setVideoSize(int videoWidth, int videoHeight) { 88 | if (videoWidth > 0 && videoHeight > 0) { 89 | mMeasureHelper.setVideoSize(videoWidth, videoHeight); 90 | getHolder().setFixedSize(videoWidth, videoHeight); 91 | requestLayout(); 92 | } 93 | } 94 | 95 | @Override 96 | public void setVideoSampleAspectRatio(int videoSarNum, int videoSarDen) { 97 | if (videoSarNum > 0 && videoSarDen > 0) { 98 | mMeasureHelper.setVideoSampleAspectRatio(videoSarNum, videoSarDen); 99 | requestLayout(); 100 | } 101 | } 102 | 103 | @Override 104 | public void setVideoRotation(int degree) { 105 | Log.e("", "SurfaceView doesn't support rotation (" + degree + ")!\n"); 106 | } 107 | 108 | @Override 109 | public void setAspectRatio(int aspectRatio) { 110 | mMeasureHelper.setAspectRatio(aspectRatio); 111 | requestLayout(); 112 | } 113 | 114 | @Override 115 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 116 | mMeasureHelper.doMeasure(widthMeasureSpec, heightMeasureSpec); 117 | setMeasuredDimension(mMeasureHelper.getMeasuredWidth(), mMeasureHelper.getMeasuredHeight()); 118 | } 119 | 120 | //-------------------- 121 | // SurfaceViewHolder 122 | //-------------------- 123 | 124 | private static final class InternalSurfaceHolder implements IRenderView.ISurfaceHolder { 125 | private SurfaceRenderView mSurfaceView; 126 | private SurfaceHolder mSurfaceHolder; 127 | 128 | public InternalSurfaceHolder(@NonNull SurfaceRenderView surfaceView, 129 | @Nullable SurfaceHolder surfaceHolder) { 130 | mSurfaceView = surfaceView; 131 | mSurfaceHolder = surfaceHolder; 132 | } 133 | 134 | public void bindToMediaPlayer(IMediaPlayer mp) { 135 | if (mp != null) { 136 | if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) && 137 | (mp instanceof ISurfaceTextureHolder)) { 138 | ISurfaceTextureHolder textureHolder = (ISurfaceTextureHolder) mp; 139 | textureHolder.setSurfaceTexture(null); 140 | } 141 | mp.setDisplay(mSurfaceHolder); 142 | } 143 | } 144 | 145 | @NonNull 146 | @Override 147 | public IRenderView getRenderView() { 148 | return mSurfaceView; 149 | } 150 | 151 | @Nullable 152 | @Override 153 | public SurfaceHolder getSurfaceHolder() { 154 | return mSurfaceHolder; 155 | } 156 | 157 | @Nullable 158 | @Override 159 | public SurfaceTexture getSurfaceTexture() { 160 | return null; 161 | } 162 | 163 | @Nullable 164 | @Override 165 | public Surface openSurface() { 166 | if (mSurfaceHolder == null) 167 | return null; 168 | return mSurfaceHolder.getSurface(); 169 | } 170 | } 171 | 172 | //------------------------- 173 | // SurfaceHolder.Callback 174 | //------------------------- 175 | 176 | @Override 177 | public void addRenderCallback(IRenderCallback callback) { 178 | mSurfaceCallback.addRenderCallback(callback); 179 | } 180 | 181 | @Override 182 | public void removeRenderCallback(IRenderCallback callback) { 183 | mSurfaceCallback.removeRenderCallback(callback); 184 | } 185 | 186 | private SurfaceCallback mSurfaceCallback; 187 | 188 | private static final class SurfaceCallback implements SurfaceHolder.Callback { 189 | private SurfaceHolder mSurfaceHolder; 190 | private boolean mIsFormatChanged; 191 | private int mFormat; 192 | private int mWidth; 193 | private int mHeight; 194 | 195 | private WeakReference mWeakSurfaceView; 196 | private Map mRenderCallbackMap = new ConcurrentHashMap(); 197 | 198 | public SurfaceCallback(@NonNull SurfaceRenderView surfaceView) { 199 | mWeakSurfaceView = new WeakReference(surfaceView); 200 | } 201 | 202 | public void addRenderCallback(@NonNull IRenderCallback callback) { 203 | mRenderCallbackMap.put(callback, callback); 204 | 205 | ISurfaceHolder surfaceHolder = null; 206 | if (mSurfaceHolder != null) { 207 | if (surfaceHolder == null) 208 | surfaceHolder = new InternalSurfaceHolder(mWeakSurfaceView.get(), mSurfaceHolder); 209 | callback.onSurfaceCreated(surfaceHolder, mWidth, mHeight); 210 | } 211 | 212 | if (mIsFormatChanged) { 213 | if (surfaceHolder == null) 214 | surfaceHolder = new InternalSurfaceHolder(mWeakSurfaceView.get(), mSurfaceHolder); 215 | callback.onSurfaceChanged(surfaceHolder, mFormat, mWidth, mHeight); 216 | } 217 | } 218 | 219 | public void removeRenderCallback(@NonNull IRenderCallback callback) { 220 | mRenderCallbackMap.remove(callback); 221 | } 222 | 223 | @Override 224 | public void surfaceCreated(SurfaceHolder holder) { 225 | mSurfaceHolder = holder; 226 | mIsFormatChanged = false; 227 | mFormat = 0; 228 | mWidth = 0; 229 | mHeight = 0; 230 | 231 | ISurfaceHolder surfaceHolder = new InternalSurfaceHolder(mWeakSurfaceView.get(), mSurfaceHolder); 232 | for (IRenderCallback renderCallback : mRenderCallbackMap.keySet()) { 233 | renderCallback.onSurfaceCreated(surfaceHolder, 0, 0); 234 | } 235 | } 236 | 237 | @Override 238 | public void surfaceDestroyed(SurfaceHolder holder) { 239 | mSurfaceHolder = null; 240 | mIsFormatChanged = false; 241 | mFormat = 0; 242 | mWidth = 0; 243 | mHeight = 0; 244 | 245 | ISurfaceHolder surfaceHolder = new InternalSurfaceHolder(mWeakSurfaceView.get(), mSurfaceHolder); 246 | for (IRenderCallback renderCallback : mRenderCallbackMap.keySet()) { 247 | renderCallback.onSurfaceDestroyed(surfaceHolder); 248 | } 249 | } 250 | 251 | @Override 252 | public void surfaceChanged(SurfaceHolder holder, int format, 253 | int width, int height) { 254 | mSurfaceHolder = holder; 255 | mIsFormatChanged = true; 256 | mFormat = format; 257 | mWidth = width; 258 | mHeight = height; 259 | 260 | // mMeasureHelper.setVideoSize(width, height); 261 | 262 | ISurfaceHolder surfaceHolder = new InternalSurfaceHolder(mWeakSurfaceView.get(), mSurfaceHolder); 263 | for (IRenderCallback renderCallback : mRenderCallbackMap.keySet()) { 264 | renderCallback.onSurfaceChanged(surfaceHolder, format, width, height); 265 | } 266 | } 267 | } 268 | 269 | //-------------------- 270 | // Accessibility 271 | //-------------------- 272 | 273 | @Override 274 | public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 275 | super.onInitializeAccessibilityEvent(event); 276 | event.setClassName(SurfaceRenderView.class.getName()); 277 | } 278 | 279 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) 280 | @Override 281 | public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 282 | super.onInitializeAccessibilityNodeInfo(info); 283 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { 284 | info.setClassName(SurfaceRenderView.class.getName()); 285 | } 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /app/src/main/java/cn/demo/videolist/media/TableLayoutBinder.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 cn.demo.videolist.media; 18 | 19 | import android.content.Context; 20 | import android.support.v7.app.AlertDialog; 21 | import android.view.LayoutInflater; 22 | import android.view.View; 23 | import android.view.ViewGroup; 24 | import android.widget.TableLayout; 25 | import android.widget.TextView; 26 | 27 | import cn.demo.videolist.R; 28 | 29 | 30 | public class TableLayoutBinder { 31 | private Context mContext; 32 | public ViewGroup mTableView; 33 | public TableLayout mTableLayout; 34 | 35 | public TableLayoutBinder(Context context) { 36 | this(context, R.layout.table_media_info); 37 | } 38 | 39 | public TableLayoutBinder(Context context, int layoutResourceId) { 40 | mContext = context; 41 | mTableView = (ViewGroup) LayoutInflater.from(mContext).inflate(layoutResourceId, null); 42 | mTableLayout = (TableLayout) mTableView.findViewById(R.id.table); 43 | } 44 | 45 | public TableLayoutBinder(Context context, TableLayout tableLayout) { 46 | mContext = context; 47 | mTableView = tableLayout; 48 | mTableLayout = tableLayout; 49 | } 50 | 51 | public View appendRow1(String name, String value) { 52 | return appendRow(R.layout.table_media_info_row1, name, value); 53 | } 54 | 55 | public View appendRow1(int nameId, String value) { 56 | return appendRow1(mContext.getString(nameId), value); 57 | } 58 | 59 | public View appendRow2(String name, String value) { 60 | return appendRow(R.layout.table_media_info_row2, name, value); 61 | } 62 | 63 | public View appendRow2(int nameId, String value) { 64 | return appendRow2(mContext.getString(nameId), value); 65 | } 66 | 67 | public View appendSection(String name) { 68 | return appendRow(R.layout.table_media_info_section, name, null); 69 | } 70 | 71 | public View appendSection(int nameId) { 72 | return appendSection(mContext.getString(nameId)); 73 | } 74 | 75 | public View appendRow(int layoutId, String name, String value) { 76 | ViewGroup rowView = (ViewGroup) LayoutInflater.from(mContext).inflate(layoutId, mTableLayout, false); 77 | setNameValueText(rowView, name, value); 78 | 79 | mTableLayout.addView(rowView); 80 | return rowView; 81 | } 82 | 83 | public ViewHolder obtainViewHolder(View rowView) { 84 | ViewHolder viewHolder = (ViewHolder) rowView.getTag(); 85 | if (viewHolder == null) { 86 | viewHolder = new ViewHolder(); 87 | viewHolder.mNameTextView = (TextView) rowView.findViewById(R.id.name); 88 | viewHolder.mValueTextView = (TextView) rowView.findViewById(R.id.value); 89 | rowView.setTag(viewHolder); 90 | } 91 | return viewHolder; 92 | } 93 | 94 | public void setNameValueText(View rowView, String name, String value) { 95 | ViewHolder viewHolder = obtainViewHolder(rowView); 96 | viewHolder.setName(name); 97 | viewHolder.setValue(value); 98 | } 99 | 100 | public void setValueText(View rowView, String value) { 101 | ViewHolder viewHolder = obtainViewHolder(rowView); 102 | viewHolder.setValue(value); 103 | } 104 | 105 | public ViewGroup buildLayout() { 106 | return mTableView; 107 | } 108 | 109 | public AlertDialog.Builder buildAlertDialogBuilder() { 110 | AlertDialog.Builder dlgBuilder = new AlertDialog.Builder(mContext); 111 | dlgBuilder.setView(buildLayout()); 112 | return dlgBuilder; 113 | } 114 | 115 | private static class ViewHolder { 116 | public TextView mNameTextView; 117 | public TextView mValueTextView; 118 | 119 | public void setName(String name) { 120 | if (mNameTextView != null) { 121 | mNameTextView.setText(name); 122 | } 123 | } 124 | 125 | public void setValue(String value) { 126 | if (mValueTextView != null) { 127 | mValueTextView.setText(value); 128 | } 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /app/src/main/java/cn/demo/videolist/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 cn.demo.videolist.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 | -------------------------------------------------------------------------------- /app/src/main/java/cn/demo/videolist/media/VideoAdapter.java: -------------------------------------------------------------------------------- 1 | package cn.demo.videolist.media; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.FrameLayout; 9 | import android.widget.RelativeLayout; 10 | import android.widget.TextView; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | import cn.demo.videolist.R; 16 | import cn.demo.videolist.VideoItemData; 17 | 18 | /** 19 | * Author wangchenchen 20 | * Description video列表adapter 21 | */ 22 | public class VideoAdapter extends RecyclerView.Adapter { 23 | 24 | private List list; 25 | public VideoAdapter(Context context){ 26 | list=new ArrayList<>(); 27 | } 28 | 29 | @Override 30 | public VideoAdapter.VideoViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 31 | 32 | View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_video, parent, false); 33 | VideoAdapter.VideoViewHolder holder = new VideoAdapter.VideoViewHolder(view); 34 | view.setTag(holder); 35 | return new VideoAdapter.VideoViewHolder(view); 36 | } 37 | 38 | @Override 39 | public void onBindViewHolder(VideoAdapter.VideoViewHolder holder, int position) { 40 | holder.update(position); 41 | } 42 | 43 | @Override 44 | public int getItemCount() { 45 | return list.size(); 46 | } 47 | 48 | public void refresh(List list){ 49 | this.list.clear(); 50 | this.list.addAll(list); 51 | notifyDataSetChanged(); 52 | } 53 | 54 | public class VideoViewHolder extends RecyclerView.ViewHolder { 55 | private FrameLayout videoLayout; 56 | private int position; 57 | private RelativeLayout showView; 58 | private TextView title,from; 59 | 60 | 61 | public VideoViewHolder(View itemView) { 62 | super(itemView); 63 | videoLayout = (FrameLayout) itemView.findViewById(R.id.layout_video); 64 | showView= (RelativeLayout) itemView.findViewById(R.id.showview); 65 | title= (TextView) itemView.findViewById(R.id.title); 66 | from= (TextView) itemView.findViewById(R.id.from); 67 | } 68 | 69 | public void update(final int position) { 70 | this.position = position; 71 | title.setText(list.get(position).getTitle()); 72 | title.setText(list.get(position).getVideosource()); 73 | showView.setOnClickListener(new View.OnClickListener() { 74 | @Override 75 | public void onClick(View v) { 76 | showView.setVisibility(View.GONE); 77 | if (click != null) 78 | click.onclick(position); 79 | } 80 | }); 81 | } 82 | } 83 | 84 | private onClick click; 85 | 86 | public void setClick(onClick click){ 87 | this.click=click; 88 | } 89 | 90 | public static interface onClick{ 91 | void onclick(int position); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /app/src/main/jniLibs/arm64-v8a/libijkffmpeg.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w1123440793/VideoListDemo/e0b124d398016d81b4105e0d05b5df14a179bcfb/app/src/main/jniLibs/arm64-v8a/libijkffmpeg.so -------------------------------------------------------------------------------- /app/src/main/jniLibs/arm64-v8a/libijkplayer.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w1123440793/VideoListDemo/e0b124d398016d81b4105e0d05b5df14a179bcfb/app/src/main/jniLibs/arm64-v8a/libijkplayer.so -------------------------------------------------------------------------------- /app/src/main/jniLibs/arm64-v8a/libijksdl.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w1123440793/VideoListDemo/e0b124d398016d81b4105e0d05b5df14a179bcfb/app/src/main/jniLibs/arm64-v8a/libijksdl.so -------------------------------------------------------------------------------- /app/src/main/jniLibs/x86_64/libijkffmpeg.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w1123440793/VideoListDemo/e0b124d398016d81b4105e0d05b5df14a179bcfb/app/src/main/jniLibs/x86_64/libijkffmpeg.so -------------------------------------------------------------------------------- /app/src/main/jniLibs/x86_64/libijkplayer.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w1123440793/VideoListDemo/e0b124d398016d81b4105e0d05b5df14a179bcfb/app/src/main/jniLibs/x86_64/libijkplayer.so -------------------------------------------------------------------------------- /app/src/main/jniLibs/x86_64/libijksdl.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w1123440793/VideoListDemo/e0b124d398016d81b4105e0d05b5df14a179bcfb/app/src/main/jniLibs/x86_64/libijksdl.so -------------------------------------------------------------------------------- /app/src/main/res/drawable/app_video_center_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_seek.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_thumb.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_video.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 17 | 18 | 23 | 24 | 28 | 29 | 30 | 31 | 35 | 36 | 41 | 42 | 48 | 49 | 50 | 51 | 52 | 57 | 58 | 67 | 68 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_video_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 16 | 17 | 24 | 25 | 29 | 30 | 37 | 38 | 39 | 40 | 45 | 46 | -------------------------------------------------------------------------------- /app/src/main/res/layout/media_contoller.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 15 | 21 | 22 | 32 | 33 | 39 | 40 | 48 | 49 | 61 | 62 | 69 | 70 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /app/src/main/res/layout/table_media_info.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/table_media_info_row1.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/table_media_info_row2.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/layout/table_media_info_section.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/view_video_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 23 | 24 | 29 | 30 | 36 | 37 | 46 | 47 | 52 | 53 | 63 | 64 | 65 | 74 | 75 | 80 | 81 | 91 | 92 | 93 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w1123440793/VideoListDemo/e0b124d398016d81b4105e0d05b5df14a179bcfb/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w1123440793/VideoListDemo/e0b124d398016d81b4105e0d05b5df14a179bcfb/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/full_screen_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w1123440793/VideoListDemo/e0b124d398016d81b4105e0d05b5df14a179bcfb/app/src/main/res/mipmap-xhdpi/full_screen_icon.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w1123440793/VideoListDemo/e0b124d398016d81b4105e0d05b5df14a179bcfb/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/sound_mult_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w1123440793/VideoListDemo/e0b124d398016d81b4105e0d05b5df14a179bcfb/app/src/main/res/mipmap-xhdpi/sound_mult_icon.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/sound_open_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w1123440793/VideoListDemo/e0b124d398016d81b4105e0d05b5df14a179bcfb/app/src/main/res/mipmap-xhdpi/sound_open_icon.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/video_play_btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w1123440793/VideoListDemo/e0b124d398016d81b4105e0d05b5df14a179bcfb/app/src/main/res/mipmap-xhdpi/video_play_btn.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/video_stop_btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w1123440793/VideoListDemo/e0b124d398016d81b4105e0d05b5df14a179bcfb/app/src/main/res/mipmap-xhdpi/video_stop_btn.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w1123440793/VideoListDemo/e0b124d398016d81b4105e0d05b5df14a179bcfb/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w1123440793/VideoListDemo/e0b124d398016d81b4105e0d05b5df14a179bcfb/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/raw/video_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "list":[{ 3 | "topicImg": "http://vimg1.ws.126.net/image/snapshot/2016/4/7/G/VBILRDA7G.jpg", 4 | "replyCount": 0, 5 | "videosource": "新媒体", 6 | "mp4Hd_url": null, 7 | "topicDesc": "此诚哥非彼诚哥", 8 | "topicSid": "VBILRDA7E", 9 | "cover": "http://vimg1.ws.126.net/image/snapshot/2016/4/3/7/VBKQP3437.jpg", 10 | "title": "我看过魔术之中最牛逼的一个", 11 | "playCount": 12336, 12 | "replyBoard": "video_bbs", 13 | "videoTopic": { 14 | "alias": "此诚哥非彼诚哥", 15 | "tname": "诚哥讲笑话", 16 | "ename": "T1460515708449", 17 | "tid": "T1460515708449" 18 | }, 19 | "sectiontitle": "", 20 | "description": "我的天,他是怎么做到的。。。", 21 | "replyid": "BKQP3436008535RB", 22 | "mp4_url": "http://flv2.bn.netease.com/videolib3/1604/28/fVobI0704/SD/fVobI0704-mobile.mp4", 23 | "length": 65, 24 | "playersize": 1, 25 | "m3u8Hd_url": null, 26 | "vid": "VBKQP3436", 27 | "m3u8_url": "http://flv2.bn.netease.com/videolib3/1604/28/fVobI0704/SD/movie_index.m3u8", 28 | "ptime": "2016-04-28 16:04:50", 29 | "topicName": "诚哥讲笑话" 30 | }, { 31 | "topicImg": "http://vimg2.ws.126.net/image/snapshot/2016/2/I/M/VBG7HE1IM.jpg", 32 | "replyCount": 0, 33 | "videosource": "新媒体", 34 | "mp4Hd_url": null, 35 | "topicDesc": "不开心?来这寻找最快乐的自己", 36 | "topicSid": "VBG2BBJ7H", 37 | "cover": "http://vimg3.ws.126.net/image/snapshot/2016/4/U/0/VBKQOPUU0.jpg", 38 | "title": "逗比美女:养我也是为了吃肉吗", 39 | "playCount": 9448, 40 | "replyBoard": "video_bbs", 41 | "videoTopic": { 42 | "alias": "不开心?来这寻找最快乐的自己", 43 | "tname": "逗比对嘴表演", 44 | "ename": "T1460515712745", 45 | "tid": "T1460515712745" 46 | }, 47 | "sectiontitle": "", 48 | "description": "小咖秀合作", 49 | "replyid": "BKQOAO51008535RB", 50 | "mp4_url": "http://flv2.bn.netease.com/tvmrepo/2016/4/G/O/EBKQOA8GO/SD/EBKQOA8GO-mobile.mp4", 51 | "length": 17, 52 | "playersize": 0, 53 | "m3u8Hd_url": null, 54 | "vid": "VBKQOAO51", 55 | "m3u8_url": "http://flv2.bn.netease.com/tvmrepo/2016/4/G/O/EBKQOA8GO/SD/movie_index.m3u8", 56 | "ptime": "2016-04-28 15:51:15", 57 | "topicName": "逗比对嘴表演" 58 | }, { 59 | "topicImg": "http://vimg2.ws.126.net/image/snapshot/2016/3/5/S/VBHT6285S.jpg", 60 | "replyCount": 0, 61 | "videosource": "新媒体", 62 | "mp4Hd_url": null, 63 | "topicDesc": "让你每天笑不停。", 64 | "topicSid": "VBHT6285Q", 65 | "cover": "http://vimg3.ws.126.net/image/snapshot/2016/4/0/R/VBKQOOU0R.jpg", 66 | "title": "女蛇精病唱民歌:把这段话念一遍", 67 | "playCount": 9600, 68 | "replyBoard": "video_bbs", 69 | "videoTopic": { 70 | "alias": "让你每天笑不停。", 71 | "tname": "十秒你就笑", 72 | "ename": "T1460515715472", 73 | "tid": "T1460515715472" 74 | }, 75 | "sectiontitle": "", 76 | "description": "小咖秀合作", 77 | "replyid": "BKQO9M84008535RB", 78 | "mp4_url": "http://flv2.bn.netease.com/tvmrepo/2016/4/M/Q/EBKQO95MQ/SD/EBKQO95MQ-mobile.mp4", 79 | "length": 32, 80 | "playersize": 0, 81 | "m3u8Hd_url": null, 82 | "vid": "VBKQO9M84", 83 | "m3u8_url": "http://flv2.bn.netease.com/tvmrepo/2016/4/M/Q/EBKQO95MQ/SD/movie_index.m3u8", 84 | "ptime": "2016-04-28 15:50:40", 85 | "topicName": "十秒你就笑" 86 | }, { 87 | "topicImg": "http://vimg1.ws.126.net/image/snapshot/2016/4/C/0/VBJ4774C0.jpg", 88 | "replyCount": 0, 89 | "videosource": "新媒体", 90 | "mp4Hd_url": null, 91 | "topicDesc": "这里有世界各地的奇葩事", 92 | "topicSid": "VBJ4774BU", 93 | "cover": "http://vimg1.ws.126.net/image/snapshot/2016/4/C/L/VBKQOOPCL.jpg", 94 | "title": "呆萌妹子实力出演:可爱皮卡丘", 95 | "playCount": 9117, 96 | "replyBoard": "video_bbs", 97 | "videoTopic": { 98 | "alias": "这里有世界各地的奇葩事", 99 | "tname": "阿拉伯飞毯", 100 | "ename": "T1460515711614", 101 | "tid": "T1460515711614" 102 | }, 103 | "sectiontitle": "", 104 | "description": "小咖秀合作", 105 | "replyid": "BKQO86Q5008535RB", 106 | "mp4_url": "http://flv2.bn.netease.com/tvmrepo/2016/4/G/S/EBKQO7TGS/SD/EBKQO7TGS-mobile.mp4", 107 | "length": 9, 108 | "playersize": 0, 109 | "m3u8Hd_url": null, 110 | "vid": "VBKQO86Q5", 111 | "m3u8_url": "http://flv2.bn.netease.com/tvmrepo/2016/4/G/S/EBKQO7TGS/SD/movie_index.m3u8", 112 | "ptime": "2016-04-28 15:49:59", 113 | "topicName": "阿拉伯飞毯" 114 | }, { 115 | "topicImg": "http://vimg3.ws.126.net/image/snapshot/2016/3/C/N/VBG7AA2CN.jpg", 116 | "replyCount": 0, 117 | "videosource": "新媒体", 118 | "mp4Hd_url": null, 119 | "topicDesc": "你是猴子请来的逗比么?", 120 | "topicSid": "VBG7AA2CK", 121 | "cover": "http://vimg1.ws.126.net/image/snapshot/2016/4/7/E/VBKQOA87E.jpg", 122 | "title": "史上最诱惑的广告,基本广告词说完大家也不知道在说什么", 123 | "playCount": 9448, 124 | "replyBoard": "video_bbs", 125 | "videoTopic": { 126 | "alias": "你是猴子请来的逗比么?", 127 | "tname": "有一种美女叫逗比", 128 | "ename": "T1460515706915", 129 | "tid": "T1460515706915" 130 | }, 131 | "sectiontitle": "", 132 | "description": "小咖秀合作", 133 | "replyid": "BKQO3HRO008535RB", 134 | "mp4_url": "http://flv2.bn.netease.com/tvmrepo/2016/4/0/K/EBKQO3C0K-mobile.mp4", 135 | "length": 17, 136 | "playersize": 0, 137 | "m3u8Hd_url": null, 138 | "vid": "VBKQO3HRO", 139 | "m3u8_url": "http://flv2.bn.netease.com/tvmrepo/2016/4/0/K/EBKQO3C0K-mobile.mp4", 140 | "ptime": "2016-04-28 15:47:30", 141 | "topicName": "有一种美女叫逗比" 142 | }, { 143 | "topicImg": "http://vimg2.ws.126.net/image/snapshot/2016/2/U/L/VBG7H0SUL.jpg", 144 | "replyCount": 0, 145 | "videosource": "新媒体", 146 | "mp4Hd_url": null, 147 | "topicDesc": "不说不笑不热闹", 148 | "topicSid": "VBFGARMFR", 149 | "cover": "http://vimg2.ws.126.net/image/snapshot/2016/4/I/V/VBKQOCKIV.jpg", 150 | "title": "男子逗比搞笑自语:下雨了出去洗个头", 151 | "playCount": 19721, 152 | "replyBoard": "video_bbs", 153 | "videoTopic": { 154 | "alias": "不说不笑不热闹", 155 | "tname": "搞笑一箩筐", 156 | "ename": "T1460515708679", 157 | "tid": "T1460515708679" 158 | }, 159 | "sectiontitle": "", 160 | "description": "小咖秀合作", 161 | "replyid": "BKQO37QV008535RB", 162 | "mp4_url": "http://flv2.bn.netease.com/tvmrepo/2016/4/I/G/EBKQO2SIG/SD/EBKQO2SIG-mobile.mp4", 163 | "length": 39, 164 | "playersize": 0, 165 | "m3u8Hd_url": null, 166 | "vid": "VBKQO37QV", 167 | "m3u8_url": "http://flv2.bn.netease.com/tvmrepo/2016/4/I/G/EBKQO2SIG/SD/movie_index.m3u8", 168 | "ptime": "2016-04-28 15:47:14", 169 | "topicName": "搞笑一箩筐" 170 | }, { 171 | "topicImg": "http://vimg1.ws.126.net/image/snapshot/2016/2/I/M/VBG7HE1IM.jpg", 172 | "replyCount": 0, 173 | "videosource": "新媒体", 174 | "mp4Hd_url": null, 175 | "topicDesc": "不开心?来这寻找最快乐的自己", 176 | "topicSid": "VBG2BBJ7H", 177 | "cover": "http://vimg3.ws.126.net/image/snapshot/2016/4/C/4/VBKQO4JC4.jpg", 178 | "title": "逗逼制服美女之旅客朋友们请注意啦", 179 | "playCount": 9768, 180 | "replyBoard": "video_bbs", 181 | "videoTopic": { 182 | "alias": "不开心?来这寻找最快乐的自己", 183 | "tname": "逗比对嘴表演", 184 | "ename": "T1460515712745", 185 | "tid": "T1460515712745" 186 | }, 187 | "sectiontitle": "", 188 | "description": "小咖秀合作", 189 | "replyid": "BKQO0LN8008535RB", 190 | "mp4_url": "http://flv2.bn.netease.com/tvmrepo/2016/4/5/G/EBKQO085G/SD/EBKQO085G-mobile.mp4", 191 | "length": 40, 192 | "playersize": 0, 193 | "m3u8Hd_url": null, 194 | "vid": "VBKQO0LN8", 195 | "m3u8_url": "http://flv2.bn.netease.com/tvmrepo/2016/4/5/G/EBKQO085G/SD/movie_index.m3u8", 196 | "ptime": "2016-04-28 15:45:47", 197 | "topicName": "逗比对嘴表演" 198 | }, { 199 | "topicImg": "http://vimg2.ws.126.net/image/snapshot/2016/2/B/5/VBG7H5IB5.jpg", 200 | "replyCount": 0, 201 | "videosource": "新媒体", 202 | "mp4Hd_url": null, 203 | "topicDesc": "探索世界,捕捉甜虾奇闻怪诞。", 204 | "topicSid": "VBFGGQAN8", 205 | "cover": "http://vimg3.ws.126.net/image/snapshot/2016/4/I/V/VBKQNCCIV.jpg", 206 | "title": "我家一条巷相隔六尺宽包容无线大", 207 | "playCount": 9847, 208 | "replyBoard": "video_bbs", 209 | "videoTopic": { 210 | "alias": "探索世界,捕捉甜虾奇闻怪诞。", 211 | "tname": "奇闻异事", 212 | "ename": "T1460515709001", 213 | "tid": "T1460515709001" 214 | }, 215 | "sectiontitle": "", 216 | "description": "六尺巷", 217 | "replyid": "BKQN40G0008535RB", 218 | "mp4_url": "http://flv2.bn.netease.com/tvmrepo/2016/4/U/4/EBKQN3GU4/SD/EBKQN3GU4-mobile.mp4", 219 | "length": 36, 220 | "playersize": 0, 221 | "m3u8Hd_url": null, 222 | "vid": "VBKQN40G0", 223 | "m3u8_url": "http://flv2.bn.netease.com/tvmrepo/2016/4/U/4/EBKQN3GU4/SD/movie_index.m3u8", 224 | "ptime": "2016-04-28 15:30:06", 225 | "topicName": "奇闻异事" 226 | }, { 227 | "topicImg": "http://vimg1.ws.126.net/image/snapshot/2016/3/B/0/VBI02ISB0.jpg", 228 | "replyCount": 0, 229 | "videosource": "新媒体", 230 | "mp4Hd_url": null, 231 | "topicDesc": "狗哥出品,必属精品。", 232 | "topicSid": "VBI02ISAT", 233 | "cover": "http://vimg3.ws.126.net/image/snapshot/2016/4/4/O/VBKQMQE4O.jpg", 234 | "title": "教你如何当一位完美的男友", 235 | "playCount": 18049, 236 | "replyBoard": "video_bbs", 237 | "videoTopic": { 238 | "alias": "狗哥出品,必属精品。", 239 | "tname": "东北李二狗", 240 | "ename": "T1460515706478", 241 | "tid": "T1460515706478" 242 | }, 243 | "sectiontitle": "", 244 | "description": "单身狗哭晕,这样的女友还不珍惜。", 245 | "replyid": "BKQMKH2P008535RB", 246 | "mp4_url": "http://flv2.bn.netease.com/videolib3/1604/28/Dgwkr8165/SD/Dgwkr8165-mobile.mp4", 247 | "length": 70, 248 | "playersize": 0, 249 | "m3u8Hd_url": null, 250 | "vid": "VBKQMKH2P", 251 | "m3u8_url": "http://flv2.bn.netease.com/videolib3/1604/28/Dgwkr8165/SD/movie_index.m3u8", 252 | "ptime": "2016-04-28 15:21:55", 253 | "topicName": "东北李二狗" 254 | }, { 255 | "topicImg": "http://vimg1.ws.126.net/image/snapshot/2016/3/C/6/VBI02GKC6.jpg", 256 | "replyCount": 0, 257 | "videosource": "新媒体", 258 | "mp4Hd_url": null, 259 | "topicDesc": "让你开心每一天。", 260 | "topicSid": "VBI02GKC3", 261 | "cover": "http://vimg1.ws.126.net/image/snapshot/2016/4/S/R/VBKQMQFSR.jpg", 262 | "title": "逗比美女的逗比故事:和老外打招呼", 263 | "playCount": 9511, 264 | "replyBoard": "video_bbs", 265 | "videoTopic": { 266 | "alias": "让你开心每一天。", 267 | "tname": "劲爆搞笑", 268 | "ename": "T1460515706582", 269 | "tid": "T1460515706582" 270 | }, 271 | "sectiontitle": "", 272 | "description": "小咖秀合作", 273 | "replyid": "BKQMD062008535RB", 274 | "mp4_url": "http://flv2.bn.netease.com/tvmrepo/2016/4/N/C/EBKQMCMNC/SD/EBKQMCMNC-mobile.mp4", 275 | "length": 20, 276 | "playersize": 0, 277 | "m3u8Hd_url": null, 278 | "vid": "VBKQMD062", 279 | "m3u8_url": "http://flv2.bn.netease.com/tvmrepo/2016/4/N/C/EBKQMCMNC/SD/movie_index.m3u8", 280 | "ptime": "2016-04-28 15:17:38", 281 | "topicName": "劲爆搞笑" 282 | }] 283 | 284 | } -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10dp 4 | 10dp 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | VideoListDemo 3 | 4 | 本地视频 5 | 在线视频 6 | 7 | 8 | loading... 9 | Click to load more 10 | All the data has been loaded 11 | It is empty. :( 12 | Load error, click to retry 13 | N/A 14 | Close 15 | Exit 16 | Sample 17 | Recent 18 | Settings 19 | Tracks 20 | Player 21 | Render 22 | Scale 23 | Info 24 | vdec 25 | fps 26 | v-cache 27 | a-cache 28 | 29 | Media Information 30 | Player 31 | Media 32 | Profile level 33 | Pixel format 34 | Resolution 35 | Length 36 | Stream #%d 37 | Type 38 | Language 39 | Codec 40 | Frame rate 41 | Bit rate 42 | Sample rate 43 | Channels 44 | * 45 | * 46 | 47 | Video 48 | Audio 49 | Subtitle 50 | Timed text 51 | Meta data 52 | Unknown 53 | 54 | Invalid progressive playback 55 | Unknown 56 | OK 57 | 58 | Aspect / Fit parent 59 | Aspect / Fill parent 60 | Aspect / Wrap content 61 | Free / Fill parent 62 | 16:9 / Fit parent 63 | 4:3 / Fit parent 64 | 65 | Render: None 66 | Render: SurfaceView 67 | Render: TextureView 68 | 69 | Player: None 70 | Player: AndroidMediaPlayer 71 | Player: IjkMediaPlayer 72 | Player: IjkExoMediaPlayer 73 | 74 | 75 | Please specify the address of the video playback 76 | 77 | Player encountered a small problem 78 | 79 | can not play this video 80 | 81 | player not support this device 82 | 83 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings_pref.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | General 5 | 6 | pref.enable_background_play 7 | Enable background play 8 | need Android 4.0+ 9 | 10 | pref.using_android_player 11 | Using system player 12 | 13 | 14 | pref.player 15 | Choose Player 16 | 17 | Auto Select 18 | AndroidMediaPlayer 19 | IjkMediaPlayer 20 | IjkExoMediaPlayer 21 | 22 | 23 | 0 24 | 1 25 | 2 26 | 3 27 | 28 | 29 | Auto Select 30 | AndroidMediaPlayer 31 | IjkMediaPlayer 32 | IjkExoMediaPlayer 33 | 34 | 35 | 36 | Video: ijkplayer 37 | 38 | pref.using_media_codec 39 | Using MediaCodec 40 | 41 | 42 | pref.using_media_codec_auto_rotate 43 | Using MediaCodec auto rotate 44 | 45 | 46 | pref.pixel_format 47 | Pixel Format 48 | 49 | Auto Select 50 | RGB 565 51 | RGB 888 52 | RGBX 8888 53 | YV12 54 | OpenGL ES2 55 | 56 | 57 | 58 | fcc-rv16 59 | fcc-rv24 60 | fcc-rv32 61 | fcc-yv12 62 | fcc-_es2 63 | 64 | 65 | Auto Select 66 | RGB 565 67 | RGB 888 68 | RGBX 8888 69 | YV12 70 | OpenGL ES2 71 | 72 | 73 | 74 | Audio: ijkplayer 75 | 76 | pref.using_opensl_es 77 | Using OpenSL ES 78 | 79 | 80 | 81 | RenderView 82 | 83 | pref.enable_no_view 84 | Enable NoView 85 | 86 | 87 | pref.enable_surface_view 88 | Enable SurfaceView 89 | 90 | 91 | pref.enable_texture_view 92 | Enable TextureView 93 | 94 | 95 | pref.enable_detached_surface_texture 96 | Enable detached SurfaceTexture 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/cn/demo/videolist/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package cn.demo.videolist; 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 | } -------------------------------------------------------------------------------- /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 | 25 | ext { 26 | compileSdkVersion = 23 27 | buildToolsVersion = "23.0.3" 28 | targetSdkVersion = 21 29 | } 30 | -------------------------------------------------------------------------------- /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/w1123440793/VideoListDemo/e0b124d398016d81b4105e0d05b5df14a179bcfb/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 | -------------------------------------------------------------------------------- /screenshots/vidiolist.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w1123440793/VideoListDemo/e0b124d398016d81b4105e0d05b5df14a179bcfb/screenshots/vidiolist.gif -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------