Add onVideoRecordingEnd

pull/506/head
Mattia Iavarone 6 years ago
parent c7f9b92896
commit 6f703cdbdc
  1. 9
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewCallbacksTest.java
  2. 8
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/CameraIntegrationTest.java
  3. 5
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/MockCameraEngine.java
  4. 7
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/video/VideoRecorderTest.java
  5. 21
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraListener.java
  6. 15
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
  7. 5
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java
  8. 1
      cameraview/src/main/java/com/otaliastudios/cameraview/video/FullVideoRecorder.java
  9. 8
      cameraview/src/main/java/com/otaliastudios/cameraview/video/SnapshotVideoRecorder.java
  10. 19
      cameraview/src/main/java/com/otaliastudios/cameraview/video/VideoRecorder.java
  11. 1
      cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/AudioMediaEncoder.java
  12. 15
      cameraview/src/main/java/com/otaliastudios/cameraview/video/encoding/MediaEncoderEngine.java
  13. 21
      demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java
  14. 2
      docs/_posts/2018-12-20-camera-events.md
  15. 31
      docs/_posts/2018-12-20-capturing-media.md
  16. 2
      docs/_posts/2018-12-20-changelog.md
  17. 40
      docs/_posts/2018-12-20-controls.md
  18. 32
      docs/_posts/2018-12-20-more-features.md

@ -164,6 +164,15 @@ public class CameraViewCallbacksTest extends BaseTest {
verify(listener, times(1)).onVideoRecordingStart(); verify(listener, times(1)).onVideoRecordingStart();
} }
@Test
public void testDispatchOnVideoRecordingEnd() {
completeTask().when(listener).onVideoRecordingEnd();
camera.mCameraCallbacks.dispatchOnVideoRecordingEnd();
assertNotNull(op.await(500));
verify(listener, times(1)).onVideoRecordingEnd();
}
@Test @Test
public void testDispatchOnVideoTaken() { public void testDispatchOnVideoTaken() {
VideoResult.Stub stub = new VideoResult.Stub(); VideoResult.Stub stub = new VideoResult.Stub();

@ -167,6 +167,11 @@ public abstract class CameraIntegrationTest extends BaseTest {
@SuppressWarnings("UnusedReturnValue") @SuppressWarnings("UnusedReturnValue")
@Nullable @Nullable
private VideoResult waitForVideoResult(boolean expectSuccess) { private VideoResult waitForVideoResult(boolean expectSuccess) {
// CountDownLatch for onVideoRecordingEnd.
CountDownLatch onVideoRecordingEnd = new CountDownLatch(1);
doCountDown(onVideoRecordingEnd).when(listener).onVideoRecordingEnd();
// Op for onVideoTaken.
final Op<VideoResult> video = new Op<>(true); final Op<VideoResult> video = new Op<>(true);
doEndTask(video, 0).when(listener).onVideoTaken(any(VideoResult.class)); doEndTask(video, 0).when(listener).onVideoTaken(any(VideoResult.class));
doEndTask(video, null).when(listener).onCameraError(argThat(new ArgumentMatcher<CameraException>() { doEndTask(video, null).when(listener).onCameraError(argThat(new ArgumentMatcher<CameraException>() {
@ -175,8 +180,11 @@ public abstract class CameraIntegrationTest extends BaseTest {
return argument.getReason() == CameraException.REASON_VIDEO_FAILED; return argument.getReason() == CameraException.REASON_VIDEO_FAILED;
} }
})); }));
// Wait for onVideoTaken and check.
VideoResult result = video.await(VIDEO_DELAY); VideoResult result = video.await(VIDEO_DELAY);
if (expectSuccess) { if (expectSuccess) {
assertEquals("Should call onVideoRecordingEnd", 0, onVideoRecordingEnd.getCount());
assertNotNull("Should end video", result); assertNotNull("Should end video", result);
} else { } else {
assertNull("Should not end video", result); assertNull("Should not end video", result);

@ -177,9 +177,4 @@ public class MockCameraEngine extends CameraEngine {
protected boolean collectCameraInfo(@NonNull Facing facing) { protected boolean collectCameraInfo(@NonNull Facing facing) {
return true; return true;
} }
@Override
public void onVideoRecordingStart() {
}
} }

@ -27,10 +27,13 @@ public class VideoRecorderTest extends BaseTest {
VideoRecorder.VideoResultListener listener = Mockito.mock(VideoRecorder.VideoResultListener.class); VideoRecorder.VideoResultListener listener = Mockito.mock(VideoRecorder.VideoResultListener.class);
VideoRecorder recorder = new VideoRecorder(listener) { VideoRecorder recorder = new VideoRecorder(listener) {
@Override @Override
protected void onStart() { dispatchVideoRecordingStart(); } protected void onStart() {
dispatchVideoRecordingStart();
}
@Override @Override
protected void onStop() { protected void onStop() {
dispatchVideoRecordingEnd();
dispatchResult(); dispatchResult();
} }
}; };
@ -38,6 +41,8 @@ public class VideoRecorderTest extends BaseTest {
Mockito.verify(listener,Mockito.times(1) ) Mockito.verify(listener,Mockito.times(1) )
.onVideoRecordingStart(); .onVideoRecordingStart();
recorder.stop(); recorder.stop();
Mockito.verify(listener, Mockito.times(1))
.onVideoRecordingEnd();
Mockito.verify(listener, Mockito.times(1)) Mockito.verify(listener, Mockito.times(1))
.onVideoResult(result, null); .onVideoResult(result, null);
} }

@ -129,13 +129,30 @@ public abstract class CameraListener {
/** /**
* Notifies that the actual video recording has started * Notifies that the actual video recording has started.
* This is the time when actual frames recording starts. * This is the time when actual frames recording starts.
* This can be used to show some indicator while the actual video recording. *
* This can be used to show some UI indicator for video recording or counting time.
*
* @see #onVideoRecordingEnd()
*/ */
@UiThread @UiThread
public void onVideoRecordingStart() { public void onVideoRecordingStart() {
} }
/**
* Notifies that the actual video recording has ended.
* At this point recording has ended, though the file might still be processed.
* The {@link #onVideoTaken(VideoResult)} callback will be called soon.
*
* This can be used to remove UI indicators for video recording.
*
* @see #onVideoRecordingStart()
*/
@UiThread
public void onVideoRecordingEnd() {
}
} }

@ -2080,7 +2080,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
@Override @Override
public void dispatchOnVideoRecordingStart() { public void dispatchOnVideoRecordingStart() {
mLogger.i("dispatchOnVideoRecordingStart", "dispatchOnVideoRecordingStart"); mLogger.i("dispatchOnVideoRecordingStart");
mUiHandler.post(new Runnable() { mUiHandler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
@ -2090,6 +2090,19 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
} }
}); });
} }
@Override
public void dispatchOnVideoRecordingEnd() {
mLogger.i("dispatchOnVideoRecordingEnd");
mUiHandler.post(new Runnable() {
@Override
public void run() {
for (CameraListener listener : mListeners) {
listener.onVideoRecordingEnd();
}
}
});
}
} }
//endregion //endregion

@ -136,6 +136,7 @@ public abstract class CameraEngine implements
void dispatchFrame(Frame frame); void dispatchFrame(Frame frame);
void dispatchError(CameraException exception); void dispatchError(CameraException exception);
void dispatchOnVideoRecordingStart(); void dispatchOnVideoRecordingStart();
void dispatchOnVideoRecordingEnd();
} }
private static final String TAG = CameraEngine.class.getSimpleName(); private static final String TAG = CameraEngine.class.getSimpleName();
@ -1210,6 +1211,10 @@ public abstract class CameraEngine implements
mCallback.dispatchOnVideoRecordingStart(); mCallback.dispatchOnVideoRecordingStart();
} }
@Override
public void onVideoRecordingEnd() {
mCallback.dispatchOnVideoRecordingEnd();
}
@WorkerThread @WorkerThread
protected abstract void onTakePicture(@NonNull PictureResult.Stub stub); protected abstract void onTakePicture(@NonNull PictureResult.Stub stub);

@ -148,6 +148,7 @@ public abstract class FullVideoRecorder extends VideoRecorder {
@Override @Override
protected void onStop() { protected void onStop() {
if (mMediaRecorder != null) { if (mMediaRecorder != null) {
dispatchVideoRecordingEnd();
try { try {
mMediaRecorder.stop(); mMediaRecorder.stop();
} catch (Exception e) { } catch (Exception e) {

@ -81,7 +81,6 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram
protected void onStart() { protected void onStart() {
mPreview.addRendererFrameCallback(this); mPreview.addRendererFrameCallback(this);
mDesiredState = STATE_RECORDING; mDesiredState = STATE_RECORDING;
dispatchVideoRecordingStart();
} }
@Override @Override
@ -210,7 +209,12 @@ public class SnapshotVideoRecorder extends VideoRecorder implements RendererFram
@Override @Override
public void onEncodingStart() { public void onEncodingStart() {
// Do nothing. dispatchVideoRecordingStart();
}
@Override
public void onEncodingStop() {
dispatchVideoRecordingEnd();
} }
@EncoderThread @EncoderThread

@ -29,10 +29,16 @@ public abstract class VideoRecorder {
* The callback for the actual video recording starting. * The callback for the actual video recording starting.
*/ */
void onVideoRecordingStart(); void onVideoRecordingStart();
/**
* Video recording has ended. We will finish processing the file
* and soon {@link #onVideoResult(VideoResult.Stub, Exception)} will be called.
*/
void onVideoRecordingEnd();
} }
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) VideoResult.Stub mResult; @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) VideoResult.Stub mResult;
@VisibleForTesting final VideoResultListener mListener; private final VideoResultListener mListener;
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
protected Exception mError; protected Exception mError;
private boolean mIsRecording; private boolean mIsRecording;
@ -101,4 +107,15 @@ public abstract class VideoRecorder {
mListener.onVideoRecordingStart(); mListener.onVideoRecordingStart();
} }
} }
/**
* Subclasses can call this to notify that the video recording has ended,
* although the video result might still be processed.
*/
@CallSuper
protected void dispatchVideoRecordingEnd() {
if (mListener != null) {
mListener.onVideoRecordingEnd();
}
}
} }

@ -22,7 +22,6 @@ import java.util.concurrent.LinkedBlockingQueue;
/** /**
* Default implementation for audio encoding. * Default implementation for audio encoding.
*/ */
// TODO create onVideoRecordingEnd callback
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
public class AudioMediaEncoder extends MediaEncoder { public class AudioMediaEncoder extends MediaEncoder {

@ -68,7 +68,17 @@ public class MediaEncoderEngine {
void onEncodingStart(); void onEncodingStart();
/** /**
* Called when encoding stopped for some reason. * Called when encoding stopped. At this point the mxuer might still be processing,
* but we have stopped receiving input (recording video and audio frames).
*
* The {@link #onEncodingEnd(int, Exception)} callback will soon be called
* with the results.
*/
@EncoderThread
void onEncodingStop();
/**
* Called when encoding ended for some reason.
* If there's an exception, it failed. * If there's an exception, it failed.
* @param reason the reason * @param reason the reason
* @param e the error, if present * @param e the error, if present
@ -353,6 +363,9 @@ public class MediaEncoderEngine {
LOG.w("notifyStopped:", "Called for track", track); LOG.w("notifyStopped:", "Called for track", track);
if (++mReleasedEncodersCount == mEncoders.size()) { if (++mReleasedEncodersCount == mEncoders.size()) {
LOG.w("requestStop:", "All encoders have been released. Stopping the muxer."); LOG.w("requestStop:", "All encoders have been released. Stopping the muxer.");
if (mListener != null) {
mListener.onEncodingStop();
}
end(); end();
} }
} }

@ -35,6 +35,8 @@ import java.util.List;
public class CameraActivity extends AppCompatActivity implements View.OnClickListener, OptionView.Callback { public class CameraActivity extends AppCompatActivity implements View.OnClickListener, OptionView.Callback {
private final static CameraLogger LOG = CameraLogger.create("DemoApp");
private CameraView camera; private CameraView camera;
private ViewGroup controlPanel; private ViewGroup controlPanel;
private long mCaptureTime; private long mCaptureTime;
@ -134,9 +136,14 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
animator.start(); animator.start();
} }
private void message(String content, boolean important) { private void message(@NonNull String content, boolean important) {
int length = important ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT; if (important) {
Toast.makeText(this, content, length).show(); LOG.w(content);
Toast.makeText(this, content, Toast.LENGTH_LONG).show();
} else {
LOG.i(content);
Toast.makeText(this, content, Toast.LENGTH_SHORT).show();
}
} }
private class Listener extends CameraListener { private class Listener extends CameraListener {
@ -170,7 +177,7 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
PicturePreviewActivity.setPictureResult(result); PicturePreviewActivity.setPictureResult(result);
Intent intent = new Intent(CameraActivity.this, PicturePreviewActivity.class); Intent intent = new Intent(CameraActivity.this, PicturePreviewActivity.class);
intent.putExtra("delay", callbackTime - mCaptureTime); intent.putExtra("delay", callbackTime - mCaptureTime);
Log.e("CameraActivity", "Picture delay: " + (callbackTime - mCaptureTime)); LOG.w("Picture delay:", callbackTime - mCaptureTime);
startActivity(intent); startActivity(intent);
mCaptureTime = 0; mCaptureTime = 0;
} }
@ -182,6 +189,12 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
Intent intent = new Intent(CameraActivity.this, VideoPreviewActivity.class); Intent intent = new Intent(CameraActivity.this, VideoPreviewActivity.class);
startActivity(intent); startActivity(intent);
} }
@Override
public void onVideoRecordingStart() {
super.onVideoRecordingStart();
LOG.w("onVideoRecordingStart!");
}
} }
@Override @Override

@ -39,6 +39,8 @@ camera.addCameraListener(new CameraListener() {
public void onExposureCorrectionChanged(float newValue, float[] bounds, PointF[] fingers) {} public void onExposureCorrectionChanged(float newValue, float[] bounds, PointF[] fingers) {}
public void onVideoRecordingStart() {} public void onVideoRecordingStart() {}
public void onVideoRecordingEnd() {}
}); });
``` ```

@ -59,7 +59,7 @@ Please note that the video snaphot features requires:
This is allowed at the following conditions: This is allowed at the following conditions:
- `takePictureSnapshot()` is used (no HQ pictures) - `takePictureSnapshot()` is used (no HQ pictures)
- the OpenGL preview is used (see [previews](previews.html)) - the `GL_SURFACE` preview is used (see [previews](previews.html))
### Related XML attributes ### Related XML attributes
@ -68,6 +68,35 @@ This is allowed at the following conditions:
app:cameraMode="picture|video"/> app:cameraMode="picture|video"/>
``` ```
### Related callbacks
```java
camera.addCameraListener(new CameraListener() {
@Override
public void onPictureTaken(@NonNull PictureResult result) {
// A Picture was taken!
}
@Override
public void onVideoTaken(@NonNull VideoResult result) {
// A Video was taken!
}
@Override
public void onVideoRecordingStart() {
// Notifies that the actual video recording has started.
// Can be used to show some UI indicator for video recording or counting time.
}
@Override
public void onVideoRecordingEnd() {
// Notifies that the actual video recording has ended.
// Can be used to remove UI indicators added in onVideoRecordingStart.
}
})
```
### Related APIs ### Related APIs
|Method|Description| |Method|Description|

@ -27,7 +27,7 @@ New versions are released through GitHub, so the reference page is the [GitHub R
If you were using `focus`, just switch to `autoFocus`. If you were using `focus`, just switch to `autoFocus`.
If you were using `focusWithMarker`, you can [add back the old marker](../docs/more-features.html#cameraautofocusmarker). If you were using `focusWithMarker`, you can [add back the old marker](../docs/controls.html#cameraautofocusmarker).
### v2.0.0-beta05 ### v2.0.0-beta05

@ -143,7 +143,7 @@ cameraView.setVideoBitRate(0);
cameraView.setVideoBitRate(4000000); cameraView.setVideoBitRate(4000000);
``` ```
### Manual Focus ### Auto Focus
There are many ways to focus a CameraView engine: There are many ways to focus a CameraView engine:
@ -175,6 +175,44 @@ cameraView.addCameraListener(new CameraListener() {
Auto focus is not guaranteed to be supported: check the `CameraOptions` to be sure. Auto focus is not guaranteed to be supported: check the `CameraOptions` to be sure.
```xml
<com.otaliastudios.cameraview.CameraView
app:cameraAutoFocusMarker="@string/cameraview_default_autofocus_marker"
app:cameraAutoFocusResetDelay="3000"/>
```
##### cameraAutoFocusMarker
Lets you set a marker for drawing on screen in response to auto focus events.
In XML, you should pass the qualified class name of your marker.
```java
cameraView.setAutoFocusMarker(null);
cameraView.setAutoFocusMarker(marker);
```
We offer a default marker (similar to the old `focusWithMarker` attribute in v1),
which you can set in XML using the `@string/cameraview_default_autofocus_marker` resource,
or programmatically:
```java
cameraView.setAutoFocusMarker(new DefaultAutoFocusMarker());
```
##### cameraAutoFocusResetDelay
Lets you control how an auto-focus operation is reset after completed.
Setting a value <= 0 or == Long.MAX_VALUE will not reset the auto-focus.
This is useful for low end devices that have slow auto-focus capabilities.
Defaults to 3 seconds.
```java
cameraView.setCameraAutoFocusResetDelay(1000); // 1 second
cameraView.setCameraAutoFocusResetDelay(0); // NO reset
cameraView.setCameraAutoFocusResetDelay(-1); // NO reset
cameraView.setCameraAutoFocusResetDelay(Long.MAX_VALUE); // NO reset
```
### Zoom ### Zoom
There are two ways to control the zoom value: There are two ways to control the zoom value:

@ -58,38 +58,6 @@ cameraView.setGridColor(Color.WHITE);
cameraView.setGridColor(Color.BLACK); cameraView.setGridColor(Color.BLACK);
``` ```
##### cameraAutoFocusMarker
Lets you set a marker for drawing on screen in response to auto focus events.
In XML, you should pass the qualified class name of your marker.
```java
cameraView.setAutoFocusMarker(null);
cameraView.setAutoFocusMarker(marker);
```
We offer a default marker (similar to the old `focusWithMarker` attribute in v1),
which you can set in XML using the `@string/cameraview_default_autofocus_marker` resource,
or programmatically:
```java
cameraView.setAutoFocusMarker(new DefaultAutoFocusMarker());
```
##### cameraAutoFocusResetDelay
Lets you control how an auto-focus operation is reset after completed.
Setting a value <= 0 or == Long.MAX_VALUE will not reset the auto-focus.
This is useful for low end devices that have slow auto-focus capabilities.
Defaults to 3 seconds.
```java
cameraView.setCameraAutoFocusResetDelay(1000); // 1 second
cameraView.setCameraAutoFocusResetDelay(0); // NO reset
cameraView.setCameraAutoFocusResetDelay(-1); // NO reset
cameraView.setCameraAutoFocusResetDelay(Long.MAX_VALUE); // NO reset
```
##### cameraUseDeviceOrientation ##### cameraUseDeviceOrientation
Controls whether we should consider the device orientation for picture and video outputs. Controls whether we should consider the device orientation for picture and video outputs.

Loading…
Cancel
Save