diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraListener.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraListener.java index 9f8a71de..d9187fd8 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraListener.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraListener.java @@ -161,4 +161,14 @@ public abstract class CameraListener { } + @UiThread + public void onVideoRecordingPause() { + + } + + @UiThread + public void onVideoRecordingResume() { + + } + } \ No newline at end of file diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java index 5dbd656b..189acab3 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java @@ -1834,6 +1834,27 @@ public class CameraView extends FrameLayout implements LifecycleObserver { }); } + public void pauseVideoRecording() { + mCameraEngine.pauseVideoRecording(); + mUiHandler.post(new Runnable() { + @Override + public void run() { + if (getKeepScreenOn() != mKeepScreenOn) setKeepScreenOn(mKeepScreenOn); + } + }); + } + + + public void resumeVideoRecording() { + mCameraEngine.resumeVideoRecording(); + mUiHandler.post(new Runnable() { + @Override + public void run() { + if (getKeepScreenOn() != mKeepScreenOn) setKeepScreenOn(mKeepScreenOn); + } + }); + } + /** * Sets the max width for snapshots taken with {@link #takePictureSnapshot()} or * {@link #takeVideoSnapshot(File)}. If the snapshot width exceeds this value, the snapshot @@ -2082,6 +2103,11 @@ public class CameraView extends FrameLayout implements LifecycleObserver { return mCameraEngine.isTakingVideo(); } + + public boolean isRecordingPaused() { + return mCameraEngine.isRecordingPaused(); + } + /** * Returns true if the camera is currently capturing a picture * @return boolean indicating if the camera is capturing a picture @@ -2380,6 +2406,30 @@ public class CameraView extends FrameLayout implements LifecycleObserver { } }); } + + @Override + public void dispatchOnVideoRecordingPause() { + mUiHandler.post(new Runnable() { + @Override + public void run() { + for (CameraListener listener : mListeners) { + listener.onVideoRecordingPause(); + } + } + }); + } + + @Override + public void dispatchOnVideoRecordingResume() { + mUiHandler.post(new Runnable() { + @Override + public void run() { + for (CameraListener listener : mListeners) { + listener.onVideoRecordingResume(); + } + } + }); + } } //endregion diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java index 95549c64..8bc6f768 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java @@ -439,6 +439,16 @@ public class Camera1Engine extends CameraBaseEngine implements } } + @Override + public void onVideoRecordingPause() { + setVideoRecordingPauseCallback(); + } + + @Override + public void onVideoRecordingResume() { + setVideoRecordingResumeCallback(); + } + //endregion //region Parameters diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java index 7e09f588..5fbaa96a 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java @@ -19,6 +19,7 @@ import android.location.Location; import android.media.Image; import android.media.ImageReader; import android.os.Build; +import android.util.Log; import android.util.Pair; import android.util.Range; import android.util.Rational; @@ -966,6 +967,22 @@ public class Camera2Engine extends CameraBaseEngine implements } } + @Override + public void onVideoRecordingPause() { + Log.d(TAG, "onVideoRecordingPause: "); + setVideoRecordingPauseCallback(); + + + } + + @Override + public void onVideoRecordingResume() { + Log.d(TAG, "onVideoRecordingResume: "); + setVideoRecordingResumeCallback(); + } + + + @Override public void onVideoResult(@Nullable VideoResult.Stub result, @Nullable Exception exception) { super.onVideoResult(result, exception); diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraBaseEngine.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraBaseEngine.java index df95098f..3c15c2c2 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraBaseEngine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraBaseEngine.java @@ -560,6 +560,13 @@ public abstract class CameraBaseEngine extends CameraEngine { return mVideoRecorder != null && mVideoRecorder.isRecording(); } + + @Override + public final boolean isRecordingPaused() { + return mVideoRecorder != null && mVideoRecorder.isRecordingPaused(); + } + + @Override public final void takeVideo(final @NonNull VideoResult.Stub stub, final @Nullable File file, @@ -644,6 +651,57 @@ public abstract class CameraBaseEngine extends CameraEngine { } } + @Override + public final void pauseVideoRecording() { + getOrchestrator().schedule("pause video", true, new Runnable() { + @Override + public void run() { +// LOG.i("pauseVideoRecording", "running. isTakingVideo?", isTakingVideo()); + onPauseVideoRecording(); + } + }); + } + + @EngineThread + @SuppressWarnings("WeakerAccess") + protected void onPauseVideoRecording() { + if (mVideoRecorder != null) { + mVideoRecorder.pauseVideoRecording(); + } + } + + + protected void setVideoRecordingPauseCallback() { + getCallback().dispatchOnVideoRecordingPause(); + } + + + @Override + public final void resumeVideoRecording() { + getOrchestrator().schedule("resume video", true, new Runnable() { + @Override + public void run() { +// LOG.i("pauseVideoRecording", "running. isTakingVideo?", isTakingVideo()); + onResumeVideoRecording(); + } + }); + } + + + @EngineThread + @SuppressWarnings("WeakerAccess") + protected void onResumeVideoRecording() { + if (mVideoRecorder != null) { + mVideoRecorder.resumeVideoRecording(); + } + } + + protected void setVideoRecordingResumeCallback() { + getCallback().dispatchOnVideoRecordingResume(); + } + + + @CallSuper @Override public void onVideoResult(@Nullable VideoResult.Stub result, @Nullable Exception exception) { diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java index 2817bbdb..c32466f1 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java @@ -127,6 +127,8 @@ public abstract class CameraEngine implements void dispatchError(CameraException exception); void dispatchOnVideoRecordingStart(); void dispatchOnVideoRecordingEnd(); + void dispatchOnVideoRecordingPause(); + void dispatchOnVideoRecordingResume(); } protected static final String TAG = CameraEngine.class.getSimpleName(); @@ -714,11 +716,14 @@ public abstract class CameraEngine implements public abstract void takePictureSnapshot(final @NonNull PictureResult.Stub stub); public abstract boolean isTakingVideo(); + public abstract boolean isRecordingPaused(); public abstract void takeVideo(@NonNull VideoResult.Stub stub, @Nullable File file, @Nullable FileDescriptor fileDescriptor); public abstract void takeVideoSnapshot(@NonNull VideoResult.Stub stub, @NonNull File file); public abstract void stopVideo(); + public abstract void pauseVideoRecording(); + public abstract void resumeVideoRecording(); //endregion } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/video/FullVideoRecorder.java b/cameraview/src/main/java/com/otaliastudios/cameraview/video/FullVideoRecorder.java index c5a4a4e3..77e15319 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/video/FullVideoRecorder.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/video/FullVideoRecorder.java @@ -2,6 +2,8 @@ package com.otaliastudios.cameraview.video; import android.media.CamcorderProfile; import android.media.MediaRecorder; +import android.os.Build; +import android.util.Log; import com.otaliastudios.cameraview.CameraLogger; import com.otaliastudios.cameraview.VideoResult; @@ -346,4 +348,29 @@ public abstract class FullVideoRecorder extends VideoRecorder { dispatchResult(); } + + @Override + protected void onPauseVideoRecording() { + Log.d(TAG, "onPauseVideoRecording: "); + if (mMediaRecorder != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){ + mMediaRecorder.pause(); + dispatchVideoRecordingPause(); + } + + } + } + + + @Override + protected void onResumeVideoRecording() { + Log.d(TAG, "onPauseVideoRecording: "); + if (mMediaRecorder != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){ + mMediaRecorder.resume(); + dispatchVideoRecordingResume(); + } + } + } + } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/video/SnapshotVideoRecorder.java b/cameraview/src/main/java/com/otaliastudios/cameraview/video/SnapshotVideoRecorder.java index b702dbf8..11e4b703 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/video/SnapshotVideoRecorder.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/video/SnapshotVideoRecorder.java @@ -3,6 +3,7 @@ package com.otaliastudios.cameraview.video; import android.graphics.SurfaceTexture; import android.opengl.EGL14; import android.os.Build; +import android.util.Log; import com.otaliastudios.cameraview.CameraLogger; import com.otaliastudios.cameraview.internal.DeviceEncoders; @@ -103,6 +104,7 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram } } + @RendererThread @Override public void onRendererTextureCreated(int textureId) { @@ -339,4 +341,16 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram } dispatchResult(); } + + + @Override + protected void onPauseVideoRecording() { + // not supported + } + + @Override + protected void onResumeVideoRecording() { + // not supported + } + } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/video/VideoRecorder.java b/cameraview/src/main/java/com/otaliastudios/cameraview/video/VideoRecorder.java index d2a2bddb..53b98cb9 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/video/VideoRecorder.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/video/VideoRecorder.java @@ -39,11 +39,17 @@ public abstract class VideoRecorder { * and soon {@link #onVideoResult(VideoResult.Stub, Exception)} will be called. */ void onVideoRecordingEnd(); + + void onVideoRecordingPause(); + + void onVideoRecordingResume(); + } private final static int STATE_IDLE = 0; private final static int STATE_RECORDING = 1; private final static int STATE_STOPPING = 2; + private final static int STATE_PAUSING = 3; @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) VideoResult.Stub mResult; private final VideoResultListener mListener; @@ -105,7 +111,15 @@ public abstract class VideoRecorder { public boolean isRecording() { // true if not idle. synchronized (mStateLock) { - return mState != STATE_IDLE; + return mState != STATE_IDLE && mState!=STATE_PAUSING; + } + } + + + public boolean isRecordingPaused() { + // true if not idle. + synchronized (mStateLock) { + return mState ==STATE_PAUSING; } } @@ -173,4 +187,50 @@ public abstract class VideoRecorder { mListener.onVideoRecordingEnd(); } } + + + public final void pauseVideoRecording() { + synchronized (mStateLock) { + if (mState == STATE_IDLE) { + return; + } + LOG.i("pause:", "Changed state to STATE_PAUSING"); + mState = STATE_PAUSING; + } + onPauseVideoRecording(); + } + + protected abstract void onPauseVideoRecording(); + + public final void resumeVideoRecording() { + synchronized (mStateLock) { + if (mState == STATE_IDLE) { + return; + } + LOG.i("resume:", "Changed state to STATE_RECORDING"); + mState = STATE_RECORDING; + } + onResumeVideoRecording(); + } + + protected abstract void onResumeVideoRecording(); + + @SuppressWarnings("WeakerAccess") + @CallSuper + protected void dispatchVideoRecordingPause() { + LOG.i("dispatchVideoRecordingPause:", "pause"); + if (mListener != null) { + mListener.onVideoRecordingPause(); + } + } + + @SuppressWarnings("WeakerAccess") + @CallSuper + protected void dispatchVideoRecordingResume() { + LOG.i("dispatchVideoRecordingResume:", "resume"); + if (mListener != null) { + mListener.onVideoRecordingResume(); + } + } + } diff --git a/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java b/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java index bf5a4c52..a60e8d43 100644 --- a/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java +++ b/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java @@ -20,6 +20,7 @@ import androidx.appcompat.app.AppCompatActivity; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; +import android.widget.ImageButton; import android.widget.Toast; import com.otaliastudios.cameraview.CameraException; @@ -57,6 +58,12 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis private int mCurrentFilter = 0; private final Filters[] mAllFilters = Filters.values(); + + private ImageButton mImgPlayPauseVideo; + private ImageButton mImgStopVideo; + + private static final int MAX_RECORDING_TIME = 2 * 60 * 1000; // 2 MINUTES! + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -67,6 +74,40 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis camera.setLifecycleOwner(this); camera.addCameraListener(new Listener()); + mImgPlayPauseVideo = findViewById(R.id.imgPlayPauseVideo); + mImgStopVideo = findViewById(R.id.imgStopVideo); + + mImgStopVideo.setVisibility(View.GONE); + + + mImgPlayPauseVideo.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + camera.setMode(Mode.VIDEO); + mImgStopVideo.setVisibility(View.VISIBLE); + if (camera.isTakingVideo()) { + mImgPlayPauseVideo.setImageResource(R.drawable.ic_play_circle_outline_black_24dp); + camera.pauseVideoRecording(); + } else if (camera.isRecordingPaused()) { + mImgPlayPauseVideo.setImageResource(R.drawable.ic_pause_circle_outline_black_24dp); + camera.resumeVideoRecording(); + } else { + mImgPlayPauseVideo.setImageResource(R.drawable.ic_pause_circle_outline_black_24dp); + camera.takeVideo(new File(getFilesDir(), "video.mp4"), MAX_RECORDING_TIME); + } + + } + }); + + + mImgStopVideo.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + camera.stopVideo(); + } + }); + + if (USE_FRAME_PROCESSOR) { camera.addFrameProcessor(new FrameProcessor() { private long lastTime = System.currentTimeMillis(); @@ -257,8 +298,26 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis super.onVideoRecordingEnd(); message("Video taken. Processing...", false); LOG.w("onVideoRecordingEnd!"); + mImgPlayPauseVideo.setImageResource(R.drawable.ic_play_circle_outline_black_24dp); + mImgStopVideo.setVisibility(View.GONE); } + + @Override + public void onVideoRecordingPause() { + super.onVideoRecordingPause(); + message("Video pause", true); + mImgPlayPauseVideo.setImageResource(R.drawable.ic_play_circle_outline_black_24dp); + } + + @Override + public void onVideoRecordingResume() { + super.onVideoRecordingResume(); + message("Video resume",true); + mImgPlayPauseVideo.setImageResource(R.drawable.ic_pause_circle_outline_black_24dp); + } + + @Override public void onExposureCorrectionChanged(float newValue, @NonNull float[] bounds, @Nullable PointF[] fingers) { super.onExposureCorrectionChanged(newValue, bounds, fingers); diff --git a/demo/src/main/res/drawable/ic_pause_circle_outline_black_24dp.xml b/demo/src/main/res/drawable/ic_pause_circle_outline_black_24dp.xml new file mode 100644 index 00000000..83e5e1c2 --- /dev/null +++ b/demo/src/main/res/drawable/ic_pause_circle_outline_black_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/demo/src/main/res/drawable/ic_play_circle_outline_black_24dp.xml b/demo/src/main/res/drawable/ic_play_circle_outline_black_24dp.xml new file mode 100644 index 00000000..41bec11b --- /dev/null +++ b/demo/src/main/res/drawable/ic_play_circle_outline_black_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/demo/src/main/res/drawable/ic_stop_white_24dp.xml b/demo/src/main/res/drawable/ic_stop_white_24dp.xml new file mode 100644 index 00000000..081c346d --- /dev/null +++ b/demo/src/main/res/drawable/ic_stop_white_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/demo/src/main/res/layout/activity_camera.xml b/demo/src/main/res/layout/activity_camera.xml index 2cc1d8a5..3d99aea1 100644 --- a/demo/src/main/res/layout/activity_camera.xml +++ b/demo/src/main/res/layout/activity_camera.xml @@ -51,6 +51,23 @@ android:layout_marginBottom="96dp" android:layout_gravity="bottom|start"> + + +