├── .gitignore ├── 01.gif ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── shuyu │ │ └── videorecord │ │ ├── CommonUtils.java │ │ ├── FileUtils.java │ │ ├── MainActivity.java │ │ ├── PlayActivity.java │ │ ├── PlayView.java │ │ └── RecordActivity.java │ └── res │ ├── drawable-xxhdpi │ ├── flash.png │ ├── flash_off.png │ ├── record.png │ ├── stop_record.png │ └── switch_camera.png │ ├── layout │ ├── activity_main.xml │ ├── activity_play.xml │ └── activity_record.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── dependencies.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 | .DS_Store 5 | /build 6 | .idea 7 | /captures 8 | .externalNativeBuild 9 | *.apk 10 | keyid -------------------------------------------------------------------------------- /01.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/VideoRecord/7fc2d11eff5ef863cba317f381b9c60aee9a5117/01.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #### 最全的懒人视频拍摄,支持横屏拍摄效果与竖屏拍摄效果 2 | 3 | * 简书说明:http://www.jianshu.com/p/752f0dd842f8 4 | * 正反横屏拍摄横屏视频 5 | * 竖屏拍摄竖屏视频 6 | * 横竖屏重力旋转,动画切换显示动画效果 7 | * 视频播放预览效果 8 | 9 | 10 | 11 | ## GIF效果,有点掉帧 12 | ![](https://github.com/CarGuo/VideoRecord/blob/master/01.gif) 13 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'com.neenbedankt.android-apt' 3 | 4 | android { 5 | 6 | def globalConfiguration = rootProject.extensions.getByName("ext") 7 | compileSdkVersion globalConfiguration.androidCompileSdkVersion 8 | buildToolsVersion globalConfiguration.androidBuildToolsVersion 9 | 10 | defaultConfig { 11 | applicationId "com.shuyu.videorecord" 12 | minSdkVersion globalConfiguration.androidMinSdkVersion 13 | targetSdkVersion globalConfiguration.androidTargetSdkVersion 14 | versionCode 1 15 | versionName "1.0" 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | compile fileTree(dir: 'libs', include: ['*.jar']) 27 | def viewDependencies = rootProject.ext.viewDependencies 28 | def androidDependencies = rootProject.ext.androidDependencies 29 | apt viewDependencies.apt_butterKnife 30 | compile viewDependencies.butterKnife 31 | compile androidDependencies.support_v4 32 | compile androidDependencies.appcompat_v7 33 | compile androidDependencies.recyclerView 34 | compile androidDependencies.cardview_v7 35 | compile androidDependencies.design 36 | } 37 | -------------------------------------------------------------------------------- /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:\workSpace\softWare\Android\adt-bundle-windows-x86_64-20140702\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/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 38 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/shuyu/videorecord/CommonUtils.java: -------------------------------------------------------------------------------- 1 | package com.shuyu.videorecord; 2 | 3 | import android.content.Context; 4 | import android.util.DisplayMetrics; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.view.WindowManager; 8 | 9 | 10 | /** 11 | * Created by shuyu on 2016/11/21. 12 | */ 13 | 14 | public class CommonUtils { 15 | 16 | 17 | public final static int SIZE_1 = 640; 18 | public final static int SIZE_2 = 480; 19 | 20 | /** 21 | * dip转为PX 22 | */ 23 | public static int dp2px(Context context, float dipValue) { 24 | float fontScale = context.getResources().getDisplayMetrics().density; 25 | return (int) (dipValue * fontScale + 0.5f); 26 | } 27 | 28 | /** 29 | * 根据手机的分辨率从 px(像素) 的单位 转成为 dp 30 | */ 31 | public static int px2dp(Context context, float pxValue) { 32 | final float scale = context.getResources().getDisplayMetrics().density; 33 | return (int) (pxValue / scale + 0.5f); 34 | } 35 | 36 | /** 37 | * 获取屏幕的宽度px 38 | * 39 | * @param context 上下文 40 | * @return 屏幕宽px 41 | */ 42 | public static int getScreenWidth(Context context) { 43 | WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 44 | DisplayMetrics outMetrics = new DisplayMetrics();// 创建了一张白纸 45 | windowManager.getDefaultDisplay().getMetrics(outMetrics);// 给白纸设置宽高 46 | return outMetrics.widthPixels; 47 | } 48 | 49 | /** 50 | * 获取屏幕的高度px 51 | * 52 | * @param context 上下文 53 | * @return 屏幕高px 54 | */ 55 | public static int getScreenHeight(Context context) { 56 | WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 57 | DisplayMetrics outMetrics = new DisplayMetrics();// 创建了一张白纸 58 | windowManager.getDefaultDisplay().getMetrics(outMetrics);// 给白纸设置宽高 59 | return outMetrics.heightPixels; 60 | } 61 | 62 | 63 | public static void setViewSize(View view, int width, int height) { 64 | ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); 65 | if (null == layoutParams) 66 | return; 67 | layoutParams.width = width; 68 | layoutParams.height = height; 69 | view.setLayoutParams(layoutParams); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app/src/main/java/com/shuyu/videorecord/FileUtils.java: -------------------------------------------------------------------------------- 1 | package com.shuyu.videorecord; 2 | 3 | import android.os.Environment; 4 | 5 | import java.io.File; 6 | 7 | public class FileUtils { 8 | private static final String SD_PATH = Environment.getExternalStorageDirectory().getPath(); 9 | public static final String NAME = "videorecord"; 10 | 11 | public static String getAppPath() { 12 | StringBuilder sb = new StringBuilder(); 13 | sb.append(SD_PATH); 14 | sb.append(File.separator); 15 | sb.append(NAME); 16 | sb.append(File.separator); 17 | return sb.toString(); 18 | } 19 | 20 | public static void deleteFile(String filePath) { 21 | File file = new File(filePath); 22 | if (file.exists()) { 23 | if (file.isFile()) { 24 | file.delete(); 25 | } else { 26 | String[] filePaths = file.list(); 27 | for (String path : filePaths) { 28 | deleteFile(filePath + File.separator + path); 29 | } 30 | file.delete(); 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/shuyu/videorecord/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.shuyu.videorecord; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | 7 | import butterknife.ButterKnife; 8 | import butterknife.OnClick; 9 | 10 | public class MainActivity extends AppCompatActivity { 11 | 12 | @Override 13 | protected void onCreate(Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | setContentView(R.layout.activity_main); 16 | ButterKnife.bind(this); 17 | } 18 | 19 | @OnClick(R.id.recordBTN) 20 | public void onClick() { 21 | Intent intent = new Intent(this, RecordActivity.class); 22 | startActivity(intent); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/shuyu/videorecord/PlayActivity.java: -------------------------------------------------------------------------------- 1 | package com.shuyu.videorecord; 2 | 3 | import android.content.Context; 4 | import android.media.MediaPlayer; 5 | import android.net.Uri; 6 | import android.os.Bundle; 7 | import android.os.PowerManager; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.widget.Button; 10 | import android.widget.RelativeLayout; 11 | 12 | import butterknife.BindView; 13 | import butterknife.ButterKnife; 14 | import butterknife.OnClick; 15 | 16 | public class PlayActivity extends AppCompatActivity { 17 | 18 | public final static String DATA = "URL"; 19 | 20 | @BindView(R.id.playView) 21 | PlayView playView; 22 | @BindView(R.id.playBtn) 23 | Button playBtn; 24 | @BindView(R.id.activity_play) 25 | RelativeLayout activityPlay; 26 | 27 | private long playPostion = -1; 28 | private long duration = -1; 29 | String uri; 30 | 31 | @Override 32 | protected void onCreate(Bundle savedInstanceState) { 33 | super.onCreate(savedInstanceState); 34 | setContentView(R.layout.activity_play); 35 | ButterKnife.bind(this); 36 | 37 | uri = getIntent().getStringExtra(DATA); 38 | 39 | playView.setVideoURI(Uri.parse(uri)); 40 | 41 | playView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { 42 | @Override 43 | public void onCompletion(MediaPlayer mp) { 44 | playView.seekTo(1); 45 | startVideo(); 46 | } 47 | }); 48 | 49 | playView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { 50 | @Override 51 | public void onPrepared(MediaPlayer mp) { 52 | //获取视频资源的宽度 53 | int videoWidth = mp.getVideoWidth(); 54 | //获取视频资源的高度 55 | int videoHeight = mp.getVideoHeight(); 56 | playView.setSizeH(videoHeight); 57 | playView.setSizeW(videoWidth); 58 | playView.requestLayout(); 59 | duration = mp.getDuration(); 60 | play(); 61 | } 62 | }); 63 | 64 | PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); 65 | boolean isScreenOn = pm.isScreenOn();//如果为true,则表示屏幕“亮”了,否则屏幕“暗”了。 66 | if (!isScreenOn) { 67 | pauseVideo(); 68 | } 69 | } 70 | 71 | @OnClick(R.id.playBtn) 72 | public void onClick() { 73 | play(); 74 | } 75 | 76 | 77 | @Override 78 | protected void onResume() { 79 | super.onResume(); 80 | if (playPostion > 0) { 81 | pauseVideo(); 82 | } 83 | playView.seekTo((int) ((playPostion > 0 && playPostion < duration) ? playPostion : 1)); 84 | 85 | } 86 | 87 | @Override 88 | protected void onDestroy() { 89 | super.onDestroy(); 90 | playView.stopPlayback(); 91 | } 92 | 93 | @Override 94 | protected void onPause() { 95 | super.onPause(); 96 | playView.pause(); 97 | playPostion = playView.getCurrentPosition(); 98 | pauseVideo(); 99 | 100 | } 101 | 102 | @Override 103 | public void onBackPressed() { 104 | FileUtils.deleteFile(uri); 105 | finish(); 106 | overridePendingTransition(R.anim.fab_in, R.anim.fab_out); 107 | } 108 | 109 | 110 | private void pauseVideo() { 111 | playView.pause(); 112 | playBtn.setText("播放"); 113 | } 114 | 115 | private void startVideo() { 116 | playView.start(); 117 | playBtn.setText("停止"); 118 | } 119 | 120 | /** 121 | * 播放 122 | */ 123 | private void play() { 124 | if (playView.isPlaying()) { 125 | pauseVideo(); 126 | } else { 127 | if (playView.getCurrentPosition() == playView.getDuration()) { 128 | playView.seekTo(0); 129 | } 130 | startVideo(); 131 | } 132 | } 133 | 134 | 135 | } 136 | -------------------------------------------------------------------------------- /app/src/main/java/com/shuyu/videorecord/PlayView.java: -------------------------------------------------------------------------------- 1 | package com.shuyu.videorecord; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.widget.VideoView; 6 | 7 | 8 | public class PlayView extends VideoView { 9 | 10 | private int sizeW = 0; 11 | private int sizeH = 0; 12 | 13 | public PlayView(Context context) { 14 | super(context); 15 | } 16 | 17 | public PlayView(Context context, AttributeSet attrs) { 18 | super(context, attrs); 19 | } 20 | 21 | public PlayView(Context context, AttributeSet attrs, int defStyle) { 22 | super(context, attrs, defStyle); 23 | } 24 | 25 | @Override 26 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 27 | 28 | int videoWidth = sizeW; 29 | int videoHeight = sizeH; 30 | 31 | int width = getDefaultSize(videoWidth, widthMeasureSpec); 32 | int height = getDefaultSize(videoHeight, heightMeasureSpec); 33 | 34 | int widthS = getDefaultSize(videoWidth, widthMeasureSpec); 35 | int heightS = getDefaultSize(videoHeight, heightMeasureSpec); 36 | 37 | 38 | if (videoWidth > 0 && videoHeight > 0) { 39 | 40 | int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); 41 | int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); 42 | int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); 43 | int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); 44 | 45 | if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) { 46 | width = widthSpecSize; 47 | height = heightSpecSize; 48 | 49 | if (videoWidth * height < width * videoHeight) { 50 | width = height * videoWidth / videoHeight; 51 | } else if (videoWidth * height > width * videoHeight) { 52 | height = width * videoHeight / videoWidth; 53 | } 54 | } else if (widthSpecMode == MeasureSpec.EXACTLY) { 55 | width = widthSpecSize; 56 | height = width * videoHeight / videoWidth; 57 | if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) { 58 | height = heightSpecSize; 59 | } 60 | } else if (heightSpecMode == MeasureSpec.EXACTLY) { 61 | height = heightSpecSize; 62 | width = height * videoWidth / videoHeight; 63 | if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) { 64 | width = widthSpecSize; 65 | } 66 | } else { 67 | width = videoWidth; 68 | height = videoHeight; 69 | if (heightSpecMode == MeasureSpec.AT_MOST && height > heightSpecSize) { 70 | height = heightSpecSize; 71 | width = height * videoWidth / videoHeight; 72 | } 73 | if (widthSpecMode == MeasureSpec.AT_MOST && width > widthSpecSize) { 74 | width = widthSpecSize; 75 | height = width * videoHeight / videoWidth; 76 | } 77 | } 78 | } else { 79 | // no size yet, just adopt the given spec sizes 80 | } 81 | 82 | if (getRotation() != 0 && getRotation() % 90 == 0) { 83 | if (widthS < heightS) { 84 | if (width > height) { 85 | width = (int) (width * (float) widthS / height); 86 | height = widthS; 87 | } else { 88 | height = (int) (height * (float) width / widthS); 89 | width = widthS; 90 | } 91 | } else { 92 | if (width > height) { 93 | height = (int) (height * (float) width / widthS); 94 | width = widthS; 95 | } else { 96 | width = (int) (width * (float) widthS / height); 97 | height = widthS; 98 | } 99 | } 100 | } 101 | setMeasuredDimension(width, height); 102 | } 103 | 104 | public int getSizeH() { 105 | return sizeH; 106 | } 107 | 108 | public void setSizeH(int sizeH) { 109 | this.sizeH = sizeH; 110 | } 111 | 112 | public int getSizeW() { 113 | return sizeW; 114 | } 115 | 116 | public void setSizeW(int sizeW) { 117 | this.sizeW = sizeW; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /app/src/main/java/com/shuyu/videorecord/RecordActivity.java: -------------------------------------------------------------------------------- 1 | package com.shuyu.videorecord; 2 | 3 | 4 | import android.Manifest; 5 | import android.animation.ValueAnimator; 6 | import android.app.Activity; 7 | import android.content.Intent; 8 | import android.content.pm.PackageManager; 9 | import android.graphics.Point; 10 | import android.hardware.Camera; 11 | import android.media.MediaRecorder; 12 | import android.os.Bundle; 13 | import android.os.SystemClock; 14 | import android.support.v4.app.ActivityCompat; 15 | import android.support.v7.app.AppCompatActivity; 16 | import android.view.OrientationEventListener; 17 | import android.view.Surface; 18 | import android.view.SurfaceHolder; 19 | import android.view.SurfaceView; 20 | import android.view.View; 21 | import android.widget.Chronometer; 22 | import android.widget.ImageView; 23 | import android.widget.Toast; 24 | 25 | import java.io.File; 26 | import java.io.IOException; 27 | import java.util.List; 28 | 29 | import butterknife.BindView; 30 | import butterknife.ButterKnife; 31 | import butterknife.OnClick; 32 | 33 | import static com.shuyu.videorecord.CommonUtils.SIZE_1; 34 | import static com.shuyu.videorecord.CommonUtils.SIZE_2; 35 | import static com.shuyu.videorecord.CommonUtils.getScreenHeight; 36 | import static com.shuyu.videorecord.CommonUtils.getScreenWidth; 37 | 38 | public class RecordActivity extends AppCompatActivity implements SurfaceHolder.Callback { 39 | 40 | @BindView(R.id.camera_show_view) 41 | SurfaceView cameraShowView; 42 | @BindView(R.id.video_flash_light) 43 | ImageView videoFlashLight; 44 | @BindView(R.id.video_time) 45 | Chronometer videoTime; 46 | @BindView(R.id.swicth_camera) 47 | ImageView swicthCamera; 48 | @BindView(R.id.record_button) 49 | ImageView recordButton; 50 | 51 | MediaRecorder recorder; 52 | 53 | SurfaceHolder surfaceHolder; 54 | 55 | Camera camera; 56 | 57 | OrientationEventListener orientationEventListener; 58 | 59 | File videoFile; 60 | 61 | int rotationRecord = 90; 62 | 63 | int rotationFlag = 90; 64 | 65 | int flashType; 66 | 67 | int frontRotate; 68 | 69 | int frontOri; 70 | 71 | int cameraType = 0; 72 | 73 | int cameraFlag = 1; //1为后置 74 | 75 | boolean flagRecord = false;//是否正在录像 76 | 77 | 78 | @Override 79 | protected void onCreate(Bundle savedInstanceState) { 80 | super.onCreate(savedInstanceState); 81 | setContentView(R.layout.activity_record); 82 | ButterKnife.bind(this); 83 | 84 | initView(); 85 | } 86 | 87 | @Override 88 | protected void onPause() { 89 | super.onPause(); 90 | try { 91 | if (flagRecord) { 92 | endRecord(); 93 | if (camera != null && cameraType == 0) { 94 | //关闭后置摄像头闪光灯 95 | camera.lock(); 96 | FlashLogic(camera.getParameters(), 0, true); 97 | camera.unlock(); 98 | } 99 | } 100 | } catch (Exception ex) { 101 | ex.printStackTrace(); 102 | } 103 | } 104 | 105 | @Override 106 | public void onBackPressed() { 107 | if (flagRecord) { 108 | //如果是录制中的就完成录制 109 | onPause(); 110 | return; 111 | } 112 | super.onBackPressed(); 113 | } 114 | 115 | 116 | @OnClick({R.id.video_flash_light, R.id.swicth_camera, R.id.record_button}) 117 | public void onClick(View view) { 118 | switch (view.getId()) { 119 | case R.id.video_flash_light: 120 | clickFlash(); 121 | break; 122 | case R.id.swicth_camera: 123 | switchCamera(); 124 | break; 125 | case R.id.record_button: 126 | clickRecord(); 127 | break; 128 | } 129 | } 130 | 131 | @Override 132 | public void surfaceCreated(SurfaceHolder holder) { 133 | surfaceHolder = holder; 134 | initCamera(cameraType, false); 135 | } 136 | 137 | @Override 138 | public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 139 | surfaceHolder = holder; 140 | } 141 | 142 | @Override 143 | public void surfaceDestroyed(SurfaceHolder holder) { 144 | endRecord(); 145 | releaseCamera(); 146 | } 147 | 148 | @Override 149 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 150 | super.onActivityResult(requestCode, resultCode, data); 151 | endRecordUI(); 152 | } 153 | 154 | private void initView() { 155 | doStartSize(); 156 | SurfaceHolder holder = cameraShowView.getHolder(); 157 | holder.addCallback(this); 158 | // setType必须设置,要不出错. 159 | holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 160 | rotationUIListener(); 161 | } 162 | 163 | 164 | /** 165 | * 开始录制时候的状态 166 | */ 167 | private void startRecordUI() { 168 | swicthCamera.setVisibility(View.GONE); // 旋转摄像头关闭 169 | videoFlashLight.setVisibility(View.GONE); //闪光灯关闭 170 | recordButton.setImageResource(R.drawable.stop_record); //录制按钮变成待停止 171 | } 172 | 173 | /** 174 | * 停止录制时候的状态 175 | */ 176 | private void endRecordUI() { 177 | swicthCamera.setVisibility(View.VISIBLE); // 旋转摄像头关闭 178 | videoFlashLight.setVisibility(View.VISIBLE); //闪光灯关闭 179 | recordButton.setImageResource(R.drawable.record); //录制按钮变成待停止 180 | } 181 | 182 | /** 183 | * 录制按键 184 | */ 185 | private void clickRecord() { 186 | if (!flagRecord) { 187 | if (startRecord()) { 188 | startRecordUI(); 189 | videoTime.setBase(SystemClock.elapsedRealtime()); 190 | videoTime.start(); 191 | videoTime.setOnChronometerTickListener(new Chronometer.OnChronometerTickListener() { 192 | @Override 193 | public void onChronometerTick(Chronometer chronometer) { 194 | //最大录制时长 195 | if (chronometer.getText().equals("00:61")) { 196 | if (flagRecord) { 197 | endRecord(); 198 | } 199 | } 200 | } 201 | }); 202 | } 203 | } else { 204 | endRecord(); 205 | } 206 | } 207 | 208 | /** 209 | * 旋转界面UI 210 | */ 211 | private void rotationUIListener() { 212 | orientationEventListener = new OrientationEventListener(this) { 213 | @Override 214 | public void onOrientationChanged(int rotation) { 215 | if (!flagRecord) { 216 | if (((rotation >= 0) && (rotation <= 30)) || (rotation >= 330)) { 217 | // 竖屏拍摄 218 | if (rotationFlag != 0) { 219 | //旋转logo 220 | rotationAnimation(rotationFlag, 0); 221 | //这是竖屏视频需要的角度 222 | rotationRecord = 90; 223 | //这是记录当前角度的flag 224 | rotationFlag = 0; 225 | } 226 | } else if (((rotation >= 230) && (rotation <= 310))) { 227 | // 横屏拍摄 228 | if (rotationFlag != 90) { 229 | //旋转logo 230 | rotationAnimation(rotationFlag, 90); 231 | //这是正横屏视频需要的角度 232 | rotationRecord = 0; 233 | //这是记录当前角度的flag 234 | rotationFlag = 90; 235 | } 236 | } else if (rotation > 30 && rotation < 95) { 237 | // 反横屏拍摄 238 | if (rotationFlag != 270) { 239 | //旋转logo 240 | rotationAnimation(rotationFlag, 270); 241 | //这是反横屏视频需要的角度 242 | rotationRecord = 180; 243 | //这是记录当前角度的flag 244 | rotationFlag = 270; 245 | } 246 | } 247 | } 248 | } 249 | }; 250 | orientationEventListener.enable(); 251 | } 252 | 253 | 254 | private void rotationAnimation(int from, int to) { 255 | ValueAnimator progressAnimator = ValueAnimator.ofInt(from, to); 256 | progressAnimator.setDuration(300); 257 | progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 258 | @Override 259 | public void onAnimationUpdate(ValueAnimator animation) { 260 | int currentAngle = (int) animation.getAnimatedValue(); 261 | videoFlashLight.setRotation(currentAngle); 262 | videoTime.setRotation(currentAngle); 263 | swicthCamera.setRotation(currentAngle); 264 | } 265 | }); 266 | progressAnimator.start(); 267 | } 268 | 269 | /** 270 | * 因为录制改分辨率的比例可能和屏幕比例一直,所以需要调整比例显示 271 | */ 272 | private void doStartSize() { 273 | int screenWidth = getScreenWidth(this); 274 | int screenHeight = getScreenHeight(this); 275 | CommonUtils.setViewSize(cameraShowView, screenWidth * SIZE_1 / SIZE_2, screenHeight); 276 | } 277 | 278 | /** 279 | * 初始化相机 280 | * 281 | * @param type 前后的类型 282 | * @param flashDo 赏光灯是否工作 283 | */ 284 | private void initCamera(int type, boolean flashDo) { 285 | 286 | if (camera != null) { 287 | //如果已经初始化过,就先释放 288 | releaseCamera(); 289 | } 290 | 291 | try { 292 | camera = Camera.open(type); 293 | if (camera == null) { 294 | showCameraPermission(); 295 | return; 296 | } 297 | camera.lock(); 298 | 299 | //Point screen = new Point(getScreenWidth(this), getScreenHeight(this)); 300 | //现在不用获取最高的显示效果 301 | //Point show = getBestCameraShow(camera.getParameters(), screen); 302 | 303 | Camera.Parameters parameters = camera.getParameters(); 304 | if (type == 0) { 305 | //基本是都支持这个比例 306 | parameters.setPreviewSize(SIZE_1, SIZE_2); 307 | parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);//1连续对焦 308 | camera.cancelAutoFocus();// 2如果要实现连续的自动对焦,这一句必须加上 309 | } 310 | camera.setParameters(parameters); 311 | FlashLogic(camera.getParameters(), flashType, flashDo); 312 | if (cameraType == 1) { 313 | frontCameraRotate(); 314 | camera.setDisplayOrientation(frontRotate); 315 | } else { 316 | camera.setDisplayOrientation(90); 317 | } 318 | camera.setPreviewDisplay(surfaceHolder); 319 | camera.startPreview(); 320 | camera.unlock(); 321 | } catch (Exception e) { 322 | e.printStackTrace(); 323 | releaseCamera(); 324 | } 325 | } 326 | 327 | private boolean startRecord() { 328 | 329 | //懒人模式,根据闪光灯和摄像头前后重新初始化一遍,开期闪光灯工作模式 330 | initCamera(cameraType, true); 331 | 332 | if (recorder == null) { 333 | recorder = new MediaRecorder(); 334 | } 335 | if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED 336 | || camera == null || recorder == null) { 337 | camera = null; 338 | recorder = null; 339 | //还是没权限啊 340 | showCameraPermission(); 341 | return false; 342 | } 343 | 344 | try { 345 | 346 | recorder.setCamera(camera); 347 | // 这两项需要放在setOutputFormat之前 348 | recorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); 349 | recorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 350 | // Set output file format,输出格式 351 | recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); 352 | 353 | //必须在setEncoder之前 354 | recorder.setVideoFrameRate(15); //帧数 一分钟帧,15帧就够了 355 | recorder.setVideoSize(SIZE_1, SIZE_2);//这个大小就够了 356 | 357 | // 这两项需要放在setOutputFormat之后 358 | recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); 359 | recorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); 360 | 361 | recorder.setVideoEncodingBitRate(3 * SIZE_1 * SIZE_2);//第一个数字越大,清晰度就越高,考虑文件大小的缘故,就调整为1 362 | int frontRotation; 363 | if (rotationRecord == 180) { 364 | //反向的前置 365 | frontRotation = 180; 366 | } else { 367 | //正向的前置 368 | frontRotation = (rotationRecord == 0) ? 270 - frontOri : frontOri; //录制下来的视屏选择角度,此处为前置 369 | } 370 | recorder.setOrientationHint((cameraType == 1) ? frontRotation : rotationRecord); 371 | //把摄像头的画面给它 372 | recorder.setPreviewDisplay(surfaceHolder.getSurface()); 373 | //创建好视频文件用来保存 374 | videoDir(); 375 | if (videoFile != null) { 376 | //设置创建好的输入路径 377 | recorder.setOutputFile(videoFile.getPath()); 378 | recorder.prepare(); 379 | recorder.start(); 380 | //不能旋转啦 381 | orientationEventListener.disable(); 382 | flagRecord = true; 383 | } 384 | } catch (Exception e) { 385 | //一般没有录制权限或者录制参数出现问题都走这里 386 | e.printStackTrace(); 387 | //还是没权限啊 388 | recorder.reset(); 389 | recorder.release(); 390 | recorder = null; 391 | showCameraPermission(); 392 | FileUtils.deleteFile(videoFile.getPath()); 393 | return false; 394 | } 395 | return true; 396 | 397 | } 398 | 399 | private void endRecord() { 400 | //反正多次进入,比如surface的destroy和界面onPause 401 | if (!flagRecord) { 402 | return; 403 | } 404 | flagRecord = false; 405 | try { 406 | if (recorder != null) { 407 | recorder.stop(); 408 | recorder.reset(); 409 | recorder.release(); 410 | orientationEventListener.enable(); 411 | recorder = null; 412 | } 413 | } catch (Exception e) { 414 | e.printStackTrace(); 415 | } 416 | videoTime.stop(); 417 | videoTime.setBase(SystemClock.elapsedRealtime()); 418 | Intent intent = new Intent(this, PlayActivity.class); 419 | intent.putExtra(PlayActivity.DATA, videoFile.getAbsolutePath()); 420 | startActivityForResult(intent, 2222); 421 | overridePendingTransition(R.anim.fab_in, R.anim.fab_out); 422 | } 423 | 424 | public void clickFlash() { 425 | if (camera == null) { 426 | return; 427 | } 428 | camera.lock(); 429 | Camera.Parameters p = camera.getParameters(); 430 | if (flashType == 0) { 431 | FlashLogic(p, 1, false); 432 | } else { 433 | FlashLogic(p, 0, false); 434 | 435 | } 436 | camera.unlock(); 437 | } 438 | 439 | /** 440 | * 释放摄像头资源 441 | */ 442 | private void releaseCamera() { 443 | try { 444 | if (camera != null) { 445 | camera.setPreviewCallback(null); 446 | camera.stopPreview(); 447 | camera.lock(); 448 | camera.release(); 449 | camera = null; 450 | } 451 | } catch (Exception e) { 452 | e.printStackTrace(); 453 | } 454 | } 455 | 456 | /** 457 | * 闪光灯逻辑 458 | * 459 | * @param p 相机参数 460 | * @param type 打开还是关闭 461 | * @param isOn 是否启动 462 | */ 463 | private void FlashLogic(Camera.Parameters p, int type, boolean isOn) { 464 | flashType = type; 465 | if (type == 0) { 466 | if (isOn) { 467 | p.setFlashMode(Camera.Parameters.FLASH_MODE_OFF); 468 | camera.setParameters(p); 469 | } 470 | videoFlashLight.setImageResource(R.drawable.flash_off); 471 | } else { 472 | if (isOn) { 473 | p.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH); 474 | camera.setParameters(p); 475 | } 476 | videoFlashLight.setImageResource(R.drawable.flash); 477 | } 478 | if (cameraFlag == 0) { 479 | videoFlashLight.setVisibility(View.GONE); 480 | } else { 481 | videoFlashLight.setVisibility(View.VISIBLE); 482 | } 483 | } 484 | 485 | /** 486 | * 切换摄像头 487 | */ 488 | public void switchCamera() { 489 | Camera.CameraInfo cameraInfo = new Camera.CameraInfo(); 490 | int cameraCount = Camera.getNumberOfCameras();//得到摄像头的个数0或者1; 491 | 492 | try { 493 | for (int i = 0; i < cameraCount; i++) { 494 | Camera.getCameraInfo(i, cameraInfo);//得到每一个摄像头的信息 495 | if (cameraFlag == 1) { 496 | //后置到前置 497 | if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {//代表摄像头的方位,CAMERA_FACING_FRONT前置 CAMERA_FACING_BACK后置 498 | frontCameraRotate();//前置旋转摄像头度数 499 | switchCameraLogic(i, 0, frontRotate); 500 | break; 501 | } 502 | } else { 503 | //前置到后置 504 | if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {//代表摄像头的方位,CAMERA_FACING_FRONT前置 CAMERA_FACING_BACK后置 505 | switchCameraLogic(i, 1, 90); 506 | break; 507 | } 508 | } 509 | } 510 | } catch (Exception exception) { 511 | exception.printStackTrace(); 512 | } 513 | } 514 | 515 | /*** 516 | * 处理摄像头切换逻辑 517 | * 518 | * @param i 哪一个,前置还是后置 519 | * @param flag 切换后的标志 520 | * @param orientation 旋转的角度 521 | */ 522 | private void switchCameraLogic(int i, int flag, int orientation) { 523 | if (camera != null) { 524 | camera.lock(); 525 | } 526 | endRecordUI(); 527 | releaseCamera(); 528 | camera = Camera.open(i);//打开当前选中的摄像头 529 | try { 530 | camera.setDisplayOrientation(orientation); 531 | camera.setPreviewDisplay(surfaceHolder); 532 | } catch (IOException e) { 533 | e.printStackTrace(); 534 | } 535 | cameraFlag = flag; 536 | FlashLogic(camera.getParameters(), 0, false); 537 | camera.startPreview(); 538 | cameraType = i; 539 | camera.unlock(); 540 | } 541 | 542 | /** 543 | * 旋转前置摄像头为正的 544 | */ 545 | private void frontCameraRotate() { 546 | Camera.CameraInfo info = new Camera.CameraInfo(); 547 | Camera.getCameraInfo(1, info); 548 | int degrees = getDisplayRotation(this); 549 | int result; 550 | if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { 551 | result = (info.orientation + degrees) % 360; 552 | result = (360 - result) % 360; // compensate the mirror 553 | } else { // back-facing 554 | result = (info.orientation - degrees + 360) % 360; 555 | } 556 | frontOri = info.orientation; 557 | frontRotate = result; 558 | } 559 | 560 | /** 561 | * 获取旋转角度 562 | */ 563 | private int getDisplayRotation(Activity activity) { 564 | int rotation = activity.getWindowManager().getDefaultDisplay() 565 | .getRotation(); 566 | switch (rotation) { 567 | case Surface.ROTATION_0: 568 | return 0; 569 | case Surface.ROTATION_90: 570 | return 90; 571 | case Surface.ROTATION_180: 572 | return 180; 573 | case Surface.ROTATION_270: 574 | return 270; 575 | } 576 | return 0; 577 | } 578 | 579 | 580 | private void showCameraPermission() { 581 | Toast.makeText(this, "您没有开启相机权限或者录音权限", Toast.LENGTH_SHORT).show(); 582 | } 583 | 584 | public String videoDir() { 585 | File sampleDir = new File(FileUtils.getAppPath()); 586 | if (!sampleDir.exists()) { 587 | sampleDir.mkdirs(); 588 | } 589 | File vecordDir = sampleDir; 590 | // 创建文件 591 | try { 592 | videoFile = File.createTempFile("recording", ".mp4", vecordDir); 593 | } catch (IOException e) { 594 | e.printStackTrace(); 595 | } 596 | 597 | return null; 598 | } 599 | 600 | /** 601 | * 获取和屏幕比例最相近的,质量最高的显示 602 | */ 603 | private Point getBestCameraShow(Camera.Parameters parameters, Point screenResolution) { 604 | float tmpSize; 605 | float minDiffSize = 100f; 606 | float scale = (float) screenResolution.x / (float) screenResolution.y; 607 | Camera.Size best = null; 608 | List supportedPreviewSizes = parameters.getSupportedPreviewSizes(); 609 | for (Camera.Size s : supportedPreviewSizes) { 610 | tmpSize = Math.abs(((float) s.height / (float) s.width) - scale); 611 | if (tmpSize < minDiffSize) { 612 | minDiffSize = tmpSize; 613 | best = s; 614 | } 615 | } 616 | return new Point(best.width, best.height); 617 | } 618 | 619 | 620 | } 621 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/flash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/VideoRecord/7fc2d11eff5ef863cba317f381b9c60aee9a5117/app/src/main/res/drawable-xxhdpi/flash.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/flash_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/VideoRecord/7fc2d11eff5ef863cba317f381b9c60aee9a5117/app/src/main/res/drawable-xxhdpi/flash_off.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/record.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/VideoRecord/7fc2d11eff5ef863cba317f381b9c60aee9a5117/app/src/main/res/drawable-xxhdpi/record.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/stop_record.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/VideoRecord/7fc2d11eff5ef863cba317f381b9c60aee9a5117/app/src/main/res/drawable-xxhdpi/stop_record.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/switch_camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CarGuo/VideoRecord/7fc2d11eff5ef863cba317f381b9c60aee9a5117/app/src/main/res/drawable-xxhdpi/switch_camera.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 |