├── .gitignore ├── .idea ├── caches │ └── build_file_checksums.ser ├── codeStyles │ └── Project.xml ├── gradle.xml ├── misc.xml ├── modules.xml └── runConfigurations.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── ch │ │ └── doudemo │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── ch │ │ │ └── doudemo │ │ │ ├── activity │ │ │ ├── ListActivity.java │ │ │ ├── MainActivity.java │ │ │ ├── Page2Activity.java │ │ │ ├── PageActivity.java │ │ │ ├── PrepareActivity.java │ │ │ ├── Record2Activity.java │ │ │ └── RecordActivity.java │ │ │ ├── adapter │ │ │ └── VerticalViewPagerAdapter.java │ │ │ ├── base │ │ │ ├── BaseRecAdapter.java │ │ │ ├── BaseRecViewHolder.java │ │ │ └── MyApp.java │ │ │ ├── fragment │ │ │ ├── BaseFragment.java │ │ │ └── VideoFragment.java │ │ │ └── widget │ │ │ ├── MyFileNameGenerator.java │ │ │ ├── MyVideoPlayer.java │ │ │ ├── VerticalViewPager.java │ │ │ └── VerticalViewPager2.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_list.xml │ │ ├── activity_list2.xml │ │ ├── activity_main.xml │ │ ├── activity_page.xml │ │ ├── activity_page2.xml │ │ ├── activity_prepare.xml │ │ ├── activity_record.xml │ │ ├── activity_record2.xml │ │ ├── fm_video.xml │ │ ├── item_page2.xml │ │ ├── item_video.xml │ │ └── jz_layout_std.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ ├── stop.png │ │ └── video_play_parse.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── ch │ └── doudemo │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/18702953620/DouDemo/a639ebe305431f0a47c9b44dd9688e4ec8031fb5/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DouDemo 2 | [![Badge](https://img.shields.io/badge/link-996.icu-red.svg)](https://996.icu/#/zh_CN) 3 | ### 仿抖音部分功能 4 | * [【Android 进阶】仿抖音系列之翻页上下滑切换视频(一)](https://www.jianshu.com/p/2c71f699c5c4) 5 | 垂直滑动的viewpager 实现仿抖音视频切换 6 | * [【Android 进阶】仿抖音系列之列表播放视频(二)](https://www.jianshu.com/p/ee6b7c200c9c) 7 | 仿抖音列表播放视频 8 | * [【Android 进阶】仿抖音系列之列表播放视频(三)](https://www.jianshu.com/p/15a70f242c4d) 9 | 列表视频的优化,视频缓存的使用 10 | * [【Android 进阶】仿抖音系列之翻页上下滑切换视频(四)](https://www.jianshu.com/p/e0bd595d6321) 11 | PagerSnapHelper+RecyclerView 实现仿抖音视频切换 12 | * [【Android 进阶】仿抖音系列之视频预览和录制(五)](https://www.jianshu.com/p/dc63d77b6761) 13 | SurfaceView实现相机预览,MediaRecorder实现视频录制及保存 14 | 15 | 16 | ### 关于 17 | * 关于下载慢的问题,已去除腾讯播放器代码,项目降低到167kb. 18 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 27 5 | defaultConfig { 6 | applicationId "com.ch.doudemo" 7 | minSdkVersion 21 8 | targetSdkVersion 27 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | compileOptions { 20 | sourceCompatibility JavaVersion.VERSION_1_8 21 | targetCompatibility JavaVersion.VERSION_1_8 22 | } 23 | } 24 | 25 | dependencies { 26 | implementation fileTree(dir: 'libs', include: ['*.jar']) 27 | implementation 'com.android.support:appcompat-v7:27.1.1' 28 | implementation 'com.android.support:support-v4:27.1.1' 29 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 30 | testImplementation 'junit:junit:4.12' 31 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 32 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 33 | implementation 'com.jakewharton:butterknife:8.8.1' 34 | annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1' 35 | implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.47' 36 | //glide 37 | implementation 'com.github.bumptech.glide:glide:4.8.0' 38 | annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0' 39 | implementation 'com.android.support:recyclerview-v7:27.1.1' 40 | 41 | //播放器 42 | implementation 'cn.jzvd:jiaozivideoplayer:7.0.5' 43 | 44 | //视频缓存 45 | implementation 'com.danikula:videocache:2.7.1' 46 | 47 | //权限 48 | implementation 'pub.devrel:easypermissions:1.3.0' 49 | implementation 'com.scwang.smartrefresh:SmartRefreshLayout:1.1.0-alpha-5' 50 | } 51 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/ch/doudemo/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.ch.doudemo; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.ch.doudemo", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/ch/doudemo/activity/ListActivity.java: -------------------------------------------------------------------------------- 1 | package com.ch.doudemo.activity; 2 | 3 | import android.graphics.Rect; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.support.v7.widget.LinearLayoutManager; 7 | import android.support.v7.widget.RecyclerView; 8 | import android.view.View; 9 | import android.widget.TextView; 10 | 11 | import com.bumptech.glide.Glide; 12 | import com.ch.doudemo.R; 13 | import com.ch.doudemo.base.BaseRecAdapter; 14 | import com.ch.doudemo.base.BaseRecViewHolder; 15 | import com.ch.doudemo.widget.MyVideoPlayer; 16 | 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | 20 | import butterknife.BindView; 21 | import butterknife.ButterKnife; 22 | 23 | /** 24 | * 视频列表 25 | */ 26 | public class ListActivity extends AppCompatActivity { 27 | 28 | @BindView(R.id.rv_list) 29 | RecyclerView rvList; 30 | private ArrayList urlList; 31 | private ListVideoAdapter videoAdapter; 32 | private int firstVisibleItem; 33 | private int lastVisibleItem; 34 | 35 | @Override 36 | protected void onCreate(Bundle savedInstanceState) { 37 | super.onCreate(savedInstanceState); 38 | setContentView(R.layout.activity_list); 39 | ButterKnife.bind(this); 40 | 41 | urlList = new ArrayList<>(); 42 | urlList.add("http://chuangfen.oss-cn-hangzhou.aliyuncs.com/public/attachment/201805/100651/201805181532123423.mp4"); 43 | urlList.add("http://chuangfen.oss-cn-hangzhou.aliyuncs.com/public/attachment/201803/100651/201803151735198462.mp4"); 44 | urlList.add("http://chuangfen.oss-cn-hangzhou.aliyuncs.com/public/attachment/201803/100651/201803150923220770.mp4"); 45 | urlList.add("http://chuangfen.oss-cn-hangzhou.aliyuncs.com/public/attachment/201803/100651/201803150922255785.mp4"); 46 | urlList.add("http://chuangfen.oss-cn-hangzhou.aliyuncs.com/public/attachment/201803/100651/201803150920130302.mp4"); 47 | urlList.add("http://chuangfen.oss-cn-hangzhou.aliyuncs.com/public/attachment/201803/100651/201803141625005241.mp4"); 48 | urlList.add("http://chuangfen.oss-cn-hangzhou.aliyuncs.com/public/attachment/201803/100651/201803141624378522.mp4"); 49 | urlList.add("http://chuangfen.oss-cn-hangzhou.aliyuncs.com/public/attachment/201803/100651/201803131546119319.mp4"); 50 | 51 | videoAdapter = new ListVideoAdapter(urlList); 52 | rvList.setLayoutManager(new LinearLayoutManager(ListActivity.this)); 53 | rvList.setAdapter(videoAdapter); 54 | 55 | addListener(); 56 | 57 | } 58 | 59 | private void addListener() { 60 | 61 | 62 | rvList.addOnScrollListener(new RecyclerView.OnScrollListener() { 63 | @Override 64 | public void onScrolled(RecyclerView recyclerView, int dx, int dy) { 65 | LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); 66 | firstVisibleItem = layoutManager.findFirstVisibleItemPosition(); 67 | lastVisibleItem = layoutManager.findLastVisibleItemPosition(); 68 | 69 | } 70 | 71 | @Override 72 | public void onScrollStateChanged(RecyclerView recyclerView, int newState) { 73 | switch (newState) { 74 | case RecyclerView.SCROLL_STATE_IDLE://停止滚动 75 | /**在这里执行,视频的自动播放与停止*/ 76 | autoPlayVideo(recyclerView); 77 | break; 78 | case RecyclerView.SCROLL_STATE_DRAGGING://拖动 79 | autoPlayVideo(recyclerView); 80 | break; 81 | case RecyclerView.SCROLL_STATE_SETTLING://惯性滑动 82 | MyVideoPlayer.releaseAllVideos(); 83 | break; 84 | } 85 | 86 | } 87 | }); 88 | } 89 | 90 | /** 91 | * 自动播放 92 | */ 93 | private void autoPlayVideo(RecyclerView recyclerView) { 94 | 95 | if (firstVisibleItem == 0 && lastVisibleItem == 0 && recyclerView.getChildAt(0) != null) { 96 | 97 | MyVideoPlayer videoView = null; 98 | if (recyclerView != null && recyclerView.getChildAt(0) != null) { 99 | videoView = recyclerView.getChildAt(0).findViewById(R.id.mp_video); 100 | } 101 | if (videoView != null) { 102 | if (videoView.state == MyVideoPlayer.STATE_NORMAL || videoView.state == MyVideoPlayer.STATE_PAUSE) { 103 | videoView.startVideo(); 104 | } 105 | } 106 | } 107 | 108 | for (int i = 0; i <= lastVisibleItem; i++) { 109 | if (recyclerView == null || recyclerView.getChildAt(i) == null) { 110 | return; 111 | } 112 | 113 | 114 | MyVideoPlayer 115 | videoView = recyclerView.getChildAt(i).findViewById(R.id.mp_video); 116 | if (videoView != null) { 117 | 118 | Rect rect = new Rect(); 119 | //获取视图本身的可见坐标,把值传入到rect对象中 120 | videoView.getLocalVisibleRect(rect); 121 | //获取视频的高度 122 | int videoHeight = videoView.getHeight(); 123 | 124 | if (rect.top <= 100 && rect.bottom >= videoHeight) { 125 | if (videoView.state == MyVideoPlayer.STATE_NORMAL || videoView.state == MyVideoPlayer.STATE_PAUSE) { 126 | videoView.startVideo(); 127 | } 128 | return; 129 | } 130 | 131 | MyVideoPlayer.releaseAllVideos(); 132 | 133 | } else { 134 | MyVideoPlayer.releaseAllVideos(); 135 | } 136 | 137 | } 138 | 139 | } 140 | 141 | 142 | @Override 143 | public void onPause() { 144 | super.onPause(); 145 | MyVideoPlayer.releaseAllVideos(); 146 | } 147 | 148 | 149 | class ListVideoAdapter extends BaseRecAdapter { 150 | 151 | 152 | public ListVideoAdapter(List list) { 153 | super(list); 154 | } 155 | 156 | @Override 157 | public void onHolder(VideoViewHolder holder, String bean, int position) { 158 | holder.mp_video.setUp(bean, "第" + position + "个视频", MyVideoPlayer.STATE_NORMAL); 159 | if (position == 0) { 160 | holder.mp_video.startVideo(); 161 | } 162 | Glide.with(context).load(bean).into(holder.mp_video.thumbImageView); 163 | holder.tv_title.setText("第" + position + "个视频"); 164 | } 165 | 166 | @Override 167 | public VideoViewHolder onCreateHolder() { 168 | return new VideoViewHolder(getViewByRes(R.layout.item_video)); 169 | 170 | } 171 | 172 | 173 | } 174 | 175 | public class VideoViewHolder extends BaseRecViewHolder { 176 | public View rootView; 177 | public MyVideoPlayer mp_video; 178 | public TextView tv_title; 179 | 180 | public VideoViewHolder(View rootView) { 181 | super(rootView); 182 | this.rootView = rootView; 183 | this.mp_video = rootView.findViewById(R.id.mp_video); 184 | this.tv_title = rootView.findViewById(R.id.tv_title); 185 | } 186 | 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /app/src/main/java/com/ch/doudemo/activity/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.ch.doudemo.activity; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.view.View; 7 | import android.widget.Button; 8 | 9 | import com.ch.doudemo.R; 10 | 11 | import butterknife.BindView; 12 | import butterknife.ButterKnife; 13 | import butterknife.OnClick; 14 | 15 | public class MainActivity extends AppCompatActivity { 16 | 17 | 18 | @BindView(R.id.btn_page) 19 | Button btnPage; 20 | @BindView(R.id.btn_list) 21 | Button btnList; 22 | @BindView(R.id.btn_record) 23 | Button btnList2; 24 | @BindView(R.id.btn_record2) 25 | Button btnRecord2; 26 | @BindView(R.id.btn_page2) 27 | Button btnPage2; 28 | 29 | @Override 30 | protected void onCreate(Bundle savedInstanceState) { 31 | super.onCreate(savedInstanceState); 32 | setContentView(R.layout.activity_main); 33 | ButterKnife.bind(this); 34 | initView(); 35 | addListener(); 36 | } 37 | 38 | private void addListener() { 39 | 40 | } 41 | 42 | private void initView() { 43 | 44 | 45 | } 46 | 47 | 48 | @OnClick({R.id.btn_page, R.id.btn_list, R.id.btn_record, R.id.btn_record2, R.id.btn_page2}) 49 | public void onViewClicked(View view) { 50 | Intent intent = new Intent(); 51 | switch (view.getId()) { 52 | case R.id.btn_page: 53 | intent.setClass(MainActivity.this, PageActivity.class); 54 | break; 55 | case R.id.btn_list: 56 | intent.setClass(MainActivity.this, ListActivity.class); 57 | break; 58 | case R.id.btn_record: 59 | intent.setClass(MainActivity.this, RecordActivity.class); 60 | break; 61 | 62 | case R.id.btn_record2: 63 | intent.setClass(MainActivity.this, Record2Activity.class); 64 | break; 65 | 66 | case R.id.btn_page2: 67 | intent.setClass(MainActivity.this, Page2Activity.class); 68 | 69 | break; 70 | } 71 | 72 | startActivity(intent); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /app/src/main/java/com/ch/doudemo/activity/Page2Activity.java: -------------------------------------------------------------------------------- 1 | package com.ch.doudemo.activity; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.support.v7.widget.LinearLayoutManager; 6 | import android.support.v7.widget.PagerSnapHelper; 7 | import android.support.v7.widget.RecyclerView; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.TextView; 11 | 12 | import com.bumptech.glide.Glide; 13 | import com.ch.doudemo.R; 14 | import com.ch.doudemo.base.BaseRecAdapter; 15 | import com.ch.doudemo.base.BaseRecViewHolder; 16 | import com.ch.doudemo.widget.MyVideoPlayer; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | import butterknife.BindView; 22 | import butterknife.ButterKnife; 23 | 24 | /** 25 | * 翻页2 26 | */ 27 | public class Page2Activity extends AppCompatActivity { 28 | 29 | @BindView(R.id.rv_page2) 30 | RecyclerView rvPage2; 31 | private List urlList; 32 | private ListVideoAdapter videoAdapter; 33 | private PagerSnapHelper snapHelper; 34 | private LinearLayoutManager layoutManager; 35 | private int currentPosition; 36 | 37 | @Override 38 | protected void onCreate(Bundle savedInstanceState) { 39 | super.onCreate(savedInstanceState); 40 | setContentView(R.layout.activity_page2); 41 | ButterKnife.bind(this); 42 | initView(); 43 | addListener(); 44 | } 45 | 46 | 47 | private void initView() { 48 | urlList = new ArrayList<>(); 49 | urlList.add("http://chuangfen.oss-cn-hangzhou.aliyuncs.com/public/attachment/201811/26/09/5bfb4c55633c9.mp4"); 50 | urlList.add("http://chuangfen.oss-cn-hangzhou.aliyuncs.com/public/attachment/201805/100651/201805181532123423.mp4"); 51 | urlList.add("http://chuangfen.oss-cn-hangzhou.aliyuncs.com/public/attachment/201803/100651/201803151735198462.mp4"); 52 | urlList.add("http://chuangfen.oss-cn-hangzhou.aliyuncs.com/public/attachment/201803/100651/201803150923220770.mp4"); 53 | urlList.add("http://chuangfen.oss-cn-hangzhou.aliyuncs.com/public/attachment/201803/100651/201803150922255785.mp4"); 54 | urlList.add("http://chuangfen.oss-cn-hangzhou.aliyuncs.com/public/attachment/201803/100651/201803150920130302.mp4"); 55 | urlList.add("http://chuangfen.oss-cn-hangzhou.aliyuncs.com/public/attachment/201803/100651/201803141625005241.mp4"); 56 | urlList.add("http://chuangfen.oss-cn-hangzhou.aliyuncs.com/public/attachment/201803/100651/201803141624378522.mp4"); 57 | urlList.add("http://chuangfen.oss-cn-hangzhou.aliyuncs.com/public/attachment/201803/100651/201803131546119319.mp4"); 58 | 59 | 60 | snapHelper = new PagerSnapHelper(); 61 | snapHelper.attachToRecyclerView(rvPage2); 62 | 63 | 64 | videoAdapter = new ListVideoAdapter(urlList); 65 | layoutManager = new LinearLayoutManager(Page2Activity.this, LinearLayoutManager.VERTICAL, false); 66 | rvPage2.setLayoutManager(layoutManager); 67 | rvPage2.setAdapter(videoAdapter); 68 | 69 | } 70 | 71 | private void addListener() { 72 | 73 | rvPage2.addOnScrollListener(new RecyclerView.OnScrollListener() { 74 | @Override 75 | public void onScrolled(RecyclerView recyclerView, int dx, int dy) { 76 | 77 | 78 | } 79 | 80 | @Override 81 | public void onScrollStateChanged(RecyclerView recyclerView, int newState) { 82 | switch (newState) { 83 | case RecyclerView.SCROLL_STATE_IDLE://停止滚动 84 | View view = snapHelper.findSnapView(layoutManager); 85 | 86 | //当前固定后的item position 87 | int position = recyclerView.getChildAdapterPosition(view); 88 | if (currentPosition != position) { 89 | //如果当前position 和 上一次固定后的position 相同, 说明是同一个, 只不过滑动了一点点, 然后又释放了 90 | MyVideoPlayer.releaseAllVideos(); 91 | RecyclerView.ViewHolder viewHolder = recyclerView.getChildViewHolder(view); 92 | if (viewHolder != null && viewHolder instanceof VideoViewHolder) { 93 | ((VideoViewHolder) viewHolder).mp_video.startVideo(); 94 | } 95 | } 96 | currentPosition = position; 97 | break; 98 | case RecyclerView.SCROLL_STATE_DRAGGING://拖动 99 | break; 100 | case RecyclerView.SCROLL_STATE_SETTLING://惯性滑动 101 | break; 102 | } 103 | 104 | } 105 | }); 106 | } 107 | 108 | 109 | @Override 110 | public void onPause() { 111 | super.onPause(); 112 | MyVideoPlayer.releaseAllVideos(); 113 | } 114 | 115 | 116 | class ListVideoAdapter extends BaseRecAdapter { 117 | 118 | 119 | public ListVideoAdapter(List list) { 120 | super(list); 121 | } 122 | 123 | @Override 124 | public void onHolder(VideoViewHolder holder, String bean, int position) { 125 | ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams(); 126 | layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT; 127 | 128 | holder.mp_video.setUp(bean, "第" + position + "个视频", MyVideoPlayer.STATE_NORMAL); 129 | if (position == 0) { 130 | holder.mp_video.startVideo(); 131 | } 132 | Glide.with(context).load(bean).into(holder.mp_video.thumbImageView); 133 | holder.tv_title.setText("第" + position + "个视频"); 134 | } 135 | 136 | @Override 137 | public VideoViewHolder onCreateHolder() { 138 | return new VideoViewHolder(getViewByRes(R.layout.item_page2)); 139 | 140 | } 141 | 142 | 143 | } 144 | 145 | public class VideoViewHolder extends BaseRecViewHolder { 146 | public View rootView; 147 | public MyVideoPlayer mp_video; 148 | public TextView tv_title; 149 | 150 | public VideoViewHolder(View rootView) { 151 | super(rootView); 152 | this.rootView = rootView; 153 | this.mp_video = rootView.findViewById(R.id.mp_video); 154 | this.tv_title = rootView.findViewById(R.id.tv_title); 155 | } 156 | 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /app/src/main/java/com/ch/doudemo/activity/PageActivity.java: -------------------------------------------------------------------------------- 1 | package com.ch.doudemo.activity; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.NonNull; 5 | import android.support.v4.view.ViewPager; 6 | import android.support.v7.app.AppCompatActivity; 7 | 8 | import com.ch.doudemo.R; 9 | import com.ch.doudemo.adapter.VerticalViewPagerAdapter; 10 | import com.ch.doudemo.widget.VerticalViewPager; 11 | import com.ch.doudemo.widget.VerticalViewPager2; 12 | import com.scwang.smartrefresh.layout.SmartRefreshLayout; 13 | import com.scwang.smartrefresh.layout.api.RefreshLayout; 14 | import com.scwang.smartrefresh.layout.listener.OnRefreshLoadMoreListener; 15 | 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | 19 | import butterknife.BindView; 20 | import butterknife.ButterKnife; 21 | 22 | /** 23 | * 翻页 24 | */ 25 | public class PageActivity extends AppCompatActivity { 26 | 27 | @BindView(R.id.vvp_back_play) 28 | VerticalViewPager2 vvpBackPlay; 29 | @BindView(R.id.srl_page) 30 | SmartRefreshLayout srlPage; 31 | private List urlList; 32 | private VerticalViewPagerAdapter pagerAdapter; 33 | 34 | @Override 35 | protected void onCreate(Bundle savedInstanceState) { 36 | super.onCreate(savedInstanceState); 37 | setContentView(R.layout.activity_page); 38 | ButterKnife.bind(this); 39 | initView(); 40 | addListener(); 41 | } 42 | 43 | private void addListener() { 44 | srlPage.setEnableAutoLoadMore(false); 45 | srlPage.setEnableLoadMore(false); 46 | srlPage.setOnRefreshLoadMoreListener(new OnRefreshLoadMoreListener() { 47 | @Override 48 | public void onLoadMore(@NonNull RefreshLayout refreshLayout) { 49 | srlPage.postDelayed(new Runnable() { 50 | @Override 51 | public void run() { 52 | urlList.addAll(urlList); 53 | pagerAdapter.setUrlList(urlList); 54 | pagerAdapter.notifyDataSetChanged(); 55 | 56 | srlPage.finishLoadMore(); 57 | } 58 | }, 2000); 59 | 60 | } 61 | 62 | @Override 63 | public void onRefresh(@NonNull RefreshLayout refreshLayout) { 64 | 65 | } 66 | }); 67 | } 68 | 69 | private void initView() { 70 | makeData(); 71 | pagerAdapter = new VerticalViewPagerAdapter(getSupportFragmentManager()); 72 | // vvpBackPlay.setVertical(true); 73 | vvpBackPlay.setOffscreenPageLimit(10); 74 | pagerAdapter.setUrlList(urlList); 75 | vvpBackPlay.setAdapter(pagerAdapter); 76 | 77 | vvpBackPlay.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { 78 | @Override 79 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 80 | 81 | } 82 | 83 | @Override 84 | public void onPageSelected(int position) { 85 | if (position == urlList.size() - 1) { 86 | srlPage.setEnableAutoLoadMore(true); 87 | srlPage.setEnableLoadMore(true); 88 | } else { 89 | srlPage.setEnableAutoLoadMore(false); 90 | srlPage.setEnableLoadMore(false); 91 | } 92 | } 93 | 94 | @Override 95 | public void onPageScrollStateChanged(int state) { 96 | 97 | } 98 | }); 99 | } 100 | 101 | private void makeData() { 102 | urlList = new ArrayList<>(); 103 | urlList.add("http://chuangfen.oss-cn-hangzhou.aliyuncs.com/public/attachment/201805/100651/201805181532123423.mp4"); 104 | urlList.add("http://chuangfen.oss-cn-hangzhou.aliyuncs.com/public/attachment/201803/100651/201803151735198462.mp4"); 105 | urlList.add("http://chuangfen.oss-cn-hangzhou.aliyuncs.com/public/attachment/201803/100651/201803150923220770.mp4"); 106 | urlList.add("http://chuangfen.oss-cn-hangzhou.aliyuncs.com/public/attachment/201803/100651/201803150922255785.mp4"); 107 | urlList.add("http://chuangfen.oss-cn-hangzhou.aliyuncs.com/public/attachment/201803/100651/201803150920130302.mp4"); 108 | urlList.add("http://chuangfen.oss-cn-hangzhou.aliyuncs.com/public/attachment/201803/100651/201803141625005241.mp4"); 109 | urlList.add("http://chuangfen.oss-cn-hangzhou.aliyuncs.com/public/attachment/201803/100651/201803141624378522.mp4"); 110 | urlList.add("http://chuangfen.oss-cn-hangzhou.aliyuncs.com/public/attachment/201803/100651/201803131546119319.mp4"); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /app/src/main/java/com/ch/doudemo/activity/PrepareActivity.java: -------------------------------------------------------------------------------- 1 | package com.ch.doudemo.activity; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.text.TextUtils; 6 | 7 | import com.ch.doudemo.R; 8 | import com.ch.doudemo.widget.MyVideoPlayer; 9 | 10 | import butterknife.BindView; 11 | import butterknife.ButterKnife; 12 | 13 | /** 14 | * 视频预览 15 | */ 16 | public class PrepareActivity extends AppCompatActivity { 17 | 18 | @BindView(R.id.mp_video) 19 | MyVideoPlayer mpVideo; 20 | 21 | public static final String VIDEO_PATH = "VIDEO_PATH"; 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | setContentView(R.layout.activity_prepare); 27 | ButterKnife.bind(this); 28 | initView(); 29 | } 30 | 31 | private void initView() { 32 | String path = getIntent().getStringExtra(VIDEO_PATH); 33 | if (!TextUtils.isEmpty(path)) { 34 | mpVideo.setUp(path, path, MyVideoPlayer.STATE_NORMAL); 35 | mpVideo.startVideo(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/ch/doudemo/activity/Record2Activity.java: -------------------------------------------------------------------------------- 1 | package com.ch.doudemo.activity; 2 | 3 | import android.Manifest; 4 | import android.annotation.SuppressLint; 5 | import android.content.Context; 6 | import android.content.pm.PackageManager; 7 | import android.graphics.ImageFormat; 8 | import android.hardware.camera2.CameraAccessException; 9 | import android.hardware.camera2.CameraCaptureSession; 10 | import android.hardware.camera2.CameraCharacteristics; 11 | import android.hardware.camera2.CameraDevice; 12 | import android.hardware.camera2.CameraManager; 13 | import android.hardware.camera2.CaptureRequest; 14 | import android.hardware.camera2.params.StreamConfigurationMap; 15 | import android.media.ImageReader; 16 | import android.os.Build; 17 | import android.os.Bundle; 18 | import android.os.Handler; 19 | import android.os.HandlerThread; 20 | import android.os.Message; 21 | import android.support.annotation.NonNull; 22 | import android.support.annotation.RequiresApi; 23 | import android.support.v4.app.ActivityCompat; 24 | import android.support.v7.app.AppCompatActivity; 25 | import android.util.Log; 26 | import android.util.Size; 27 | import android.view.Surface; 28 | import android.view.SurfaceHolder; 29 | import android.view.SurfaceView; 30 | import android.view.View; 31 | import android.widget.Button; 32 | import android.widget.SeekBar; 33 | 34 | import com.ch.doudemo.R; 35 | 36 | import java.util.ArrayList; 37 | import java.util.Arrays; 38 | import java.util.Collections; 39 | import java.util.Comparator; 40 | import java.util.List; 41 | 42 | import butterknife.BindView; 43 | import butterknife.ButterKnife; 44 | import butterknife.OnClick; 45 | import pub.devrel.easypermissions.EasyPermissions; 46 | 47 | public class Record2Activity extends AppCompatActivity implements SurfaceHolder.Callback, EasyPermissions.PermissionCallbacks { 48 | 49 | @BindView(R.id.sv_record) 50 | SurfaceView svRecord; 51 | @BindView(R.id.btn_start) 52 | Button btnStart; 53 | @BindView(R.id.btn_switch) 54 | Button btnSwitch; 55 | @BindView(R.id.btn_end) 56 | Button btnEnd; 57 | @BindView(R.id.pb_record) 58 | SeekBar pbRecord; 59 | 60 | private static final int RC_STORAGE = 1001; 61 | private SurfaceHolder surfaceHolder; 62 | private CameraDevice camera; 63 | 64 | private ImageReader imageReader; 65 | 66 | 67 | @SuppressLint("HandlerLeak") 68 | private Handler handler; 69 | private CaptureRequest.Builder mCaptureRequestBuilder; 70 | private CaptureRequest mCaptureRequest; 71 | private CameraCaptureSession mPreviewSession; 72 | private HandlerThread mThreadHandler; 73 | 74 | @Override 75 | protected void onCreate(Bundle savedInstanceState) { 76 | super.onCreate(savedInstanceState); 77 | setContentView(R.layout.activity_record2); 78 | ButterKnife.bind(this); 79 | initView(); 80 | } 81 | 82 | 83 | private void initView() { 84 | surfaceHolder = svRecord.getHolder(); 85 | surfaceHolder.addCallback(this); 86 | //设置一些参数方便后面绘图 87 | svRecord.setFocusable(true); 88 | svRecord.setKeepScreenOn(true); 89 | svRecord.setFocusableInTouchMode(true); 90 | 91 | pbRecord.setMax(100); 92 | pbRecord.setProgress(0); 93 | 94 | mThreadHandler = new HandlerThread("CAMERA2"); 95 | mThreadHandler.start(); 96 | handler = new Handler(mThreadHandler.getLooper()); 97 | } 98 | 99 | @OnClick({R.id.btn_start, R.id.btn_switch, R.id.btn_end}) 100 | public void onViewClicked(View view) { 101 | switch (view.getId()) { 102 | case R.id.btn_start: 103 | break; 104 | case R.id.btn_switch: 105 | break; 106 | case R.id.btn_end: 107 | break; 108 | } 109 | } 110 | 111 | private void requestPermision() { 112 | String[] perms = {Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, 113 | Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO}; 114 | if (EasyPermissions.hasPermissions(this, perms)) { 115 | // Already have permission, do the thing 116 | startPreview(); 117 | // ... 118 | } else { 119 | // Do not have permissions, request them now 120 | EasyPermissions.requestPermissions(this, "我们的app需要以下权限", 121 | RC_STORAGE, perms); 122 | } 123 | } 124 | 125 | 126 | @Override 127 | public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { 128 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 129 | // Forward results to EasyPermissions 130 | EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); 131 | } 132 | 133 | @Override 134 | public void onPermissionsGranted(int requestCode, @NonNull List perms) { 135 | // Some permissions have been granted 136 | startPreview(); 137 | } 138 | 139 | 140 | @Override 141 | public void onPermissionsDenied(int requestCode, @NonNull List perms) { 142 | // Some permissions have been denied 143 | finish(); 144 | } 145 | 146 | @Override 147 | public void surfaceCreated(SurfaceHolder holder) { 148 | surfaceHolder = holder; 149 | requestPermision(); 150 | } 151 | 152 | @Override 153 | public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 154 | 155 | } 156 | 157 | @Override 158 | public void surfaceDestroyed(SurfaceHolder holder) { 159 | 160 | //停止预览并释放摄像头资源 161 | stopPreview(); 162 | //停止录制 163 | startRecord(); 164 | 165 | } 166 | 167 | private void startPreview() { 168 | if (svRecord == null || surfaceHolder == null) { 169 | return; 170 | } 171 | 172 | CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); 173 | 174 | try { 175 | String[] cameras = manager.getCameraIdList(); 176 | if (cameras != null && cameras.length > 0) { 177 | CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameras[0]); 178 | 179 | StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 180 | Size largest = Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)), new CompareSizesByArea()); 181 | 182 | surfaceHolder.setFixedSize(largest.getWidth(), largest.getHeight()); 183 | 184 | imageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(), ImageFormat.JPEG, /*maxImages*/2);//初始化ImageReader 185 | 186 | imageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() { 187 | @Override 188 | public void onImageAvailable(ImageReader reader) { 189 | 190 | } 191 | }, handler); 192 | 193 | 194 | if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { 195 | return; 196 | } 197 | manager.openCamera(cameras[0], new CameraDevice.StateCallback() { 198 | @Override 199 | public void onOpened(@NonNull CameraDevice c) { 200 | Log.e("cheng", "onOpened"); 201 | camera = c; 202 | 203 | try { 204 | //创建CaptureRequestBuilder,TEMPLATE_PREVIEW比表示预览请求 205 | mCaptureRequestBuilder = camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); 206 | mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); 207 | //设置Surface作为预览数据的显示界面 208 | mCaptureRequestBuilder.addTarget(surfaceHolder.getSurface()); 209 | 210 | camera.createCaptureSession(Arrays.asList(surfaceHolder.getSurface(), imageReader.getSurface()), new CameraCaptureSession.StateCallback() { 211 | @Override 212 | public void onConfigured(@NonNull CameraCaptureSession session) { 213 | try { 214 | Log.e("cheng", "onConfigured"); 215 | //创建捕获请求 216 | mCaptureRequest = mCaptureRequestBuilder.build(); 217 | mPreviewSession = session; 218 | //设置反复捕获数据的请求,这样预览界面就会一直有数据显示 219 | mPreviewSession.setRepeatingRequest(mCaptureRequest, null, null); 220 | } catch (CameraAccessException e) { 221 | e.printStackTrace(); 222 | } 223 | } 224 | 225 | @Override 226 | public void onConfigureFailed(@NonNull CameraCaptureSession session) { 227 | Log.e("cheng", "onConfigureFailed"); 228 | camera.close(); 229 | } 230 | }, handler); 231 | } catch (CameraAccessException e) { 232 | e.printStackTrace(); 233 | } 234 | 235 | 236 | } 237 | 238 | @Override 239 | public void onDisconnected(@NonNull CameraDevice camera) { 240 | Log.e("cheng", "onDisconnected"); 241 | 242 | } 243 | 244 | @Override 245 | public void onError(@NonNull CameraDevice camera, int error) { 246 | Log.e("cheng", "error=" + error); 247 | if (error == CameraDevice.StateCallback.ERROR_CAMERA_IN_USE) { 248 | Log.e("cheng", "ERROR_CAMERA_IN_USE"); 249 | } 250 | 251 | camera.close(); 252 | } 253 | }, null); 254 | } 255 | 256 | } catch (CameraAccessException e) { 257 | e.printStackTrace(); 258 | } 259 | 260 | } 261 | 262 | private void startRecord() { 263 | } 264 | 265 | private void stopPreview() { 266 | if (camera != null) { 267 | camera.close(); 268 | } 269 | } 270 | 271 | 272 | private static class CompareSizesByArea implements Comparator { 273 | 274 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 275 | @Override 276 | public int compare(android.util.Size lhs, android.util.Size rhs) { 277 | // We cast here to ensure the multiplications won't overflow 278 | return Long.signum((long) lhs.getWidth() * lhs.getHeight() - (long) rhs.getWidth() * rhs.getHeight()); 279 | } 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /app/src/main/java/com/ch/doudemo/activity/RecordActivity.java: -------------------------------------------------------------------------------- 1 | package com.ch.doudemo.activity; 2 | 3 | import android.Manifest; 4 | import android.annotation.SuppressLint; 5 | import android.content.Intent; 6 | import android.hardware.Camera; 7 | import android.media.MediaRecorder; 8 | import android.os.Bundle; 9 | import android.os.CountDownTimer; 10 | import android.os.Environment; 11 | import android.os.Handler; 12 | import android.os.Message; 13 | import android.support.annotation.NonNull; 14 | import android.support.v7.app.AppCompatActivity; 15 | import android.util.Log; 16 | import android.view.SurfaceHolder; 17 | import android.view.SurfaceView; 18 | import android.view.View; 19 | import android.widget.Button; 20 | import android.widget.ProgressBar; 21 | import android.widget.SeekBar; 22 | import android.widget.Toast; 23 | 24 | import com.ch.doudemo.R; 25 | 26 | import java.io.File; 27 | import java.io.IOException; 28 | import java.util.List; 29 | 30 | import butterknife.BindView; 31 | import butterknife.ButterKnife; 32 | import butterknife.OnClick; 33 | import pub.devrel.easypermissions.EasyPermissions; 34 | 35 | /** 36 | * 录制 37 | */ 38 | public class RecordActivity extends AppCompatActivity implements SurfaceHolder.Callback, EasyPermissions.PermissionCallbacks { 39 | 40 | @BindView(R.id.sv_record) 41 | SurfaceView svRecord; 42 | @BindView(R.id.btn_start) 43 | Button btnStart; 44 | @BindView(R.id.btn_end) 45 | Button btnEnd; 46 | @BindView(R.id.btn_switch) 47 | Button btnSwitch; 48 | @BindView(R.id.pb_record) 49 | SeekBar pbRecord; 50 | private SurfaceHolder surfaceHolder; 51 | private Camera camera; 52 | private MediaRecorder mediaRecorder; 53 | //当前打开的摄像头标记 1--后,2--前 54 | private int currentCameraType = -1; 55 | private boolean isRecording; 56 | private File temFile; 57 | private MyTimer myTimer; 58 | private static final long TIME_MAX = 15 * 1000; 59 | private static final long TIME_INTERVAL = 500; 60 | 61 | private static final int RC_STORAGE = 1001; 62 | 63 | private Camera.Size size; 64 | 65 | 66 | @Override 67 | protected void onCreate(Bundle savedInstanceState) { 68 | super.onCreate(savedInstanceState); 69 | setContentView(R.layout.activity_record); 70 | ButterKnife.bind(this); 71 | 72 | initView(); 73 | } 74 | 75 | private void initView() { 76 | surfaceHolder = svRecord.getHolder(); 77 | surfaceHolder.addCallback(this); 78 | //设置一些参数方便后面绘图 79 | svRecord.setFocusable(true); 80 | svRecord.setKeepScreenOn(true); 81 | svRecord.setFocusableInTouchMode(true); 82 | 83 | pbRecord.setMax(100); 84 | pbRecord.setProgress(0); 85 | } 86 | 87 | @OnClick({R.id.btn_start, R.id.btn_end, R.id.btn_switch}) 88 | public void onViewClicked(View view) { 89 | switch (view.getId()) { 90 | //开始录制 91 | case R.id.btn_start: 92 | startRecord(); 93 | break; 94 | //停止录制 95 | case R.id.btn_end: 96 | stopRecord(false); 97 | break; 98 | //切换摄像头 99 | case R.id.btn_switch: 100 | stopPreview(); 101 | if (currentCameraType == 1) { 102 | camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_FRONT); 103 | currentCameraType = 2; 104 | btnSwitch.setText("前"); 105 | } else { 106 | camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK); 107 | currentCameraType = 1; 108 | btnSwitch.setText("后"); 109 | } 110 | 111 | startPreview(); 112 | break; 113 | } 114 | } 115 | 116 | /** 117 | * 开始录制 118 | */ 119 | private void startRecord() { 120 | if (mediaRecorder == null) { 121 | mediaRecorder = new MediaRecorder(); 122 | } 123 | temFile = getTemFile(); 124 | 125 | 126 | try { 127 | camera.unlock(); 128 | mediaRecorder.setCamera(camera); 129 | //从相机采集视频 130 | mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 131 | // 从麦克采集音频信息 132 | mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 133 | mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); 134 | //编码格式 135 | mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT); 136 | mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); 137 | 138 | mediaRecorder.setVideoSize(size.width, size.height); 139 | 140 | //每秒的帧数 141 | mediaRecorder.setVideoFrameRate(24); 142 | // 设置帧频率,然后就清晰了 143 | mediaRecorder.setVideoEncodingBitRate(1 * 1024 * 1024 * 100); 144 | 145 | 146 | mediaRecorder.setOutputFile(temFile.getAbsolutePath()); 147 | mediaRecorder.setPreviewDisplay(surfaceHolder.getSurface()); 148 | //解决录制视频, 播放器横向问题 149 | if (currentCameraType == 1) { 150 | //后置 151 | mediaRecorder.setOrientationHint(90); 152 | } else { 153 | //前置 154 | mediaRecorder.setOrientationHint(270); 155 | } 156 | mediaRecorder.prepare(); 157 | //正式录制 158 | mediaRecorder.start(); 159 | 160 | myTimer = new MyTimer(TIME_MAX, TIME_INTERVAL); 161 | myTimer.start(); 162 | 163 | isRecording = true; 164 | showtoast("开始录制"); 165 | } catch (Exception e) { 166 | e.printStackTrace(); 167 | } 168 | } 169 | 170 | @Override 171 | protected void onResume() { 172 | super.onResume(); 173 | startPreview(); 174 | } 175 | 176 | /** 177 | * 获取临时文件目录 178 | * 179 | * @return 180 | */ 181 | private File getTemFile() { 182 | String basePath = Environment.getExternalStorageDirectory().getPath() + "/doudemo/"; 183 | 184 | File baseFile = new File(basePath); 185 | if (!baseFile.exists()) { 186 | baseFile.mkdirs(); 187 | } 188 | 189 | File temp = new File(basePath + System.currentTimeMillis() + ".mp4"); 190 | 191 | return temp; 192 | } 193 | 194 | /** 195 | * 停止录制 196 | */ 197 | private void stopRecord(boolean delete) { 198 | if (mediaRecorder == null) { 199 | return; 200 | } 201 | if (myTimer != null) { 202 | myTimer.cancel(); 203 | } 204 | 205 | try { 206 | mediaRecorder.stop(); 207 | } catch (Exception e) { 208 | e.printStackTrace(); 209 | } 210 | mediaRecorder.reset(); 211 | mediaRecorder.release(); 212 | mediaRecorder = null; 213 | if (camera != null) { 214 | camera.lock(); 215 | } 216 | isRecording = false; 217 | 218 | if (delete) { 219 | if (temFile != null && temFile.exists()) { 220 | temFile.delete(); 221 | } 222 | } else { 223 | //停止预览 224 | stopPreview(); 225 | 226 | Intent intent = new Intent(RecordActivity.this, PrepareActivity.class); 227 | intent.putExtra(PrepareActivity.VIDEO_PATH, temFile.getPath()); 228 | startActivity(intent); 229 | 230 | } 231 | showtoast("停止录制"); 232 | } 233 | 234 | 235 | /** 236 | * 开始预览 237 | */ 238 | private void startPreview() { 239 | if (svRecord == null || surfaceHolder == null) { 240 | return; 241 | } 242 | 243 | 244 | if (camera == null) { 245 | camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK); 246 | currentCameraType = 1; 247 | btnSwitch.setText("后"); 248 | } 249 | 250 | 251 | try { 252 | camera.setPreviewDisplay(surfaceHolder); 253 | Camera.Parameters parameters = camera.getParameters(); 254 | 255 | camera.setDisplayOrientation(90); 256 | 257 | //实现Camera自动对焦 258 | List focusModes = parameters.getSupportedFocusModes(); 259 | if (focusModes != null) { 260 | for (String mode : focusModes) { 261 | mode.contains("continuous-video"); 262 | parameters.setFocusMode("continuous-video"); 263 | } 264 | } 265 | 266 | List sizes = parameters.getSupportedVideoSizes(); 267 | if (sizes.size() > 0) { 268 | size = sizes.get(sizes.size() - 1); 269 | } 270 | 271 | camera.setParameters(parameters); 272 | camera.startPreview(); 273 | } catch (IOException e) { 274 | e.printStackTrace(); 275 | } 276 | 277 | } 278 | 279 | /** 280 | * 停止预览 281 | */ 282 | private void stopPreview() { 283 | //停止预览并释放摄像头资源 284 | if (camera == null) { 285 | return; 286 | } 287 | 288 | camera.setPreviewCallback(null); 289 | camera.stopPreview(); 290 | camera.release(); 291 | camera = null; 292 | } 293 | 294 | 295 | @Override 296 | protected void onStop() { 297 | super.onStop(); 298 | stopPreview(); 299 | stopRecord(true); 300 | } 301 | 302 | @Override 303 | public void surfaceCreated(SurfaceHolder holder) { 304 | surfaceHolder = holder; 305 | requestPermision(); 306 | } 307 | 308 | 309 | @Override 310 | public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 311 | surfaceHolder = holder; 312 | } 313 | 314 | @Override 315 | public void surfaceDestroyed(SurfaceHolder holder) { 316 | //停止预览并释放摄像头资源 317 | stopPreview(); 318 | //停止录制 319 | startRecord(); 320 | } 321 | 322 | private void requestPermision() { 323 | String[] perms = {Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, 324 | Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO}; 325 | if (EasyPermissions.hasPermissions(this, perms)) { 326 | // Already have permission, do the thing 327 | startPreview(); 328 | // ... 329 | } else { 330 | // Do not have permissions, request them now 331 | EasyPermissions.requestPermissions(this, "我们的app需要以下权限", 332 | RC_STORAGE, perms); 333 | } 334 | } 335 | 336 | @Override 337 | public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { 338 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 339 | // Forward results to EasyPermissions 340 | EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); 341 | } 342 | 343 | @Override 344 | public void onPermissionsGranted(int requestCode, @NonNull List perms) { 345 | // Some permissions have been granted 346 | startPreview(); 347 | } 348 | 349 | @Override 350 | public void onPermissionsDenied(int requestCode, @NonNull List perms) { 351 | // Some permissions have been denied 352 | finish(); 353 | } 354 | 355 | 356 | public class MyTimer extends CountDownTimer { 357 | /** 358 | * @param millisInFuture The number of millis in the future from the call 359 | * to {@link #start()} until the countdown is done and {@link #onFinish()} 360 | * is called. 361 | * @param countDownInterval The interval along the way to receive 362 | * {@link #onTick(long)} callbacks. 363 | */ 364 | public MyTimer(long millisInFuture, long countDownInterval) { 365 | super(millisInFuture, countDownInterval); 366 | } 367 | 368 | @Override 369 | public void onTick(long millisUntilFinished) { 370 | int progress = (int) ((TIME_MAX - millisUntilFinished) / (double) TIME_MAX * 100); 371 | Log.e("cheng", "millisUntilFinished=" + progress); 372 | pbRecord.setProgress(progress); 373 | 374 | } 375 | 376 | @Override 377 | public void onFinish() { 378 | stopRecord(false); 379 | } 380 | } 381 | 382 | /** 383 | * @param s 384 | */ 385 | public void showtoast(@NonNull String s) { 386 | Toast.makeText(RecordActivity.this, s, Toast.LENGTH_SHORT).show(); 387 | } 388 | 389 | } 390 | -------------------------------------------------------------------------------- /app/src/main/java/com/ch/doudemo/adapter/VerticalViewPagerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.ch.doudemo.adapter; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.NonNull; 5 | import android.support.v4.app.Fragment; 6 | import android.support.v4.app.FragmentManager; 7 | import android.support.v4.app.FragmentTransaction; 8 | import android.support.v4.view.PagerAdapter; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | 12 | import com.ch.doudemo.fragment.VideoFragment; 13 | 14 | import java.util.List; 15 | 16 | /** 17 | * 作者: ch 18 | * 时间: 2018/7/30 0030-下午 3:42 19 | * 描述: 20 | * 来源: 21 | */ 22 | 23 | 24 | public class VerticalViewPagerAdapter extends PagerAdapter { 25 | private FragmentManager fragmentManager; 26 | private FragmentTransaction mCurTransaction; 27 | private Fragment mCurrentPrimaryItem = null; 28 | private List urlList; 29 | 30 | public void setUrlList(List urlList) { 31 | this.urlList = urlList; 32 | } 33 | 34 | 35 | public VerticalViewPagerAdapter(FragmentManager fm) { 36 | this.fragmentManager = fm; 37 | } 38 | 39 | @Override 40 | public int getCount() { 41 | return urlList.size(); 42 | } 43 | 44 | @Override 45 | public Object instantiateItem(ViewGroup container, int position) { 46 | 47 | if (mCurTransaction == null) { 48 | mCurTransaction = fragmentManager.beginTransaction(); 49 | } 50 | 51 | VideoFragment fragment = new VideoFragment(); 52 | if (urlList != null && urlList.size() > 0) { 53 | Bundle bundle = new Bundle(); 54 | if (position >= urlList.size()) { 55 | bundle.putString(VideoFragment.URL, urlList.get(position % urlList.size())); 56 | } else { 57 | bundle.putString(VideoFragment.URL, urlList.get(position)); 58 | } 59 | fragment.setArguments(bundle); 60 | } 61 | 62 | 63 | mCurTransaction.add(container.getId(), fragment, 64 | makeFragmentName(container.getId(), position)); 65 | fragment.setUserVisibleHint(false); 66 | 67 | return fragment; 68 | } 69 | 70 | 71 | @Override 72 | public void destroyItem(ViewGroup container, int position, Object object) { 73 | if (mCurTransaction == null) { 74 | mCurTransaction = fragmentManager.beginTransaction(); 75 | } 76 | mCurTransaction.detach((Fragment) object); 77 | mCurTransaction.remove((Fragment) object); 78 | } 79 | 80 | @Override 81 | public boolean isViewFromObject(@NonNull View view, @NonNull Object object) { 82 | return ((Fragment) object).getView() == view; 83 | } 84 | 85 | private String makeFragmentName(int viewId, int position) { 86 | return "android:switcher:" + viewId + position; 87 | } 88 | 89 | @Override 90 | public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) { 91 | Fragment fragment = (Fragment) object; 92 | if (fragment != mCurrentPrimaryItem) { 93 | if (mCurrentPrimaryItem != null) { 94 | mCurrentPrimaryItem.setMenuVisibility(false); 95 | mCurrentPrimaryItem.setUserVisibleHint(false); 96 | } 97 | if (fragment != null) { 98 | fragment.setMenuVisibility(true); 99 | fragment.setUserVisibleHint(true); 100 | } 101 | mCurrentPrimaryItem = fragment; 102 | } 103 | } 104 | 105 | @Override 106 | public void finishUpdate(ViewGroup container) { 107 | if (mCurTransaction != null) { 108 | mCurTransaction.commitNowAllowingStateLoss(); 109 | mCurTransaction = null; 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /app/src/main/java/com/ch/doudemo/base/BaseRecAdapter.java: -------------------------------------------------------------------------------- 1 | package com.ch.doudemo.base; 2 | 3 | 4 | import android.content.Context; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | /** 14 | * Created by ch 15 | * on 2017/12/19.14:24 16 | * 作用: 17 | */ 18 | 19 | public abstract class BaseRecAdapter extends RecyclerView.Adapter { 20 | 21 | private List list; 22 | private onItemClickListener itemClickListener; 23 | private OnItemLongClickListener itemLongClickListener; 24 | public Context context; 25 | 26 | public OnItemLongClickListener getItemLongClickListener() { 27 | return itemLongClickListener; 28 | } 29 | 30 | public void setItemLongClickListener(OnItemLongClickListener itemLongClickListener) { 31 | this.itemLongClickListener = itemLongClickListener; 32 | } 33 | 34 | public onItemClickListener getItemClickListener() { 35 | return itemClickListener; 36 | } 37 | 38 | public void setItemClickListener(onItemClickListener itemClickListener) { 39 | this.itemClickListener = itemClickListener; 40 | } 41 | 42 | public BaseRecAdapter(List list) { 43 | this.list = list; 44 | } 45 | 46 | @Override 47 | public K onCreateViewHolder(ViewGroup parent, int viewType) { 48 | context = parent.getContext(); 49 | K holder = onCreateHolder(); 50 | //绑定listener 51 | bindListener(holder); 52 | return holder; 53 | } 54 | 55 | @Override 56 | public void onBindViewHolder(K holder, int position) { 57 | 58 | onHolder(holder, list.get(position), position); 59 | 60 | } 61 | 62 | 63 | @Override 64 | public int getItemCount() { 65 | return list == null ? 0 : list.size(); 66 | } 67 | 68 | 69 | /** 70 | * 填充数据 71 | * 72 | * @param holder 73 | * @param position 74 | */ 75 | public abstract void onHolder(K holder, T bean, int position); 76 | 77 | 78 | public abstract K onCreateHolder(); 79 | 80 | 81 | /** 82 | * 通过资源res获得view 83 | * 84 | * @param res 85 | * @return 86 | */ 87 | public View getViewByRes(int res) { 88 | return LayoutInflater.from(context).inflate(res, null); 89 | } 90 | 91 | /** 92 | * 通过资源res获得view 93 | * 94 | * @param res 95 | * @return 96 | */ 97 | public View getViewByRes(int res, ViewGroup prent) { 98 | return LayoutInflater.from(context).inflate(res, prent); 99 | } 100 | 101 | /** 102 | * 绑定事件 103 | * 104 | * @param holder 105 | */ 106 | private void bindListener(final K holder) { 107 | 108 | if (holder == null) { 109 | 110 | return; 111 | } 112 | View itemView = holder.itemView; 113 | if (itemView == null) { 114 | return; 115 | } 116 | ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); 117 | itemView.setLayoutParams(params); 118 | 119 | if (getItemClickListener() != null) { 120 | itemView.setOnClickListener(new View.OnClickListener() { 121 | @Override 122 | public void onClick(View v) { 123 | getItemClickListener().onItemClick(BaseRecAdapter.this, v, holder.getLayoutPosition()); 124 | } 125 | }); 126 | } 127 | 128 | if (getItemLongClickListener() != null) { 129 | itemView.setOnLongClickListener(new View.OnLongClickListener() { 130 | @Override 131 | public boolean onLongClick(View v) { 132 | return getItemLongClickListener().onItemLongClick(BaseRecAdapter.this, v, holder.getLayoutPosition()); 133 | } 134 | }); 135 | } 136 | 137 | } 138 | 139 | 140 | public interface onItemClickListener { 141 | void onItemClick(BaseRecAdapter adapter, View view, int position); 142 | 143 | 144 | } 145 | 146 | public interface OnItemLongClickListener { 147 | boolean onItemLongClick(BaseRecAdapter adapter, View view, int position); 148 | } 149 | 150 | 151 | public void setNewData(List lt) { 152 | if (list == null) { 153 | list = new ArrayList(); 154 | } 155 | 156 | if (lt == null) { 157 | lt = new ArrayList(); 158 | } 159 | list = lt; 160 | notifyDataSetChanged(); 161 | } 162 | 163 | public List getData() { 164 | return list; 165 | } 166 | 167 | } 168 | -------------------------------------------------------------------------------- /app/src/main/java/com/ch/doudemo/base/BaseRecViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.ch.doudemo.base; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.View; 5 | 6 | /** 7 | * Created by ch 8 | * on 2017/12/19.15:42 9 | * 作用: 10 | */ 11 | 12 | public class BaseRecViewHolder extends RecyclerView.ViewHolder { 13 | public BaseRecViewHolder(View itemView) { 14 | super(itemView); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/ch/doudemo/base/MyApp.java: -------------------------------------------------------------------------------- 1 | package com.ch.doudemo.base; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | import com.ch.doudemo.widget.MyFileNameGenerator; 7 | import com.danikula.videocache.HttpProxyCacheServer; 8 | 9 | /** 10 | * 作者: ch 11 | * 时间: 2018/10/12 0012-上午 11:34 12 | * 描述: 13 | * 来源: 14 | */ 15 | 16 | public class MyApp extends Application { 17 | 18 | 19 | private HttpProxyCacheServer proxy; 20 | 21 | public static HttpProxyCacheServer getProxy(Context context) { 22 | MyApp app = (MyApp) context.getApplicationContext(); 23 | return app.proxy == null ? (app.proxy = app.newProxy()) : app.proxy; 24 | } 25 | 26 | private HttpProxyCacheServer newProxy() { 27 | return new HttpProxyCacheServer.Builder(this) 28 | .maxCacheSize(1024 * 1024 * 1024) // 1 Gb for cache 29 | .fileNameGenerator(new MyFileNameGenerator()) 30 | .build(); 31 | } 32 | 33 | @Override 34 | public void onCreate() { 35 | super.onCreate(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/ch/doudemo/fragment/BaseFragment.java: -------------------------------------------------------------------------------- 1 | package com.ch.doudemo.fragment; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.annotation.NonNull; 6 | import android.support.annotation.Nullable; 7 | import android.support.v4.app.Fragment; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | 12 | import butterknife.ButterKnife; 13 | import butterknife.Unbinder; 14 | 15 | 16 | public abstract class BaseFragment extends Fragment { 17 | 18 | protected String TAG = getClass().getSimpleName(); 19 | protected Context context; 20 | private View rootView; 21 | 22 | protected abstract int getLayoutId(); 23 | 24 | protected abstract void initView(); 25 | 26 | 27 | @Override 28 | public void onAttach(Context context) { 29 | super.onAttach(context); 30 | this.context = context; 31 | } 32 | 33 | //Fragment的View加载完毕的标记 34 | private boolean isViewCreated; 35 | //Fragment对用户可见的标记 36 | public boolean isUIVisible; 37 | protected boolean isLoadCompleted; 38 | Unbinder unbinder; 39 | 40 | 41 | @Override 42 | public void setUserVisibleHint(boolean isVisibleToUser) { 43 | super.setUserVisibleHint(isVisibleToUser); 44 | //isVisibleToUser这个boolean值表示:该Fragment的UI 用户是否可见 45 | if (isVisibleToUser && !isLoadCompleted) { 46 | isUIVisible = true; 47 | lazyLoad(); 48 | } else { 49 | isUIVisible = false; 50 | } 51 | } 52 | 53 | @Override 54 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 55 | super.onActivityCreated(savedInstanceState); 56 | if (getUserVisibleHint() && !isLoadCompleted) { 57 | // 此处不需要判断isViewCreated,因为这个方法在onCreateView方法之后执行 58 | lazyLoad(); 59 | } 60 | } 61 | 62 | @Nullable 63 | @Override 64 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 65 | rootView = inflater.inflate(getLayoutId(), container, false); 66 | unbinder = ButterKnife.bind(this, rootView); 67 | isViewCreated = true; 68 | initView(); 69 | return rootView; 70 | } 71 | 72 | @Override 73 | public void onHiddenChanged(boolean hidden) { 74 | super.onHiddenChanged(hidden); 75 | isUIVisible = !hidden; 76 | } 77 | 78 | protected void lazyLoad() { 79 | //这里进行双重标记判断,是因为setUserVisibleHint会多次回调,并且会在onCreateView执行前回调,必须确保onCreateView加载完毕且页面可见,才加载数据 80 | if (isViewCreated && isUIVisible) { 81 | loadData(); 82 | //数据加载完毕,恢复标记,防止重复加载 83 | // isViewCreated = false; 84 | // isUIVisible = false; 85 | isLoadCompleted = true; 86 | } 87 | } 88 | 89 | 90 | protected abstract void loadData(); 91 | 92 | public boolean onBackPressed() { 93 | assert getFragmentManager() != null; 94 | if (getFragmentManager().getBackStackEntryCount() > 0) { 95 | getFragmentManager().popBackStack(); 96 | return true; 97 | } 98 | return false; 99 | } 100 | 101 | 102 | @Override 103 | public void onDestroy() { 104 | super.onDestroy(); 105 | if (unbinder != null) { 106 | unbinder.unbind(); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /app/src/main/java/com/ch/doudemo/fragment/VideoFragment.java: -------------------------------------------------------------------------------- 1 | package com.ch.doudemo.fragment; 2 | 3 | import android.app.Activity; 4 | import android.graphics.Point; 5 | import android.support.v4.widget.DrawerLayout; 6 | import android.support.v4.widget.ViewDragHelper; 7 | import android.view.View; 8 | import android.widget.RelativeLayout; 9 | 10 | import com.bumptech.glide.Glide; 11 | import com.ch.doudemo.R; 12 | import com.ch.doudemo.widget.MyVideoPlayer; 13 | 14 | import java.lang.reflect.Field; 15 | 16 | import butterknife.BindView; 17 | 18 | /** 19 | * 作者: ch 20 | * 时间: 2018/7/30 0030-下午 2:55 21 | * 描述: 22 | * 来源: 23 | */ 24 | 25 | 26 | public class VideoFragment extends BaseFragment { 27 | @BindView(R.id.txv_video) 28 | MyVideoPlayer txvVideo; 29 | @BindView(R.id.rl_back_right) 30 | RelativeLayout rlBackRight; 31 | @BindView(R.id.dl_back_play) 32 | DrawerLayout dlBackPlay; 33 | private String url; 34 | public static final String URL = "URL"; 35 | 36 | @Override 37 | protected int getLayoutId() { 38 | return R.layout.fm_video; 39 | } 40 | 41 | @Override 42 | protected void initView() { 43 | 44 | url = getArguments().getString(URL); 45 | Glide.with(context) 46 | .load(url) 47 | .into(txvVideo.thumbImageView); 48 | txvVideo.rl_touch_help.setVisibility(View.GONE); 49 | txvVideo.setUp(url, url); 50 | 51 | } 52 | 53 | @Override 54 | protected void loadData() { 55 | txvVideo.startVideo(); 56 | } 57 | 58 | @Override 59 | public void setUserVisibleHint(boolean isVisibleToUser) { 60 | super.setUserVisibleHint(isVisibleToUser); 61 | 62 | if (txvVideo == null) { 63 | return; 64 | } 65 | if (isVisibleToUser) { 66 | txvVideo.goOnPlayOnResume(); 67 | } else { 68 | txvVideo.goOnPlayOnPause(); 69 | } 70 | 71 | } 72 | 73 | @Override 74 | public void onResume() { 75 | 76 | super.onResume(); 77 | if (txvVideo != null) { 78 | txvVideo.goOnPlayOnResume(); 79 | } 80 | 81 | } 82 | 83 | @Override 84 | public void onPause() { 85 | super.onPause(); 86 | if (txvVideo != null) { 87 | txvVideo.goOnPlayOnPause(); 88 | } 89 | } 90 | 91 | @Override 92 | public void onDestroy() { 93 | super.onDestroy(); 94 | if (txvVideo != null) { 95 | txvVideo.releaseAllVideos(); 96 | } 97 | } 98 | 99 | 100 | /** 101 | * 设置 全屏滑动 102 | * 103 | * @param activity 104 | * @param drawerLayout 105 | * @param displayWidthPercentage 106 | */ 107 | private void setDrawerRightEdgeSize(Activity activity, DrawerLayout drawerLayout, float displayWidthPercentage) { 108 | if (activity == null || drawerLayout == null) return; 109 | try { 110 | // 找到 ViewDragHelper 并设置 Accessible 为true 111 | Field mRightDragger = 112 | drawerLayout.getClass().getDeclaredField("mRightDragger");//Right 113 | mRightDragger.setAccessible(true); 114 | ViewDragHelper leftDragger = (ViewDragHelper) mRightDragger.get(drawerLayout); 115 | 116 | // 找到 edgeSizeField 并设置 Accessible 为true 117 | Field edgeSizeField = leftDragger.getClass().getDeclaredField("mEdgeSize"); 118 | edgeSizeField.setAccessible(true); 119 | int edgeSize = edgeSizeField.getInt(leftDragger); 120 | 121 | // 设置新的边缘大小 122 | Point displaySize = new Point(); 123 | activity.getWindowManager().getDefaultDisplay().getSize(displaySize); 124 | edgeSizeField.setInt(leftDragger, Math.max(edgeSize, (int) (displaySize.x * 125 | displayWidthPercentage))); 126 | } catch (NoSuchFieldException e) { 127 | } catch (IllegalArgumentException e) { 128 | } catch (IllegalAccessException e) { 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /app/src/main/java/com/ch/doudemo/widget/MyFileNameGenerator.java: -------------------------------------------------------------------------------- 1 | package com.ch.doudemo.widget; 2 | 3 | import android.text.TextUtils; 4 | 5 | import com.danikula.videocache.ProxyCacheUtils; 6 | import com.danikula.videocache.file.FileNameGenerator; 7 | 8 | /** 9 | * 作者: ch 10 | * 时间: 2018/10/11 0011-上午 10:12 11 | * 描述: 自定义缓存文件名 12 | * 来源: 13 | */ 14 | 15 | public class MyFileNameGenerator implements FileNameGenerator { 16 | private static final int MAX_EXTENSION_LENGTH = 4; 17 | 18 | @Override 19 | public String generate(String url) { 20 | String extension = getExtension(url); 21 | int dotIndex = url.lastIndexOf('.'); 22 | 23 | if (url.length() > 18 && dotIndex > 18) { 24 | return url.substring(dotIndex - 18); 25 | } 26 | String name = ProxyCacheUtils.computeMD5(url); 27 | return TextUtils.isEmpty(extension) ? name : name + "." + extension; 28 | } 29 | 30 | private String getExtension(String url) { 31 | int dotIndex = url.lastIndexOf('.'); 32 | int slashIndex = url.lastIndexOf('/'); 33 | return dotIndex != -1 && dotIndex > slashIndex && dotIndex + 2 + MAX_EXTENSION_LENGTH > url.length() ? 34 | url.substring(dotIndex + 1, url.length()) : ""; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/ch/doudemo/widget/MyVideoPlayer.java: -------------------------------------------------------------------------------- 1 | package com.ch.doudemo.widget; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.View; 6 | import android.widget.ImageView; 7 | import android.widget.LinearLayout; 8 | import android.widget.RelativeLayout; 9 | 10 | import com.ch.doudemo.R; 11 | import com.ch.doudemo.base.MyApp; 12 | import com.danikula.videocache.HttpProxyCacheServer; 13 | 14 | import cn.jzvd.JzvdStd; 15 | 16 | 17 | /** 18 | * 作者: ch 19 | * 时间: 2018/8/17 0017-下午 5:14 20 | * 描述: 21 | * 来源: 22 | */ 23 | 24 | 25 | public class MyVideoPlayer extends JzvdStd { 26 | public RelativeLayout rl_touch_help; 27 | private ImageView iv_start; 28 | private LinearLayout ll_start; 29 | 30 | private Context context; 31 | 32 | 33 | public MyVideoPlayer(Context context) { 34 | super(context); 35 | this.context = context; 36 | } 37 | 38 | public MyVideoPlayer(Context context, AttributeSet attrs) { 39 | super(context, attrs); 40 | this.context = context; 41 | } 42 | 43 | @Override 44 | public void onAutoCompletion() { 45 | 46 | thumbImageView.setVisibility(View.GONE); 47 | 48 | if (screen == SCREEN_FULLSCREEN) { 49 | onStateAutoComplete(); 50 | setUp((String) jzDataSource.getCurrentUrl(), jzDataSource.title, SCREEN_FULLSCREEN); 51 | } else { 52 | super.onAutoCompletion(); 53 | setUp((String) jzDataSource.getCurrentUrl(), jzDataSource.title, SCREEN_NORMAL); 54 | } 55 | //循环播放 56 | startVideo(); 57 | } 58 | 59 | 60 | @Override 61 | public void setUp(String url, String title, int screen) { 62 | super.setUp(url, title, screen); 63 | if (url.startsWith("http")) { 64 | HttpProxyCacheServer proxy = MyApp.getProxy(context); 65 | String proxyUrl = proxy.getProxyUrl(url); 66 | super.setUp(proxyUrl, title, screen); 67 | } else { 68 | super.setUp(url, title, screen); 69 | } 70 | 71 | } 72 | 73 | @Override 74 | public void init(final Context context) { 75 | super.init(context); 76 | 77 | rl_touch_help = findViewById(R.id.rl_touch_help); 78 | ll_start = findViewById(R.id.ll_start); 79 | iv_start = findViewById(R.id.iv_start); 80 | resetPlayView(); 81 | 82 | rl_touch_help.setOnClickListener(new View.OnClickListener() { 83 | @Override 84 | public void onClick(View v) { 85 | resetPlayView(); 86 | if (isPlay()) { 87 | fullscreenButton.performClick(); 88 | } 89 | 90 | } 91 | }); 92 | 93 | ll_start.setOnClickListener(new View.OnClickListener() { 94 | @Override 95 | public void onClick(View v) { 96 | if (isPlay()) { 97 | goOnPlayOnPause(); 98 | } else { 99 | //暂停 100 | if (state == STATE_PAUSE) { 101 | goOnPlayOnResume(); 102 | } else { 103 | startVideo(); 104 | } 105 | } 106 | resetPlayView(); 107 | } 108 | }); 109 | } 110 | 111 | @Override 112 | public void startVideo() { 113 | if (screen == SCREEN_FULLSCREEN) { 114 | startFullscreenDirectly(context, MyVideoPlayer.class, jzDataSource); 115 | onStatePreparing(); 116 | ll_start.setVisibility(VISIBLE); 117 | } else { 118 | super.startVideo(); 119 | ll_start.setVisibility(GONE); 120 | } 121 | resetPlayView(); 122 | } 123 | 124 | 125 | private void resetPlayView() { 126 | if (isPlay()) { 127 | iv_start.setBackgroundResource(R.mipmap.video_play_parse); 128 | } else { 129 | iv_start.setBackgroundResource(R.mipmap.stop); 130 | } 131 | } 132 | 133 | /** 134 | * 是否播放 135 | * 136 | * @return 137 | */ 138 | private boolean isPlay() { 139 | if (state == STATE_PREPARING || state == STATE_PLAYING || state == -1) { 140 | return true; 141 | } 142 | 143 | return false; 144 | } 145 | 146 | } 147 | -------------------------------------------------------------------------------- /app/src/main/java/com/ch/doudemo/widget/VerticalViewPager.java: -------------------------------------------------------------------------------- 1 | package com.ch.doudemo.widget; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.NonNull; 5 | import android.support.annotation.Nullable; 6 | import android.support.v4.view.ViewPager; 7 | import android.util.AttributeSet; 8 | import android.view.MotionEvent; 9 | import android.view.View; 10 | 11 | /** 12 | * 作者: ch 13 | * 时间: 2018/7/30 0030-下午 2:53 14 | * 描述: 15 | * 来源: 16 | */ 17 | 18 | 19 | public class VerticalViewPager extends ViewPager { 20 | 21 | private boolean isVertical = false; 22 | 23 | public VerticalViewPager(@NonNull Context context) { 24 | super(context); 25 | } 26 | 27 | public VerticalViewPager(@NonNull Context context, @Nullable AttributeSet attrs) { 28 | super(context, attrs); 29 | } 30 | 31 | private void init() { 32 | // The majority of the magic happens here 33 | setPageTransformer(true, new HorizontalVerticalPageTransformer()); 34 | // The easiest way to get rid of the over scroll drawing that happens on the left and right 35 | setOverScrollMode(OVER_SCROLL_NEVER); 36 | } 37 | 38 | 39 | public boolean isVertical() { 40 | return isVertical; 41 | } 42 | 43 | public void setVertical(boolean vertical) { 44 | isVertical = vertical; 45 | init(); 46 | } 47 | 48 | private class HorizontalVerticalPageTransformer implements PageTransformer { 49 | 50 | private static final float MIN_SCALE = 0.75f; 51 | 52 | @Override 53 | public void transformPage(View page, float position) { 54 | if (isVertical) { 55 | 56 | if (position < -1) { 57 | page.setAlpha(0); 58 | } else if (position <= 1) { 59 | page.setAlpha(1); 60 | 61 | // Counteract the default slide transition 62 | float xPosition = page.getWidth() * -position; 63 | page.setTranslationX(xPosition); 64 | 65 | //set Y position to swipe in from top 66 | float yPosition = position * page.getHeight(); 67 | page.setTranslationY(yPosition); 68 | } else { 69 | page.setAlpha(0); 70 | } 71 | } else { 72 | int pageWidth = page.getWidth(); 73 | 74 | if (position < -1) { // [-Infinity,-1) 75 | // This page is way off-screen to the left. 76 | page.setAlpha(0); 77 | 78 | } else if (position <= 0) { // [-1,0] 79 | // Use the default slide transition when moving to the left page 80 | page.setAlpha(1); 81 | page.setTranslationX(0); 82 | page.setScaleX(1); 83 | page.setScaleY(1); 84 | 85 | } else if (position <= 1) { // (0,1] 86 | // Fade the page out. 87 | page.setAlpha(1 - position); 88 | 89 | // Counteract the default slide transition 90 | page.setTranslationX(pageWidth * -position); 91 | page.setTranslationY(0); 92 | 93 | // Scale the page down (between MIN_SCALE and 1) 94 | float scaleFactor = MIN_SCALE 95 | + (1 - MIN_SCALE) * (1 - Math.abs(position)); 96 | page.setScaleX(scaleFactor); 97 | page.setScaleY(scaleFactor); 98 | 99 | } else { // (1,+Infinity] 100 | // This page is way off-screen to the right. 101 | page.setAlpha(0); 102 | } 103 | } 104 | } 105 | } 106 | 107 | /** 108 | * Swaps the X and Y coordinates of your touch event. 109 | */ 110 | private MotionEvent swapXY(MotionEvent ev) { 111 | float width = getWidth(); 112 | float height = getHeight(); 113 | 114 | float newX = (ev.getY() / height) * width; 115 | float newY = (ev.getX() / width) * height; 116 | 117 | ev.setLocation(newX, newY); 118 | 119 | return ev; 120 | } 121 | 122 | 123 | @Override 124 | public boolean onInterceptTouchEvent(MotionEvent ev) { 125 | if (isVertical) { 126 | boolean intercepted = super.onInterceptTouchEvent(swapXY(ev)); 127 | swapXY(ev); // return touch coordinates to original reference frame for any child views 128 | return intercepted; 129 | } else { 130 | return super.onInterceptTouchEvent(ev); 131 | } 132 | } 133 | 134 | @Override 135 | public boolean onTouchEvent(MotionEvent ev) { 136 | if (isVertical) { 137 | return super.onTouchEvent(swapXY(ev)); 138 | } else { 139 | return super.onTouchEvent(ev); 140 | } 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_list2.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 12 | 13 | 17 | 18 |