mirror of
https://github.com/SimpleMobileTools/Simple-Camera.git
synced 2025-02-18 12:20:36 +01:00
convert Preview to kotlin
This commit is contained in:
parent
9576aa0f51
commit
1095ed56e5
@ -1,666 +0,0 @@
|
|||||||
package com.simplemobiletools.camera;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.Point;
|
|
||||||
import android.graphics.Rect;
|
|
||||||
import android.hardware.Camera;
|
|
||||||
import android.media.AudioManager;
|
|
||||||
import android.media.MediaPlayer;
|
|
||||||
import android.media.MediaRecorder;
|
|
||||||
import android.media.MediaScannerConnection;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Environment;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.ParcelFileDescriptor;
|
|
||||||
import android.support.v4.provider.DocumentFile;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.ScaleGestureDetector;
|
|
||||||
import android.view.SurfaceHolder;
|
|
||||||
import android.view.SurfaceView;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
|
|
||||||
import com.simplemobiletools.camera.activities.MainActivity;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class Preview extends ViewGroup
|
|
||||||
implements SurfaceHolder.Callback, View.OnTouchListener, View.OnClickListener, MediaScannerConnection.OnScanCompletedListener {
|
|
||||||
public static final int PHOTO_PREVIEW_LENGTH = 1000;
|
|
||||||
private static final String TAG = Preview.class.getSimpleName();
|
|
||||||
private static final int FOCUS_AREA_SIZE = 100;
|
|
||||||
|
|
||||||
private static SurfaceHolder mSurfaceHolder;
|
|
||||||
private static Camera mCamera;
|
|
||||||
private static List<Camera.Size> mSupportedPreviewSizes;
|
|
||||||
private static SurfaceView mSurfaceView;
|
|
||||||
private static Camera.Size mPreviewSize;
|
|
||||||
private static MainActivity mActivity;
|
|
||||||
private static Camera.Parameters mParameters;
|
|
||||||
private static PreviewListener mCallback;
|
|
||||||
private static MediaRecorder mRecorder;
|
|
||||||
private static String mCurrVideoPath;
|
|
||||||
private static Point mScreenSize;
|
|
||||||
private static Uri mTargetUri;
|
|
||||||
private static Context mContext;
|
|
||||||
private static ScaleGestureDetector mScaleGestureDetector;
|
|
||||||
private static List<Integer> mZoomRatios;
|
|
||||||
private static Config mConfig;
|
|
||||||
|
|
||||||
private static boolean mCanTakePicture;
|
|
||||||
private static boolean mIsFlashEnabled;
|
|
||||||
private static boolean mIsRecording;
|
|
||||||
private static boolean mIsVideoMode;
|
|
||||||
private static boolean mIsSurfaceCreated;
|
|
||||||
private static boolean mSwitchToVideoAsap;
|
|
||||||
private static boolean mSetupPreviewAfterMeasure;
|
|
||||||
private static boolean mForceAspectRatio;
|
|
||||||
private static boolean mWasZooming;
|
|
||||||
private static int mLastClickX;
|
|
||||||
private static int mLastClickY;
|
|
||||||
private static int mInitVideoRotation;
|
|
||||||
private static int mCurrCameraId;
|
|
||||||
private static int mMaxZoom;
|
|
||||||
|
|
||||||
public Preview(Context context) {
|
|
||||||
super(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Preview(MainActivity activity, SurfaceView surfaceView, PreviewListener previewListener) {
|
|
||||||
super(activity);
|
|
||||||
|
|
||||||
mActivity = activity;
|
|
||||||
mCallback = previewListener;
|
|
||||||
mSurfaceView = surfaceView;
|
|
||||||
mSurfaceHolder = mSurfaceView.getHolder();
|
|
||||||
mSurfaceHolder.addCallback(this);
|
|
||||||
mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
|
|
||||||
mCanTakePicture = false;
|
|
||||||
mSurfaceView.setOnTouchListener(this);
|
|
||||||
mSurfaceView.setOnClickListener(this);
|
|
||||||
mIsFlashEnabled = false;
|
|
||||||
mIsVideoMode = false;
|
|
||||||
mIsSurfaceCreated = false;
|
|
||||||
mSetupPreviewAfterMeasure = false;
|
|
||||||
mCurrVideoPath = "";
|
|
||||||
mScreenSize = Utils.Companion.getScreenSize(mActivity);
|
|
||||||
mContext = getContext();
|
|
||||||
initGestureDetector();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void trySwitchToVideo() {
|
|
||||||
if (mIsSurfaceCreated) {
|
|
||||||
initRecorder();
|
|
||||||
} else {
|
|
||||||
mSwitchToVideoAsap = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean setCamera(int cameraId) {
|
|
||||||
mCurrCameraId = cameraId;
|
|
||||||
Camera newCamera;
|
|
||||||
try {
|
|
||||||
newCamera = Camera.open(cameraId);
|
|
||||||
mCallback.setIsCameraAvailable(true);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Utils.Companion.showToast(mContext, R.string.camera_open_error);
|
|
||||||
Log.e(TAG, "setCamera open " + e.getMessage());
|
|
||||||
mCallback.setIsCameraAvailable(false);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mCamera == newCamera) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
releaseCamera();
|
|
||||||
mCamera = newCamera;
|
|
||||||
if (mCamera != null) {
|
|
||||||
mParameters = mCamera.getParameters();
|
|
||||||
mMaxZoom = mParameters.getMaxZoom();
|
|
||||||
mZoomRatios = mParameters.getZoomRatios();
|
|
||||||
mSupportedPreviewSizes = mParameters.getSupportedPreviewSizes();
|
|
||||||
Collections.sort(mSupportedPreviewSizes, new SizesComparator());
|
|
||||||
requestLayout();
|
|
||||||
invalidate();
|
|
||||||
mSetupPreviewAfterMeasure = true;
|
|
||||||
|
|
||||||
final List<String> focusModes = mParameters.getSupportedFocusModes();
|
|
||||||
if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE))
|
|
||||||
mParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
|
|
||||||
|
|
||||||
final int rotation = Utils.Companion.getPreviewRotation(mActivity, cameraId);
|
|
||||||
mCamera.setDisplayOrientation(rotation);
|
|
||||||
mCamera.setParameters(mParameters);
|
|
||||||
|
|
||||||
if (mCanTakePicture) {
|
|
||||||
try {
|
|
||||||
mCamera.setPreviewDisplay(mSurfaceHolder);
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(TAG, "setCamera setPreviewDisplay " + e.getMessage());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mCallback.setFlashAvailable(Utils.Companion.hasFlash(mCamera));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mIsVideoMode) {
|
|
||||||
initRecorder();
|
|
||||||
}
|
|
||||||
|
|
||||||
mConfig = Config.Companion.newInstance(mContext);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTargetUri(Uri uri) {
|
|
||||||
mTargetUri = uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initGestureDetector() {
|
|
||||||
mScaleGestureDetector = new ScaleGestureDetector(mContext, new ScaleGestureDetector.SimpleOnScaleGestureListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onScale(ScaleGestureDetector detector) {
|
|
||||||
int zoomFactor = mParameters.getZoom();
|
|
||||||
float zoomRatio = mZoomRatios.get(zoomFactor) / 100.f;
|
|
||||||
zoomRatio *= detector.getScaleFactor();
|
|
||||||
|
|
||||||
int newZoomFactor = zoomFactor;
|
|
||||||
if (zoomRatio <= 1.f) {
|
|
||||||
newZoomFactor = 0;
|
|
||||||
} else if (zoomRatio >= mZoomRatios.get(mMaxZoom) / 100.f) {
|
|
||||||
newZoomFactor = mMaxZoom;
|
|
||||||
} else {
|
|
||||||
if (detector.getScaleFactor() > 1.f) {
|
|
||||||
for (int i = zoomFactor; i < mZoomRatios.size(); i++) {
|
|
||||||
if (mZoomRatios.get(i) / 100.0f >= zoomRatio) {
|
|
||||||
newZoomFactor = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (int i = zoomFactor; i >= 0; i--) {
|
|
||||||
if (mZoomRatios.get(i) / 100.0f <= zoomRatio) {
|
|
||||||
newZoomFactor = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
newZoomFactor = Math.max(newZoomFactor, 0);
|
|
||||||
newZoomFactor = Math.min(mMaxZoom, newZoomFactor);
|
|
||||||
|
|
||||||
mParameters.setZoom(newZoomFactor);
|
|
||||||
if (mCamera != null)
|
|
||||||
mCamera.setParameters(mParameters);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onScaleEnd(ScaleGestureDetector detector) {
|
|
||||||
super.onScaleEnd(detector);
|
|
||||||
mWasZooming = true;
|
|
||||||
mSurfaceView.setSoundEffectsEnabled(false);
|
|
||||||
mParameters.setFocusAreas(null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void takePicture() {
|
|
||||||
if (mCanTakePicture) {
|
|
||||||
int rotation = Utils.Companion.getMediaRotation(mActivity, mCurrCameraId);
|
|
||||||
rotation += Utils.Companion.compensateDeviceRotation(mCurrCameraId, mCallback.getCurrentOrientation());
|
|
||||||
|
|
||||||
/*final Camera.Size maxSize = getOptimalPictureSize();
|
|
||||||
mParameters.setPictureSize(maxSize.width, maxSize.height);*/
|
|
||||||
mParameters.setRotation(rotation % 360);
|
|
||||||
|
|
||||||
if (mConfig.isSoundEnabled()) {
|
|
||||||
final AudioManager audioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
|
|
||||||
final int volume = audioManager.getStreamVolume(AudioManager.STREAM_SYSTEM);
|
|
||||||
if (volume != 0) {
|
|
||||||
final MediaPlayer mp = MediaPlayer.create(getContext(), Uri.parse("file:///system/media/audio/ui/camera_click.ogg"));
|
|
||||||
if (mp != null)
|
|
||||||
mp.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
|
||||||
mCamera.enableShutterSound(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
mCamera.setParameters(mParameters);
|
|
||||||
mCamera.takePicture(null, null, takePictureCallback);
|
|
||||||
}
|
|
||||||
mCanTakePicture = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Camera.PictureCallback takePictureCallback = new Camera.PictureCallback() {
|
|
||||||
@Override
|
|
||||||
public void onPictureTaken(byte[] data, Camera cam) {
|
|
||||||
if (mConfig.isShowPreviewEnabled()) {
|
|
||||||
new Handler().postDelayed(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
resumePreview();
|
|
||||||
}
|
|
||||||
}, PHOTO_PREVIEW_LENGTH);
|
|
||||||
} else {
|
|
||||||
resumePreview();
|
|
||||||
}
|
|
||||||
|
|
||||||
new PhotoProcessor(mActivity, mTargetUri).execute(data);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private void resumePreview() {
|
|
||||||
if (mCamera != null) {
|
|
||||||
mCamera.startPreview();
|
|
||||||
}
|
|
||||||
|
|
||||||
mCanTakePicture = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Camera.Size> getSupportedVideoSizes() {
|
|
||||||
if (mParameters.getSupportedVideoSizes() != null) {
|
|
||||||
return mParameters.getSupportedVideoSizes();
|
|
||||||
} else {
|
|
||||||
return mParameters.getSupportedPreviewSizes();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void focusArea(final boolean takePictureAfter) {
|
|
||||||
if (mCamera == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
mCamera.cancelAutoFocus();
|
|
||||||
final Rect focusRect = calculateFocusArea(mLastClickX, mLastClickY);
|
|
||||||
if (mParameters.getMaxNumFocusAreas() > 0) {
|
|
||||||
final List<Camera.Area> focusAreas = new ArrayList<>(1);
|
|
||||||
focusAreas.add(new Camera.Area(focusRect, 1000));
|
|
||||||
mParameters.setFocusAreas(focusAreas);
|
|
||||||
mCallback.drawFocusRect(mLastClickX, mLastClickY);
|
|
||||||
}
|
|
||||||
|
|
||||||
mCamera.setParameters(mParameters);
|
|
||||||
mCamera.autoFocus(new Camera.AutoFocusCallback() {
|
|
||||||
@Override
|
|
||||||
public void onAutoFocus(boolean success, Camera camera) {
|
|
||||||
camera.cancelAutoFocus();
|
|
||||||
final List<String> focusModes = mParameters.getSupportedFocusModes();
|
|
||||||
if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE))
|
|
||||||
mParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
|
|
||||||
|
|
||||||
camera.setParameters(mParameters);
|
|
||||||
if (takePictureAfter) {
|
|
||||||
takePicture();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private Rect calculateFocusArea(float x, float y) {
|
|
||||||
int left = Float.valueOf((x / mSurfaceView.getWidth()) * 2000 - 1000).intValue();
|
|
||||||
int top = Float.valueOf((y / mSurfaceView.getHeight()) * 2000 - 1000).intValue();
|
|
||||||
|
|
||||||
int tmp = left;
|
|
||||||
left = top;
|
|
||||||
top = -tmp;
|
|
||||||
|
|
||||||
final int rectLeft = Math.max(left - FOCUS_AREA_SIZE / 2, -1000);
|
|
||||||
final int rectTop = Math.max(top - FOCUS_AREA_SIZE / 2, -1000);
|
|
||||||
final int rectRight = Math.min(left + FOCUS_AREA_SIZE / 2, 1000);
|
|
||||||
final int rectBottom = Math.min(top + FOCUS_AREA_SIZE / 2, 1000);
|
|
||||||
return new Rect(rectLeft, rectTop, rectRight, rectBottom);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void releaseCamera() {
|
|
||||||
stopRecording();
|
|
||||||
|
|
||||||
if (mCamera != null) {
|
|
||||||
mCamera.stopPreview();
|
|
||||||
mCamera.release();
|
|
||||||
mCamera = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanupRecorder();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void surfaceCreated(SurfaceHolder holder) {
|
|
||||||
mIsSurfaceCreated = true;
|
|
||||||
try {
|
|
||||||
if (mCamera != null) {
|
|
||||||
mCamera.setPreviewDisplay(mSurfaceHolder);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mSwitchToVideoAsap)
|
|
||||||
initRecorder();
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(TAG, "surfaceCreated IOException " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
|
||||||
mIsSurfaceCreated = true;
|
|
||||||
|
|
||||||
if (mIsVideoMode) {
|
|
||||||
initRecorder();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
|
||||||
mIsSurfaceCreated = false;
|
|
||||||
if (mCamera != null) {
|
|
||||||
mCamera.stopPreview();
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanupRecorder();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupPreview() {
|
|
||||||
mCanTakePicture = true;
|
|
||||||
if (mCamera != null && mPreviewSize != null) {
|
|
||||||
mParameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
|
|
||||||
mCamera.setParameters(mParameters);
|
|
||||||
mCamera.startPreview();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void cleanupRecorder() {
|
|
||||||
if (mRecorder != null) {
|
|
||||||
if (mIsRecording) {
|
|
||||||
stopRecording();
|
|
||||||
}
|
|
||||||
|
|
||||||
mRecorder.release();
|
|
||||||
mRecorder = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int width, int height) {
|
|
||||||
Camera.Size result = null;
|
|
||||||
for (Camera.Size size : sizes) {
|
|
||||||
if (size.width <= width && size.height <= height) {
|
|
||||||
if (result == null) {
|
|
||||||
result = size;
|
|
||||||
} else {
|
|
||||||
int resultArea = result.width * result.height;
|
|
||||||
int newArea = size.width * size.height;
|
|
||||||
|
|
||||||
if (newArea > resultArea) {
|
|
||||||
result = size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
|
||||||
setMeasuredDimension(mScreenSize.x, mScreenSize.y);
|
|
||||||
|
|
||||||
if (mSupportedPreviewSizes != null) {
|
|
||||||
// for simplicity lets assume that most displays are 16:9 and the remaining ones are 4:3
|
|
||||||
// always set 16:9 for videos as many devices support 4:3 only in low quality
|
|
||||||
if (mForceAspectRatio || mIsVideoMode) {
|
|
||||||
mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, mScreenSize.y, mScreenSize.x);
|
|
||||||
} else {
|
|
||||||
final int newRatioHeight = (int) (mScreenSize.x * ((double) 4 / 3));
|
|
||||||
setMeasuredDimension(mScreenSize.x, newRatioHeight);
|
|
||||||
mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, newRatioHeight, mScreenSize.x);
|
|
||||||
}
|
|
||||||
final LayoutParams lp = mSurfaceView.getLayoutParams();
|
|
||||||
|
|
||||||
// make sure to occupy whole width in every case
|
|
||||||
if (mScreenSize.x > mPreviewSize.height) {
|
|
||||||
final float ratio = (float) mScreenSize.x / mPreviewSize.height;
|
|
||||||
lp.width = (int) (mPreviewSize.height * ratio);
|
|
||||||
if (mForceAspectRatio || mIsVideoMode) {
|
|
||||||
lp.height = mScreenSize.y;
|
|
||||||
} else {
|
|
||||||
lp.height = (int) (mPreviewSize.width * ratio);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
lp.width = mPreviewSize.height;
|
|
||||||
lp.height = mPreviewSize.width;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mSetupPreviewAfterMeasure) {
|
|
||||||
mSetupPreviewAfterMeasure = false;
|
|
||||||
if (mCamera != null)
|
|
||||||
mCamera.stopPreview();
|
|
||||||
|
|
||||||
setupPreview();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onTouch(View v, MotionEvent event) {
|
|
||||||
mLastClickX = (int) event.getX();
|
|
||||||
mLastClickY = (int) event.getY();
|
|
||||||
|
|
||||||
if (mMaxZoom > 0)
|
|
||||||
mScaleGestureDetector.onTouchEvent(event);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void enableFlash() {
|
|
||||||
mParameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
|
|
||||||
mCamera.setParameters(mParameters);
|
|
||||||
mIsFlashEnabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void disableFlash() {
|
|
||||||
mIsFlashEnabled = false;
|
|
||||||
mParameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
|
|
||||||
mCamera.setParameters(mParameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void initPhotoMode() {
|
|
||||||
stopRecording();
|
|
||||||
cleanupRecorder();
|
|
||||||
mIsRecording = false;
|
|
||||||
mIsVideoMode = false;
|
|
||||||
recheckAspectRatio();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void recheckAspectRatio() {
|
|
||||||
if (!mForceAspectRatio) {
|
|
||||||
mSetupPreviewAfterMeasure = true;
|
|
||||||
invalidate();
|
|
||||||
requestLayout();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// VIDEO RECORDING
|
|
||||||
public boolean initRecorder() {
|
|
||||||
if (mCamera == null || mRecorder != null || !mIsSurfaceCreated)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
mSwitchToVideoAsap = false;
|
|
||||||
final List<Camera.Size> previewSizes = mParameters.getSupportedPreviewSizes();
|
|
||||||
Collections.sort(previewSizes, new SizesComparator());
|
|
||||||
Camera.Size preferred = previewSizes.get(0);
|
|
||||||
|
|
||||||
mParameters.setPreviewSize(preferred.width, preferred.height);
|
|
||||||
mCamera.setParameters(mParameters);
|
|
||||||
|
|
||||||
mIsRecording = false;
|
|
||||||
mIsVideoMode = true;
|
|
||||||
recheckAspectRatio();
|
|
||||||
mRecorder = new MediaRecorder();
|
|
||||||
mRecorder.setCamera(mCamera);
|
|
||||||
mRecorder.setVideoSource(MediaRecorder.VideoSource.DEFAULT);
|
|
||||||
mRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
|
|
||||||
|
|
||||||
mCurrVideoPath = Utils.Companion.getOutputMediaFile(mContext, false);
|
|
||||||
if (mCurrVideoPath.isEmpty()) {
|
|
||||||
Utils.Companion.showToast(mContext, R.string.video_creating_error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*final Camera.Size videoSize = getOptimalVideoSize();
|
|
||||||
final CamcorderProfile cpHigh = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH);
|
|
||||||
cpHigh.videoFrameWidth = videoSize.width;
|
|
||||||
cpHigh.videoFrameHeight = videoSize.height;
|
|
||||||
mRecorder.setProfile(cpHigh);*/
|
|
||||||
|
|
||||||
if (Utils.Companion.needsStupidWritePermissions(getContext(), mCurrVideoPath)) {
|
|
||||||
if (mConfig.getTreeUri().isEmpty()) {
|
|
||||||
Utils.Companion.showToast(mContext, R.string.save_error_internal_storage);
|
|
||||||
mConfig.setSavePhotosFolder(Environment.getExternalStorageDirectory().toString());
|
|
||||||
releaseCamera();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
DocumentFile document = Utils.Companion.getFileDocument(getContext(), mCurrVideoPath, mConfig.getTreeUri());
|
|
||||||
document = document.createFile("", mCurrVideoPath.substring(mCurrVideoPath.lastIndexOf('/') + 1));
|
|
||||||
final Uri uri = document.getUri();
|
|
||||||
final ParcelFileDescriptor fileDescriptor = getContext().getContentResolver().openFileDescriptor(uri, "rw");
|
|
||||||
mRecorder.setOutputFile(fileDescriptor.getFileDescriptor());
|
|
||||||
} catch (Exception e) {
|
|
||||||
setupFailed(e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mRecorder.setOutputFile(mCurrVideoPath);
|
|
||||||
}
|
|
||||||
mRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
|
|
||||||
|
|
||||||
int rotation = Utils.Companion.getFinalRotation(mActivity, mCurrCameraId, mCallback.getCurrentOrientation());
|
|
||||||
mInitVideoRotation = rotation;
|
|
||||||
mRecorder.setOrientationHint(rotation);
|
|
||||||
|
|
||||||
try {
|
|
||||||
mRecorder.prepare();
|
|
||||||
} catch (Exception e) {
|
|
||||||
setupFailed(e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupFailed(Exception e) {
|
|
||||||
Utils.Companion.showToast(mContext, R.string.video_setup_error);
|
|
||||||
Log.e(TAG, "initRecorder " + e.getMessage());
|
|
||||||
releaseCamera();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean toggleRecording() {
|
|
||||||
if (mIsRecording) {
|
|
||||||
stopRecording();
|
|
||||||
initRecorder();
|
|
||||||
} else {
|
|
||||||
startRecording();
|
|
||||||
}
|
|
||||||
return mIsRecording;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startRecording() {
|
|
||||||
if (mInitVideoRotation != Utils.Companion.getFinalRotation(mActivity, mCurrCameraId, mCallback.getCurrentOrientation())) {
|
|
||||||
cleanupRecorder();
|
|
||||||
initRecorder();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
mCamera.unlock();
|
|
||||||
toggleShutterSound(true);
|
|
||||||
mRecorder.start();
|
|
||||||
toggleShutterSound(false);
|
|
||||||
mIsRecording = true;
|
|
||||||
} catch (Exception e) {
|
|
||||||
Utils.Companion.showToast(mContext, R.string.video_setup_error);
|
|
||||||
Log.e(TAG, "toggleRecording " + e.getMessage());
|
|
||||||
releaseCamera();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void stopRecording() {
|
|
||||||
if (mRecorder != null && mIsRecording) {
|
|
||||||
try {
|
|
||||||
toggleShutterSound(true);
|
|
||||||
mRecorder.stop();
|
|
||||||
final String[] paths = {mCurrVideoPath};
|
|
||||||
MediaScannerConnection.scanFile(mContext, paths, null, this);
|
|
||||||
} catch (RuntimeException e) {
|
|
||||||
toggleShutterSound(false);
|
|
||||||
new File(mCurrVideoPath).delete();
|
|
||||||
Utils.Companion.showToast(mContext, R.string.video_saving_error);
|
|
||||||
Log.e(TAG, "stopRecording " + e.getMessage());
|
|
||||||
mRecorder = null;
|
|
||||||
mIsRecording = false;
|
|
||||||
releaseCamera();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mRecorder = null;
|
|
||||||
mIsRecording = false;
|
|
||||||
|
|
||||||
final File file = new File(mCurrVideoPath);
|
|
||||||
if (file.exists() && file.length() == 0) {
|
|
||||||
file.delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void toggleShutterSound(Boolean mute) {
|
|
||||||
if (!mConfig.isSoundEnabled()) {
|
|
||||||
((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE)).setStreamMute(AudioManager.STREAM_SYSTEM, mute);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
if (!mWasZooming)
|
|
||||||
focusArea(false);
|
|
||||||
|
|
||||||
mWasZooming = false;
|
|
||||||
mSurfaceView.setSoundEffectsEnabled(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onScanCompleted(String path, Uri uri) {
|
|
||||||
mCallback.videoSaved(uri);
|
|
||||||
toggleShutterSound(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class SizesComparator implements Comparator<Camera.Size>, Serializable {
|
|
||||||
private static final long serialVersionUID = 5431278455314658485L;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compare(final Camera.Size a, final Camera.Size b) {
|
|
||||||
return b.width * b.height - a.width * a.height;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface PreviewListener {
|
|
||||||
void setFlashAvailable(boolean available);
|
|
||||||
|
|
||||||
void setIsCameraAvailable(boolean available);
|
|
||||||
|
|
||||||
int getCurrentOrientation();
|
|
||||||
|
|
||||||
void videoSaved(Uri uri);
|
|
||||||
|
|
||||||
void drawFocusRect(int x, int y);
|
|
||||||
}
|
|
||||||
}
|
|
636
app/src/main/kotlin/com/simplemobiletools/camera/Preview.kt
Normal file
636
app/src/main/kotlin/com/simplemobiletools/camera/Preview.kt
Normal file
@ -0,0 +1,636 @@
|
|||||||
|
package com.simplemobiletools.camera
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Point
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.hardware.Camera
|
||||||
|
import android.media.AudioManager
|
||||||
|
import android.media.MediaPlayer
|
||||||
|
import android.media.MediaRecorder
|
||||||
|
import android.media.MediaScannerConnection
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Environment
|
||||||
|
import android.os.Handler
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.*
|
||||||
|
import com.simplemobiletools.camera.activities.MainActivity
|
||||||
|
import com.simplemobiletools.commons.extensions.scanPaths
|
||||||
|
import com.simplemobiletools.commons.extensions.toast
|
||||||
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.Serializable
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class Preview : ViewGroup, SurfaceHolder.Callback, View.OnTouchListener, View.OnClickListener, MediaScannerConnection.OnScanCompletedListener {
|
||||||
|
companion object {
|
||||||
|
val PHOTO_PREVIEW_LENGTH = 1000
|
||||||
|
private val TAG = Preview::class.java.simpleName
|
||||||
|
private val FOCUS_AREA_SIZE = 100
|
||||||
|
|
||||||
|
lateinit var mSurfaceHolder: SurfaceHolder
|
||||||
|
lateinit var mSurfaceView: SurfaceView
|
||||||
|
lateinit var mActivity: MainActivity
|
||||||
|
lateinit var mCallback: PreviewListener
|
||||||
|
lateinit var mScreenSize: Point
|
||||||
|
private var mCamera: Camera? = null
|
||||||
|
private var mSupportedPreviewSizes: List<Camera.Size>? = null
|
||||||
|
private var mPreviewSize: Camera.Size? = null
|
||||||
|
private var mParameters: Camera.Parameters? = null
|
||||||
|
private var mRecorder: MediaRecorder? = null
|
||||||
|
private var mCurrVideoPath: String? = null
|
||||||
|
private var mTargetUri: Uri? = null
|
||||||
|
private var mScaleGestureDetector: ScaleGestureDetector? = null
|
||||||
|
private var mZoomRatios: List<Int>? = null
|
||||||
|
private var mConfig: Config? = null
|
||||||
|
|
||||||
|
private var mCanTakePicture: Boolean = false
|
||||||
|
private var mIsFlashEnabled: Boolean = false
|
||||||
|
private var mIsRecording: Boolean = false
|
||||||
|
private var mIsVideoMode: Boolean = false
|
||||||
|
private var mIsSurfaceCreated: Boolean = false
|
||||||
|
private var mSwitchToVideoAsap: Boolean = false
|
||||||
|
private var mSetupPreviewAfterMeasure: Boolean = false
|
||||||
|
private val mForceAspectRatio: Boolean = false
|
||||||
|
private var mWasZooming: Boolean = false
|
||||||
|
private var mLastClickX: Int = 0
|
||||||
|
private var mLastClickY: Int = 0
|
||||||
|
private var mInitVideoRotation: Int = 0
|
||||||
|
private var mCurrCameraId: Int = 0
|
||||||
|
private var mMaxZoom: Int = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(context: Context) : super(context)
|
||||||
|
|
||||||
|
constructor(activity: MainActivity, surfaceView: SurfaceView, previewListener: PreviewListener) : super(activity) {
|
||||||
|
mActivity = activity
|
||||||
|
mCallback = previewListener
|
||||||
|
mSurfaceView = surfaceView
|
||||||
|
mSurfaceHolder = mSurfaceView.holder
|
||||||
|
mSurfaceHolder.addCallback(this)
|
||||||
|
mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS)
|
||||||
|
mCanTakePicture = false
|
||||||
|
mSurfaceView.setOnTouchListener(this)
|
||||||
|
mSurfaceView.setOnClickListener(this)
|
||||||
|
mIsFlashEnabled = false
|
||||||
|
mIsVideoMode = false
|
||||||
|
mIsSurfaceCreated = false
|
||||||
|
mSetupPreviewAfterMeasure = false
|
||||||
|
mCurrVideoPath = ""
|
||||||
|
mScreenSize = Utils.getScreenSize(mActivity)
|
||||||
|
initGestureDetector()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun trySwitchToVideo() {
|
||||||
|
if (mIsSurfaceCreated) {
|
||||||
|
initRecorder()
|
||||||
|
} else {
|
||||||
|
mSwitchToVideoAsap = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setCamera(cameraId: Int): Boolean {
|
||||||
|
mCurrCameraId = cameraId
|
||||||
|
val newCamera: Camera
|
||||||
|
try {
|
||||||
|
newCamera = Camera.open(cameraId)
|
||||||
|
mCallback.setIsCameraAvailable(true)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Utils.showToast(mActivity, R.string.camera_open_error)
|
||||||
|
Log.e(TAG, "setCamera open " + e.message)
|
||||||
|
mCallback.setIsCameraAvailable(false)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mCamera === newCamera) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
releaseCamera()
|
||||||
|
mCamera = newCamera
|
||||||
|
if (mCamera != null) {
|
||||||
|
mParameters = mCamera!!.parameters
|
||||||
|
mMaxZoom = mParameters!!.maxZoom
|
||||||
|
mZoomRatios = mParameters!!.zoomRatios
|
||||||
|
mSupportedPreviewSizes = mParameters!!.supportedPreviewSizes
|
||||||
|
Collections.sort<Camera.Size>(mSupportedPreviewSizes!!, SizesComparator())
|
||||||
|
requestLayout()
|
||||||
|
invalidate()
|
||||||
|
mSetupPreviewAfterMeasure = true
|
||||||
|
|
||||||
|
val focusModes = mParameters!!.supportedFocusModes
|
||||||
|
if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE))
|
||||||
|
mParameters!!.focusMode = Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE
|
||||||
|
|
||||||
|
val rotation = Utils.getPreviewRotation(mActivity, cameraId)
|
||||||
|
mCamera!!.setDisplayOrientation(rotation)
|
||||||
|
mCamera!!.parameters = mParameters
|
||||||
|
|
||||||
|
if (mCanTakePicture) {
|
||||||
|
try {
|
||||||
|
mCamera!!.setPreviewDisplay(mSurfaceHolder)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.e(TAG, "setCamera setPreviewDisplay " + e.message)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
mCallback.setFlashAvailable(Utils.hasFlash(mCamera))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mIsVideoMode) {
|
||||||
|
initRecorder()
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setTargetUri(uri: Uri) {
|
||||||
|
mTargetUri = uri
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initGestureDetector() {
|
||||||
|
mScaleGestureDetector = ScaleGestureDetector(mActivity, object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
|
||||||
|
override fun onScale(detector: ScaleGestureDetector): Boolean {
|
||||||
|
val zoomFactor = mParameters!!.zoom
|
||||||
|
var zoomRatio = mZoomRatios!![zoomFactor] / 100f
|
||||||
|
zoomRatio *= detector.scaleFactor
|
||||||
|
|
||||||
|
var newZoomFactor = zoomFactor
|
||||||
|
if (zoomRatio <= 1f) {
|
||||||
|
newZoomFactor = 0
|
||||||
|
} else if (zoomRatio >= mZoomRatios!![mMaxZoom] / 100f) {
|
||||||
|
newZoomFactor = mMaxZoom
|
||||||
|
} else {
|
||||||
|
if (detector.scaleFactor > 1f) {
|
||||||
|
for (i in zoomFactor..mZoomRatios!!.size - 1) {
|
||||||
|
if (mZoomRatios!![i] / 100.0f >= zoomRatio) {
|
||||||
|
newZoomFactor = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (i in zoomFactor downTo 0) {
|
||||||
|
if (mZoomRatios!![i] / 100.0f <= zoomRatio) {
|
||||||
|
newZoomFactor = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newZoomFactor = Math.max(newZoomFactor, 0)
|
||||||
|
newZoomFactor = Math.min(mMaxZoom, newZoomFactor)
|
||||||
|
|
||||||
|
mParameters!!.zoom = newZoomFactor
|
||||||
|
if (mCamera != null)
|
||||||
|
mCamera!!.parameters = mParameters
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onScaleEnd(detector: ScaleGestureDetector) {
|
||||||
|
super.onScaleEnd(detector)
|
||||||
|
mWasZooming = true
|
||||||
|
mSurfaceView.isSoundEffectsEnabled = false
|
||||||
|
mParameters!!.focusAreas = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun takePicture() {
|
||||||
|
if (mCanTakePicture) {
|
||||||
|
var rotation = Utils.getMediaRotation(mActivity, mCurrCameraId)
|
||||||
|
rotation += Utils.compensateDeviceRotation(mCurrCameraId, mCallback.getCurrentOrientation())
|
||||||
|
|
||||||
|
/*final Camera.Size maxSize = getOptimalPictureSize();
|
||||||
|
mParameters.setPictureSize(maxSize.width, maxSize.height);*/
|
||||||
|
mParameters!!.setRotation(rotation % 360)
|
||||||
|
|
||||||
|
if (mConfig!!.isSoundEnabled) {
|
||||||
|
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||||
|
val volume = audioManager.getStreamVolume(AudioManager.STREAM_SYSTEM)
|
||||||
|
if (volume != 0) {
|
||||||
|
val mp = MediaPlayer.create(context, Uri.parse("file:///system/media/audio/ui/camera_click.ogg"))
|
||||||
|
mp?.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||||
|
mCamera!!.enableShutterSound(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
mCamera!!.parameters = mParameters
|
||||||
|
mCamera!!.takePicture(null, null, takePictureCallback)
|
||||||
|
}
|
||||||
|
mCanTakePicture = false
|
||||||
|
}
|
||||||
|
|
||||||
|
private val takePictureCallback = Camera.PictureCallback { data, cam ->
|
||||||
|
if (mConfig!!.isShowPreviewEnabled) {
|
||||||
|
Handler().postDelayed({ resumePreview() }, PHOTO_PREVIEW_LENGTH.toLong())
|
||||||
|
} else {
|
||||||
|
resumePreview()
|
||||||
|
}
|
||||||
|
|
||||||
|
PhotoProcessor(mActivity, mTargetUri).execute(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resumePreview() {
|
||||||
|
if (mCamera != null) {
|
||||||
|
mCamera!!.startPreview()
|
||||||
|
}
|
||||||
|
|
||||||
|
mCanTakePicture = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val supportedVideoSizes: List<Camera.Size>
|
||||||
|
get() {
|
||||||
|
if (mParameters!!.supportedVideoSizes != null) {
|
||||||
|
return mParameters!!.supportedVideoSizes
|
||||||
|
} else {
|
||||||
|
return mParameters!!.supportedPreviewSizes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun focusArea(takePictureAfter: Boolean) {
|
||||||
|
if (mCamera == null)
|
||||||
|
return
|
||||||
|
|
||||||
|
mCamera!!.cancelAutoFocus()
|
||||||
|
val focusRect = calculateFocusArea(mLastClickX.toFloat(), mLastClickY.toFloat())
|
||||||
|
if (mParameters!!.maxNumFocusAreas > 0) {
|
||||||
|
val focusAreas = ArrayList<Camera.Area>(1)
|
||||||
|
focusAreas.add(Camera.Area(focusRect, 1000))
|
||||||
|
mParameters!!.focusAreas = focusAreas
|
||||||
|
mCallback.drawFocusRect(mLastClickX, mLastClickY)
|
||||||
|
}
|
||||||
|
|
||||||
|
mCamera!!.parameters = mParameters
|
||||||
|
mCamera!!.autoFocus { success, camera ->
|
||||||
|
camera.cancelAutoFocus()
|
||||||
|
val focusModes = mParameters!!.supportedFocusModes
|
||||||
|
if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE))
|
||||||
|
mParameters!!.focusMode = Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE
|
||||||
|
|
||||||
|
camera.parameters = mParameters
|
||||||
|
if (takePictureAfter) {
|
||||||
|
takePicture()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun calculateFocusArea(x: Float, y: Float): Rect {
|
||||||
|
var left = java.lang.Float.valueOf(x / mSurfaceView.width * 2000 - 1000)!!.toInt()
|
||||||
|
var top = java.lang.Float.valueOf(y / mSurfaceView.height * 2000 - 1000)!!.toInt()
|
||||||
|
|
||||||
|
val tmp = left
|
||||||
|
left = top
|
||||||
|
top = -tmp
|
||||||
|
|
||||||
|
val rectLeft = Math.max(left - FOCUS_AREA_SIZE / 2, -1000)
|
||||||
|
val rectTop = Math.max(top - FOCUS_AREA_SIZE / 2, -1000)
|
||||||
|
val rectRight = Math.min(left + FOCUS_AREA_SIZE / 2, 1000)
|
||||||
|
val rectBottom = Math.min(top + FOCUS_AREA_SIZE / 2, 1000)
|
||||||
|
return Rect(rectLeft, rectTop, rectRight, rectBottom)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun releaseCamera() {
|
||||||
|
stopRecording()
|
||||||
|
|
||||||
|
if (mCamera != null) {
|
||||||
|
mCamera!!.stopPreview()
|
||||||
|
mCamera!!.release()
|
||||||
|
mCamera = null
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanupRecorder()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun surfaceCreated(holder: SurfaceHolder) {
|
||||||
|
mIsSurfaceCreated = true
|
||||||
|
try {
|
||||||
|
if (mCamera != null) {
|
||||||
|
mCamera!!.setPreviewDisplay(mSurfaceHolder)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mSwitchToVideoAsap)
|
||||||
|
initRecorder()
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.e(TAG, "surfaceCreated IOException " + e.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
|
||||||
|
mIsSurfaceCreated = true
|
||||||
|
|
||||||
|
if (mIsVideoMode) {
|
||||||
|
initRecorder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun surfaceDestroyed(holder: SurfaceHolder) {
|
||||||
|
mIsSurfaceCreated = false
|
||||||
|
if (mCamera != null) {
|
||||||
|
mCamera!!.stopPreview()
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanupRecorder()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupPreview() {
|
||||||
|
mCanTakePicture = true
|
||||||
|
if (mCamera != null && mPreviewSize != null) {
|
||||||
|
mParameters!!.setPreviewSize(mPreviewSize!!.width, mPreviewSize!!.height)
|
||||||
|
mCamera!!.parameters = mParameters
|
||||||
|
mCamera!!.startPreview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun cleanupRecorder() {
|
||||||
|
if (mRecorder != null) {
|
||||||
|
if (mIsRecording) {
|
||||||
|
stopRecording()
|
||||||
|
}
|
||||||
|
|
||||||
|
mRecorder!!.release()
|
||||||
|
mRecorder = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getOptimalPreviewSize(sizes: List<Camera.Size>, width: Int, height: Int): Camera.Size {
|
||||||
|
var result: Camera.Size? = null
|
||||||
|
for (size in sizes) {
|
||||||
|
if (size.width <= width && size.height <= height) {
|
||||||
|
if (result == null) {
|
||||||
|
result = size
|
||||||
|
} else {
|
||||||
|
val resultArea = result.width * result.height
|
||||||
|
val newArea = size.width * size.height
|
||||||
|
|
||||||
|
if (newArea > resultArea) {
|
||||||
|
result = size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result!!
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||||
|
setMeasuredDimension(mScreenSize.x, mScreenSize.y)
|
||||||
|
|
||||||
|
if (mSupportedPreviewSizes != null) {
|
||||||
|
// for simplicity lets assume that most displays are 16:9 and the remaining ones are 4:3
|
||||||
|
// always set 16:9 for videos as many devices support 4:3 only in low quality
|
||||||
|
if (mForceAspectRatio || mIsVideoMode) {
|
||||||
|
mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes!!, mScreenSize.y, mScreenSize.x)
|
||||||
|
} else {
|
||||||
|
val newRatioHeight = (mScreenSize.x * (4.toDouble() / 3)).toInt()
|
||||||
|
setMeasuredDimension(mScreenSize.x, newRatioHeight)
|
||||||
|
mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes!!, newRatioHeight, mScreenSize.x)
|
||||||
|
}
|
||||||
|
val lp = mSurfaceView.layoutParams
|
||||||
|
|
||||||
|
// make sure to occupy whole width in every case
|
||||||
|
if (mScreenSize.x > mPreviewSize!!.height) {
|
||||||
|
val ratio = mScreenSize.x.toFloat() / mPreviewSize!!.height
|
||||||
|
lp.width = (mPreviewSize!!.height * ratio).toInt()
|
||||||
|
if (mForceAspectRatio || mIsVideoMode) {
|
||||||
|
lp.height = mScreenSize.y
|
||||||
|
} else {
|
||||||
|
lp.height = (mPreviewSize!!.width * ratio).toInt()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lp.width = mPreviewSize!!.height
|
||||||
|
lp.height = mPreviewSize!!.width
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mSetupPreviewAfterMeasure) {
|
||||||
|
mSetupPreviewAfterMeasure = false
|
||||||
|
if (mCamera != null)
|
||||||
|
mCamera!!.stopPreview()
|
||||||
|
|
||||||
|
setupPreview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTouch(v: View, event: MotionEvent): Boolean {
|
||||||
|
mLastClickX = event.x.toInt()
|
||||||
|
mLastClickY = event.y.toInt()
|
||||||
|
|
||||||
|
if (mMaxZoom > 0)
|
||||||
|
mScaleGestureDetector!!.onTouchEvent(event)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun enableFlash() {
|
||||||
|
mParameters!!.flashMode = Camera.Parameters.FLASH_MODE_TORCH
|
||||||
|
mCamera!!.parameters = mParameters
|
||||||
|
mIsFlashEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun disableFlash() {
|
||||||
|
mIsFlashEnabled = false
|
||||||
|
mParameters!!.flashMode = Camera.Parameters.FLASH_MODE_OFF
|
||||||
|
mCamera!!.parameters = mParameters
|
||||||
|
}
|
||||||
|
|
||||||
|
fun initPhotoMode() {
|
||||||
|
stopRecording()
|
||||||
|
cleanupRecorder()
|
||||||
|
mIsRecording = false
|
||||||
|
mIsVideoMode = false
|
||||||
|
recheckAspectRatio()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun recheckAspectRatio() {
|
||||||
|
if (!mForceAspectRatio) {
|
||||||
|
mSetupPreviewAfterMeasure = true
|
||||||
|
invalidate()
|
||||||
|
requestLayout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// VIDEO RECORDING
|
||||||
|
fun initRecorder(): Boolean {
|
||||||
|
if (mCamera == null || mRecorder != null || !mIsSurfaceCreated)
|
||||||
|
return false
|
||||||
|
|
||||||
|
mSwitchToVideoAsap = false
|
||||||
|
val previewSizes = mParameters!!.supportedPreviewSizes
|
||||||
|
Collections.sort<Camera.Size>(previewSizes, SizesComparator())
|
||||||
|
val preferred = previewSizes[0]
|
||||||
|
|
||||||
|
mParameters!!.setPreviewSize(preferred.width, preferred.height)
|
||||||
|
mCamera!!.parameters = mParameters
|
||||||
|
|
||||||
|
mIsRecording = false
|
||||||
|
mIsVideoMode = true
|
||||||
|
recheckAspectRatio()
|
||||||
|
mRecorder = MediaRecorder()
|
||||||
|
mRecorder!!.setCamera(mCamera)
|
||||||
|
mRecorder!!.setVideoSource(MediaRecorder.VideoSource.DEFAULT)
|
||||||
|
mRecorder!!.setAudioSource(MediaRecorder.AudioSource.DEFAULT)
|
||||||
|
|
||||||
|
mCurrVideoPath = Utils.getOutputMediaFile(mActivity, false)
|
||||||
|
if (mCurrVideoPath!!.isEmpty()) {
|
||||||
|
mActivity.toast(R.string.video_creating_error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/*final Camera.Size videoSize = getOptimalVideoSize();
|
||||||
|
final CamcorderProfile cpHigh = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH);
|
||||||
|
cpHigh.videoFrameWidth = videoSize.width;
|
||||||
|
cpHigh.videoFrameHeight = videoSize.height;
|
||||||
|
mRecorder.setProfile(cpHigh);*/
|
||||||
|
|
||||||
|
if (Utils.needsStupidWritePermissions(context, mCurrVideoPath!!)) {
|
||||||
|
if (mConfig!!.treeUri.isEmpty()) {
|
||||||
|
mActivity.toast(R.string.save_error_internal_storage)
|
||||||
|
mConfig!!.savePhotosFolder = Environment.getExternalStorageDirectory().toString()
|
||||||
|
releaseCamera()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
/*var document: DocumentFile = Utils.getFileDocument(context, mCurrVideoPath!!, mConfig!!.treeUri)
|
||||||
|
document = document.createFile("", mCurrVideoPath!!.substring(mCurrVideoPath!!.lastIndexOf('/') + 1))
|
||||||
|
val uri = document.uri
|
||||||
|
val fileDescriptor = context.contentResolver.openFileDescriptor(uri, "rw")
|
||||||
|
mRecorder!!.setOutputFile(fileDescriptor!!.fileDescriptor)*/
|
||||||
|
} catch (e: Exception) {
|
||||||
|
setupFailed(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
mRecorder!!.setOutputFile(mCurrVideoPath)
|
||||||
|
}
|
||||||
|
mRecorder!!.setPreviewDisplay(mSurfaceHolder.surface)
|
||||||
|
|
||||||
|
val rotation = Utils.getFinalRotation(mActivity, mCurrCameraId, mCallback.getCurrentOrientation())
|
||||||
|
mInitVideoRotation = rotation
|
||||||
|
mRecorder!!.setOrientationHint(rotation)
|
||||||
|
|
||||||
|
try {
|
||||||
|
mRecorder!!.prepare()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
setupFailed(e)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupFailed(e: Exception) {
|
||||||
|
mActivity.toast(R.string.video_setup_error)
|
||||||
|
Log.e(TAG, "initRecorder " + e.message)
|
||||||
|
releaseCamera()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggleRecording(): Boolean {
|
||||||
|
if (mIsRecording) {
|
||||||
|
stopRecording()
|
||||||
|
initRecorder()
|
||||||
|
} else {
|
||||||
|
startRecording()
|
||||||
|
}
|
||||||
|
return mIsRecording
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startRecording() {
|
||||||
|
if (mInitVideoRotation != Utils.getFinalRotation(mActivity, mCurrCameraId, mCallback.getCurrentOrientation())) {
|
||||||
|
cleanupRecorder()
|
||||||
|
initRecorder()
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
mCamera!!.unlock()
|
||||||
|
toggleShutterSound(true)
|
||||||
|
mRecorder!!.start()
|
||||||
|
toggleShutterSound(false)
|
||||||
|
mIsRecording = true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
mActivity.toast(R.string.video_setup_error)
|
||||||
|
Log.e(TAG, "toggleRecording " + e.message)
|
||||||
|
releaseCamera()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun stopRecording() {
|
||||||
|
if (mRecorder != null && mIsRecording) {
|
||||||
|
try {
|
||||||
|
toggleShutterSound(true)
|
||||||
|
mRecorder!!.stop()
|
||||||
|
val paths = arrayListOf<String>(mCurrVideoPath!!)
|
||||||
|
mActivity.scanPaths(paths) {}
|
||||||
|
} catch (e: RuntimeException) {
|
||||||
|
toggleShutterSound(false)
|
||||||
|
File(mCurrVideoPath!!).delete()
|
||||||
|
mActivity.toast(R.string.video_saving_error)
|
||||||
|
Log.e(TAG, "stopRecording " + e.message)
|
||||||
|
mRecorder = null
|
||||||
|
mIsRecording = false
|
||||||
|
releaseCamera()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
mRecorder = null
|
||||||
|
mIsRecording = false
|
||||||
|
|
||||||
|
val file = File(mCurrVideoPath!!)
|
||||||
|
if (file.exists() && file.length() == 0L) {
|
||||||
|
file.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun toggleShutterSound(mute: Boolean?) {
|
||||||
|
if (!mConfig!!.isSoundEnabled) {
|
||||||
|
(mActivity.getSystemService(Context.AUDIO_SERVICE) as AudioManager).setStreamMute(AudioManager.STREAM_SYSTEM, mute!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClick(v: View) {
|
||||||
|
if (!mWasZooming)
|
||||||
|
focusArea(false)
|
||||||
|
|
||||||
|
mWasZooming = false
|
||||||
|
mSurfaceView.isSoundEffectsEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onScanCompleted(path: String, uri: Uri) {
|
||||||
|
mCallback.videoSaved(uri)
|
||||||
|
toggleShutterSound(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SizesComparator : Comparator<Camera.Size>, Serializable {
|
||||||
|
|
||||||
|
override fun compare(a: Camera.Size, b: Camera.Size): Int {
|
||||||
|
return b.width * b.height - a.width * a.height
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val serialVersionUID = 5431278455314658485L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PreviewListener {
|
||||||
|
fun setFlashAvailable(available: Boolean)
|
||||||
|
|
||||||
|
fun setIsCameraAvailable(available: Boolean)
|
||||||
|
|
||||||
|
fun getCurrentOrientation(): Int
|
||||||
|
|
||||||
|
fun videoSaved(uri: Uri)
|
||||||
|
|
||||||
|
fun drawFocusRect(x: Int, y: Int)
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user