add features: pause and resume recording video

pull/789/head
oded 5 years ago
parent aa95d19902
commit 2d979b3f31
  1. 10
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraListener.java
  2. 50
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
  3. 10
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java
  4. 17
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java
  5. 58
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraBaseEngine.java
  6. 5
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java
  7. 27
      cameraview/src/main/java/com/otaliastudios/cameraview/video/FullVideoRecorder.java
  8. 14
      cameraview/src/main/java/com/otaliastudios/cameraview/video/SnapshotVideoRecorder.java
  9. 62
      cameraview/src/main/java/com/otaliastudios/cameraview/video/VideoRecorder.java
  10. 59
      demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java
  11. 5
      demo/src/main/res/drawable/ic_pause_circle_outline_black_24dp.xml
  12. 5
      demo/src/main/res/drawable/ic_play_circle_outline_black_24dp.xml
  13. 5
      demo/src/main/res/drawable/ic_stop_white_24dp.xml
  14. 17
      demo/src/main/res/layout/activity_camera.xml

@ -161,4 +161,14 @@ public abstract class CameraListener {
} }
@UiThread
public void onVideoRecordingPause() {
}
@UiThread
public void onVideoRecordingResume() {
}
} }

@ -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 * Sets the max width for snapshots taken with {@link #takePictureSnapshot()} or
* {@link #takeVideoSnapshot(File)}. If the snapshot width exceeds this value, the snapshot * {@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(); return mCameraEngine.isTakingVideo();
} }
public boolean isRecordingPaused() {
return mCameraEngine.isRecordingPaused();
}
/** /**
* Returns true if the camera is currently capturing a picture * Returns true if the camera is currently capturing a picture
* @return boolean indicating if the camera is 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 //endregion

@ -439,6 +439,16 @@ public class Camera1Engine extends CameraBaseEngine implements
} }
} }
@Override
public void onVideoRecordingPause() {
setVideoRecordingPauseCallback();
}
@Override
public void onVideoRecordingResume() {
setVideoRecordingResumeCallback();
}
//endregion //endregion
//region Parameters //region Parameters

@ -19,6 +19,7 @@ import android.location.Location;
import android.media.Image; import android.media.Image;
import android.media.ImageReader; import android.media.ImageReader;
import android.os.Build; import android.os.Build;
import android.util.Log;
import android.util.Pair; import android.util.Pair;
import android.util.Range; import android.util.Range;
import android.util.Rational; 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 @Override
public void onVideoResult(@Nullable VideoResult.Stub result, @Nullable Exception exception) { public void onVideoResult(@Nullable VideoResult.Stub result, @Nullable Exception exception) {
super.onVideoResult(result, exception); super.onVideoResult(result, exception);

@ -560,6 +560,13 @@ public abstract class CameraBaseEngine extends CameraEngine {
return mVideoRecorder != null && mVideoRecorder.isRecording(); return mVideoRecorder != null && mVideoRecorder.isRecording();
} }
@Override
public final boolean isRecordingPaused() {
return mVideoRecorder != null && mVideoRecorder.isRecordingPaused();
}
@Override @Override
public final void takeVideo(final @NonNull VideoResult.Stub stub, public final void takeVideo(final @NonNull VideoResult.Stub stub,
final @Nullable File file, 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 @CallSuper
@Override @Override
public void onVideoResult(@Nullable VideoResult.Stub result, @Nullable Exception exception) { public void onVideoResult(@Nullable VideoResult.Stub result, @Nullable Exception exception) {

@ -127,6 +127,8 @@ public abstract class CameraEngine implements
void dispatchError(CameraException exception); void dispatchError(CameraException exception);
void dispatchOnVideoRecordingStart(); void dispatchOnVideoRecordingStart();
void dispatchOnVideoRecordingEnd(); void dispatchOnVideoRecordingEnd();
void dispatchOnVideoRecordingPause();
void dispatchOnVideoRecordingResume();
} }
protected static final String TAG = CameraEngine.class.getSimpleName(); 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 void takePictureSnapshot(final @NonNull PictureResult.Stub stub);
public abstract boolean isTakingVideo(); public abstract boolean isTakingVideo();
public abstract boolean isRecordingPaused();
public abstract void takeVideo(@NonNull VideoResult.Stub stub, public abstract void takeVideo(@NonNull VideoResult.Stub stub,
@Nullable File file, @Nullable File file,
@Nullable FileDescriptor fileDescriptor); @Nullable FileDescriptor fileDescriptor);
public abstract void takeVideoSnapshot(@NonNull VideoResult.Stub stub, @NonNull File file); public abstract void takeVideoSnapshot(@NonNull VideoResult.Stub stub, @NonNull File file);
public abstract void stopVideo(); public abstract void stopVideo();
public abstract void pauseVideoRecording();
public abstract void resumeVideoRecording();
//endregion //endregion
} }

@ -2,6 +2,8 @@ package com.otaliastudios.cameraview.video;
import android.media.CamcorderProfile; import android.media.CamcorderProfile;
import android.media.MediaRecorder; import android.media.MediaRecorder;
import android.os.Build;
import android.util.Log;
import com.otaliastudios.cameraview.CameraLogger; import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.VideoResult; import com.otaliastudios.cameraview.VideoResult;
@ -346,4 +348,29 @@ public abstract class FullVideoRecorder extends VideoRecorder {
dispatchResult(); 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();
}
}
}
} }

@ -3,6 +3,7 @@ package com.otaliastudios.cameraview.video;
import android.graphics.SurfaceTexture; import android.graphics.SurfaceTexture;
import android.opengl.EGL14; import android.opengl.EGL14;
import android.os.Build; import android.os.Build;
import android.util.Log;
import com.otaliastudios.cameraview.CameraLogger; import com.otaliastudios.cameraview.CameraLogger;
import com.otaliastudios.cameraview.internal.DeviceEncoders; import com.otaliastudios.cameraview.internal.DeviceEncoders;
@ -103,6 +104,7 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram
} }
} }
@RendererThread @RendererThread
@Override @Override
public void onRendererTextureCreated(int textureId) { public void onRendererTextureCreated(int textureId) {
@ -339,4 +341,16 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram
} }
dispatchResult(); dispatchResult();
} }
@Override
protected void onPauseVideoRecording() {
// not supported
}
@Override
protected void onResumeVideoRecording() {
// not supported
}
} }

@ -39,11 +39,17 @@ public abstract class VideoRecorder {
* and soon {@link #onVideoResult(VideoResult.Stub, Exception)} will be called. * and soon {@link #onVideoResult(VideoResult.Stub, Exception)} will be called.
*/ */
void onVideoRecordingEnd(); void onVideoRecordingEnd();
void onVideoRecordingPause();
void onVideoRecordingResume();
} }
private final static int STATE_IDLE = 0; private final static int STATE_IDLE = 0;
private final static int STATE_RECORDING = 1; private final static int STATE_RECORDING = 1;
private final static int STATE_STOPPING = 2; private final static int STATE_STOPPING = 2;
private final static int STATE_PAUSING = 3;
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) VideoResult.Stub mResult; @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) VideoResult.Stub mResult;
private final VideoResultListener mListener; private final VideoResultListener mListener;
@ -105,7 +111,15 @@ public abstract class VideoRecorder {
public boolean isRecording() { public boolean isRecording() {
// true if not idle. // true if not idle.
synchronized (mStateLock) { 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(); 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();
}
}
} }

@ -20,6 +20,7 @@ import androidx.appcompat.app.AppCompatActivity;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewTreeObserver; import android.view.ViewTreeObserver;
import android.widget.ImageButton;
import android.widget.Toast; import android.widget.Toast;
import com.otaliastudios.cameraview.CameraException; import com.otaliastudios.cameraview.CameraException;
@ -57,6 +58,12 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
private int mCurrentFilter = 0; private int mCurrentFilter = 0;
private final Filters[] mAllFilters = Filters.values(); 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 @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -67,6 +74,40 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
camera.setLifecycleOwner(this); camera.setLifecycleOwner(this);
camera.addCameraListener(new Listener()); 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) { if (USE_FRAME_PROCESSOR) {
camera.addFrameProcessor(new FrameProcessor() { camera.addFrameProcessor(new FrameProcessor() {
private long lastTime = System.currentTimeMillis(); private long lastTime = System.currentTimeMillis();
@ -257,8 +298,26 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
super.onVideoRecordingEnd(); super.onVideoRecordingEnd();
message("Video taken. Processing...", false); message("Video taken. Processing...", false);
LOG.w("onVideoRecordingEnd!"); 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 @Override
public void onExposureCorrectionChanged(float newValue, @NonNull float[] bounds, @Nullable PointF[] fingers) { public void onExposureCorrectionChanged(float newValue, @NonNull float[] bounds, @Nullable PointF[] fingers) {
super.onExposureCorrectionChanged(newValue, bounds, fingers); super.onExposureCorrectionChanged(newValue, bounds, fingers);

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M9,16h2L11,8L9,8v8zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM13,16h2L15,8h-2v8z"/>
</vector>

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M10,16.5l6,-4.5 -6,-4.5v9zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
</vector>

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M6,6h12v12H6z"/>
</vector>

@ -51,6 +51,23 @@
android:layout_marginBottom="96dp" android:layout_marginBottom="96dp"
android:layout_gravity="bottom|start"> android:layout_gravity="bottom|start">
<ImageButton
android:id="@+id/imgStopVideo"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_margin="16dp"
android:background="@drawable/background"
android:elevation="3dp"
app:srcCompat="@drawable/ic_stop_white_24dp" />
<ImageButton
android:id="@+id/imgPlayPauseVideo"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_margin="16dp"
android:background="@drawable/background"
android:elevation="3dp"
app:srcCompat="@drawable/ic_play_circle_outline_black_24dp" />
<ImageButton <ImageButton
android:id="@+id/changeFilter" android:id="@+id/changeFilter"
android:layout_width="56dp" android:layout_width="56dp"

Loading…
Cancel
Save