package com.netease.nim.uikit.session.activity;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.hardware.Camera;
import android.media.CamcorderProfile;
import android.media.MediaRecorder;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.netease.nim.uikit.R;
import com.netease.nim.uikit.common.activity.UI;
import com.netease.nim.uikit.common.ui.dialog.EasyAlertDialog;
import com.netease.nim.uikit.common.ui.dialog.EasyAlertDialogHelper;
import com.netease.nim.uikit.common.util.file.AttachmentStore;
import com.netease.nim.uikit.common.util.log.LogUtil;
import com.netease.nim.uikit.common.util.sys.TimeUtil;
import java.io.File;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
/**
* 视频录制界面
* <p/>
* Created by huangjun on 2015/4/11.
*/
public class CaptureVideoActivity extends UI implements SurfaceHolder.Callback {
private static final String TAG = "video";
private static final String EXTRA_DATA_FILE_NAME = "EXTRA_DATA_FILE_NAME";
private static final int VIDEO_TIMES = 180;
private static final int VIDEO_WIDTH = 320;
private static final int VIDEO_HEIGHT = 240;
// context
public Handler handler = new Handler();
// media
private MediaRecorder mediaRecorder;// 录制视频的类
private Camera camera;
// view
private SurfaceView surfaceview;
private SurfaceHolder surfaceHolder;
private ImageView recordBtn;
private ImageView recordingState;
private TextView recordingTimeTextView;
private ImageView switchCamera; // 切换摄像头
// state
private int cameraId = 0;
private String filename;
private boolean previewing = false;
private boolean multiCamera = false;
private boolean recording = false;
private long start, end; // 录制时间控制
private long duration = 0;
private boolean destroyed = false;
private int mAngle = 0;
private LinkedList<Point> backCameraSize = new LinkedList<>();
private LinkedList<Point> frontCameraSize = new LinkedList<>();
public static void start(Activity activity, String videoFilePath, int captureCode) {
Intent intent = new Intent();
intent.setClass(activity, CaptureVideoActivity.class);
intent.putExtra(EXTRA_DATA_FILE_NAME, videoFilePath);
activity.startActivityForResult(intent, captureCode);
}
// 录制时间计数
private Runnable runnable = new Runnable() {
public void run() {
end = new Date().getTime();
duration = (end - start);
int invs = (int) (duration / 1000);
recordingTimeTextView.setText(TimeUtil.secToTime(invs));
// 录制过程中红点闪烁效果
if (invs % 2 == 0) {
recordingState.setBackgroundResource(R.drawable.nim_record_start);
} else {
recordingState.setBackgroundResource(R.drawable.nim_record_video);
}
if (invs >= VIDEO_TIMES) {
stopRecorder();
sendVideo();
} else {
handler.postDelayed(this, 1000);
}
}
};
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setFormat(PixelFormat.TRANSLUCENT); // 使得窗口支持透明度
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.nim_capture_video_activity);
setTitle(R.string.video_record);
parseIntent();
findViews();
initActionBar();
setViewsListener();
updateRecordUI();
getVideoPreviewSize();
surfaceview = (SurfaceView) this.findViewById(R.id.videoView);
SurfaceHolder holder = surfaceview.getHolder();
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
holder.addCallback(this);
resizeSurfaceView();
}
private void parseIntent() {
filename = getIntent().getExtras().getString(EXTRA_DATA_FILE_NAME);
}
private void findViews() {
recordingTimeTextView = (TextView) findViewById(R.id.record_times);
recordingState = (ImageView) findViewById(R.id.recording_id);
recordBtn = (ImageView) findViewById(R.id.record_btn);
switchCamera = (ImageView) findViewById(R.id.switch_cameras);
}
private void initActionBar() {
checkMultiCamera();
}
private void setViewsListener() {
recordBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (recording) {
stopRecorder();
sendVideo();
} else {
startRecorder();
}
}
});
switchCamera.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
switchCamera();
}
});
}
@TargetApi(9)
private void switchCamera() {
if (Build.VERSION.SDK_INT >= 9) {
cameraId = (cameraId + 1) % Camera.getNumberOfCameras();
}
resizeSurfaceView();
shutdownCamera();
initCamera();
startPreview();
}
public void onResume() {
super.onResume();
getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
public void onPause() {
super.onPause();
getWindow().setFlags(0, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
if (recording) {
stopRecorder();
sendVideo();
} else {
shutdownCamera();
}
}
public void onDestroy() {
super.onDestroy();
shutdownCamera();
destroyed = true;
}
@Override
public void onBackPressed() {
if (recording) {
stopRecorder();
}
shutdownCamera();
setResult(RESULT_CANCELED);
finish();
}
@SuppressLint("NewApi")
private void getVideoPreviewSize(boolean isFront) {
CamcorderProfile profile;
int cameraId = 0;
if (super.isCompatible(9)) {
if (isFront) {
cameraId = Camera.CameraInfo.CAMERA_FACING_FRONT;
} else {
cameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
}
}
if (super.isCompatible(11)) {
if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_480P)) {
profile = CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_480P);
if (profile != null) {
Point point = new Point();
point.x = profile.videoFrameWidth;
point.y = profile.videoFrameHeight;
if (isFront) {
frontCameraSize.addLast(point);
} else {
backCameraSize.addLast(point);
}
}
} else {
LogUtil.e(TAG, (isFront ? "Back Camera" : "Front Camera") + " no QUALITY_480P");
}
if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_CIF)) {
profile = CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_CIF);
if (profile != null) {
Point point = new Point();
point.x = profile.videoFrameWidth;
point.y = profile.videoFrameHeight;
if (isFront) {
frontCameraSize.addLast(point);
} else {
backCameraSize.addLast(point);
}
}
} else {
LogUtil.e(TAG, (isFront ? "Back Camera" : "Front Camera") + " no QUALITY_CIF");
}
if (super.isCompatible(15)) {
if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_QVGA)) {
profile = CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_QVGA);
if (profile != null) {
Point point = new Point();
point.x = profile.videoFrameWidth;
point.y = profile.videoFrameHeight;
if (isFront) {
frontCameraSize.addLast(point);
} else {
backCameraSize.addLast(point);
}
}
} else {
LogUtil.e(TAG, (isFront ? "Back Camera" : "Front Camera") + " no QUALITY_QVGA");
}
}
}
if (super.isCompatible(9)) {
profile = CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_LOW);
if (profile == null) {
Point point = new Point();
point.x = 320;
point.y = 240;
if (isFront) {
frontCameraSize.addLast(point);
} else {
backCameraSize.addLast(point);
}
LogUtil.e(TAG, (isFront ? "Back Camera" : "Front Camera") + " no QUALITY_LOW");
} else {
Point point = new Point();
point.x = profile.videoFrameWidth;
point.y = profile.videoFrameHeight;
if (isFront) {
frontCameraSize.addLast(point);
} else {
backCameraSize.addLast(point);
}
}
} else {
if (!isFront) {
profile = CamcorderProfile.get(CamcorderProfile.QUALITY_LOW);
if (profile == null) {
Point point = new Point();
point.x = 320;
point.y = 240;
backCameraSize.addLast(point);
} else {
Point point = new Point();
point.x = profile.videoFrameWidth;
point.y = profile.videoFrameHeight;
backCameraSize.addLast(point);
}
}
}
}
@SuppressLint("NewApi")
private void getVideoPreviewSize() {
backCameraSize.clear();
frontCameraSize.clear();
getVideoPreviewSize(false);
if (Build.VERSION.SDK_INT >= 9) {
if (Camera.getNumberOfCameras() >= 2) {
getVideoPreviewSize(true);
}
}
}
private Point currentUsePoint = null;
private void resizeSurfaceView() {
Point point;
if (cameraId == 0) {
point = backCameraSize.getFirst();
} else {
point = frontCameraSize.getFirst();
}
if (currentUsePoint != null && point.equals(currentUsePoint)) {
return;
} else {
currentUsePoint = point;
int screenWidth = getWindowManager().getDefaultDisplay().getWidth();
int surfaceHeight = screenWidth * point.x / point.y;
if (surfaceview != null) {
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) surfaceview.getLayoutParams();
lp.width = screenWidth;
lp.height = surfaceHeight;
lp.addRule(13);
surfaceview.setLayoutParams(lp);
}
}
}
@SuppressLint("NewApi")
private void setCamcorderProfile() {
CamcorderProfile profile;
if (super.isCompatible(11)) {
if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_CIF)) {
profile = CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_CIF);
} else {
profile = CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_LOW);
}
} else {
if (super.isCompatible(9)) {
profile = CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_LOW);
} else {
profile = CamcorderProfile.get(CamcorderProfile.QUALITY_LOW);
}
}
if (profile != null) {
if (currentUsePoint != null) {
profile.videoFrameWidth = currentUsePoint.x;
profile.videoFrameHeight = currentUsePoint.y;
}
profile.fileFormat = MediaRecorder.OutputFormat.MPEG_4;
if (Build.MODEL.equalsIgnoreCase("MB525") || Build.MODEL.equalsIgnoreCase("C8812") || Build.MODEL.equalsIgnoreCase("C8650")) {
profile.videoCodec = MediaRecorder.VideoEncoder.H263;
} else {
profile.videoCodec = MediaRecorder.VideoEncoder.H264;
}
if (Build.VERSION.SDK_INT < 11) {
profile.videoCodec = MediaRecorder.VideoEncoder.H263;
}
if (Build.VERSION.SDK_INT >= 14) {
profile.audioCodec = MediaRecorder.AudioEncoder.AAC;
} else {
if (Build.DISPLAY != null && Build.DISPLAY.indexOf("MIUI") >= 0) {
profile.audioCodec = MediaRecorder.AudioEncoder.AAC;
} else {
profile.audioCodec = MediaRecorder.AudioEncoder.AMR_NB;
}
}
mediaRecorder.setProfile(profile);
} else {
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
mediaRecorder.setVideoSize(VIDEO_WIDTH, VIDEO_HEIGHT);
}
}
@SuppressLint("NewApi")
private void setVideoOrientation() {
if (Build.VERSION.SDK_INT >= 9) {
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(cameraId, info);
mediaRecorder.setOrientationHint(info.orientation);
}
}
public void updateRecordUI() {
if (recording) {
recordBtn.setBackgroundResource(R.drawable.nim_video_capture_stop_btn);
} else {
recordBtn.setBackgroundResource(R.drawable.nim_video_capture_start_btn);
}
}
private boolean startRecorderInternal() throws Exception {
shutdownCamera();
if (!initCamera())
return false;
switchCamera.setVisibility(View.GONE);
mediaRecorder = new MediaRecorder();
camera.unlock();
mediaRecorder.setCamera(camera);
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
setCamcorderProfile();
mediaRecorder.setPreviewDisplay(surfaceHolder.getSurface());
mediaRecorder.setMaxDuration(1000 * VIDEO_TIMES);
mediaRecorder.setOutputFile(filename);
setVideoOrientation();
mediaRecorder.prepare();
mediaRecorder.start();
return true;
}
private void startRecorder() {
try {
startRecorderInternal();
} catch (Exception e) {
LogUtil.e(TAG, "start MediaRecord failed: " + e);
Toast.makeText(this, R.string.start_camera_to_record_failed, Toast.LENGTH_SHORT).show();
mediaRecorder.release();
mediaRecorder = null;
camera.release();
camera = null;
return;
}
recording = true;
start = new Date().getTime();
handler.postDelayed(runnable, 1000);
recordingTimeTextView.setText("00:00");
updateRecordUI();
}
private void stopRecorder() {
if (mediaRecorder != null) {
try {
mediaRecorder.stop();
} catch (Exception e) {
LogUtil.w(TAG, getString(R.string.stop_fail_maybe_stopped));
}
mediaRecorder.release();
mediaRecorder = null;
}
if (camera != null) {
camera.release();
camera = null;
}
handler.removeCallbacks(runnable);
recordingState.setBackgroundResource(R.drawable.nim_record_start);
recording = false;
updateRecordUI();
}
private void sendVideo() {
File convertedFile = new File(filename);
String message = "";
if (convertedFile.exists()) {
int b = (int) convertedFile.length();
int kb = b / 1024;
float mb = kb / 1024f;
message += mb > 1 ? getString(R.string.capture_video_size_in_mb, mb) : getString(
R.string.capture_video_size_in_kb, kb);
message += getString(R.string.is_send_video);
if (mb <= 1 && kb < 10) {
tooShortAlert();
return;
}
}
EasyAlertDialogHelper.OnDialogActionListener listener = new EasyAlertDialogHelper.OnDialogActionListener() {
@Override
public void doCancelAction() {
cancelRecord();
}
@Override
public void doOkAction() {
Intent intent = new Intent();
intent.putExtra("duration", duration);
setResult(RESULT_OK, intent);
finish();
}
};
final EasyAlertDialog dialog = EasyAlertDialogHelper.createOkCancelDiolag(this, null, message, true, listener);
if (!isFinishing() && !destroyed) {
dialog.show();
}
}
/**
* 视频录制太短
*/
private void tooShortAlert() {
EasyAlertDialogHelper.showOneButtonDiolag(this, null, getString(R.string.video_record_short), getString(R.string.iknow), true, new OnClickListener() {
@Override
public void onClick(View v) {
cancelRecord();
}
});
}
/**
* 取消重录
*/
private void cancelRecord() {
AttachmentStore.delete(filename);
recordingTimeTextView.setText("00:00");
shutdownCamera();
initCamera();
startPreview();
checkMultiCamera();
}
/**
* *************************************************** Camera Start ***************************************************
*/
@SuppressLint("NewApi")
public void checkMultiCamera() {
if (Build.VERSION.SDK_INT >= 9) {
if (Camera.getNumberOfCameras() > 1) {
multiCamera = true;
switchCamera.setVisibility(View.VISIBLE);
} else {
switchCamera.setVisibility(View.GONE);
}
} else {
switchCamera.setVisibility(View.GONE);
}
}
@SuppressLint("NewApi")
private boolean initCamera() {
try {
if (multiCamera) {
camera = Camera.open(cameraId);
} else {
camera = Camera.open();
}
} catch (RuntimeException e) {
LogUtil.e(TAG, "init camera failed: " + e);
Toast.makeText(this, R.string.connect_vedio_device_fail, Toast.LENGTH_SHORT).show();
return false;
}
if (camera != null) {
setCameraParameters();
}
return camera != null;
}
@SuppressLint("NewApi")
private void setCameraParameters() {
Camera.Parameters params = camera.getParameters();
if (Build.VERSION.SDK_INT >= 15) {
if (params.isVideoStabilizationSupported()) {
params.setVideoStabilization(true);
}
}
List<String> focusMode = params.getSupportedFocusModes();
if (focusMode.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
}
if (params != null) {
mAngle = setCameraDisplayOrientation(this, cameraId, camera);
Log.i(TAG, "camera angle = " + mAngle);
}
params.setPreviewSize(currentUsePoint.x, currentUsePoint.y);
try {
camera.setParameters(params);
} catch (RuntimeException e) {
LogUtil.e(TAG, "setParameters failed", e);
}
}
private void shutdownCamera() {
if (camera != null) {
if (previewing) {
camera.stopPreview();
}
camera.release();
camera = null;
previewing = false;
}
}
/**
* **************************** SurfaceHolder.Callback Start *******************************
*/
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
surfaceHolder = holder;
}
public void surfaceCreated(SurfaceHolder holder) {
surfaceHolder = holder;
shutdownCamera();
if (!initCamera())
return;
startPreview();
}
public void surfaceDestroyed(SurfaceHolder holder) {
surfaceHolder = null;
mediaRecorder = null;
}
/**
* ************************ SurfaceHolder.Callback Start ********************************
*/
private void startPreview() {
try {
camera.setPreviewDisplay(surfaceHolder);
camera.startPreview();
previewing = true;
} catch (Exception e) {
Toast.makeText(this, R.string.connect_vedio_device_fail, Toast.LENGTH_SHORT).show();
shutdownCamera();
e.printStackTrace();
}
}
/**
* ********************************* camera util ************************************
*/
@SuppressLint("NewApi")
public int setCameraDisplayOrientation(Context context, int cameraId, Camera camera) {
int orientation = 90;
boolean front = (cameraId == 1);
if (Build.VERSION.SDK_INT >= 9) {
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(cameraId, info);
orientation = info.orientation;
front = (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT);
}
WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
int rotation = manager.getDefaultDisplay().getRotation();
int activityOrientation = roundRotation(rotation);
int result;
if (front) {
result = (orientation + activityOrientation) % 360;
result = (360 - result) % 360; // compensate the mirror
} else { // back-facing
result = (orientation - activityOrientation + 360) % 360;
//遇到过一个小米1s后置摄像头旋转180°,但是不确定是不是所有小米1s都是这样的. 先做一个适配,以后有问题再说.
if ("Xiaomi_MI-ONE Plus".equalsIgnoreCase(Build.MANUFACTURER + "_" + Build.MODEL)) {
result = 90;
}
}
camera.setDisplayOrientation(result);
return result;
}
private int roundRotation(int rotation) {
switch (rotation) {
case Surface.ROTATION_0:
return 0;
case Surface.ROTATION_90:
return 90;
case Surface.ROTATION_180:
return 180;
case Surface.ROTATION_270:
return 270;
}
return 0;
}
}