Refactor onVideoTaken, introducing VideoResult

pull/360/head
Mattia Iavarone 7 years ago
parent eeca06f1da
commit 68ff331a80
  1. 1
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewCallbacksTest.java
  2. 1
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java
  3. 8
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/IntegrationTest.java
  4. 47
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/VideoResultTest.java
  5. 90
      cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java
  6. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java
  7. 15
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraListener.java
  8. 8
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
  9. 83
      cameraview/src/main/java/com/otaliastudios/cameraview/VideoResult.java
  10. 7
      demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java
  11. 2
      demo/src/main/res/layout/activity_camera.xml

@ -264,7 +264,6 @@ public class CameraViewCallbacksTest extends BaseTest {
camera.setRight(viewDim[0]); camera.setRight(viewDim[0]);
// Ensure the image will (not) be cropped. // Ensure the image will (not) be cropped.
camera.setCropOutput(crop);
mockPreview.setIsCropping(crop); mockPreview.setIsCropping(crop);
// Create fake JPEG array and trigger the process. // Create fake JPEG array and trigger the process.

@ -76,7 +76,6 @@ public class CameraViewTest extends BaseTest {
public void testNullBeforeStart() { public void testNullBeforeStart() {
assertFalse(cameraView.isStarted()); assertFalse(cameraView.isStarted());
assertNull(cameraView.getCameraOptions()); assertNull(cameraView.getCameraOptions());
assertNull(cameraView.getExtraProperties());
assertNull(cameraView.getPreviewSize()); assertNull(cameraView.getPreviewSize());
assertNull(cameraView.getPictureSize()); assertNull(cameraView.getPictureSize());
} }

@ -126,7 +126,7 @@ public class IntegrationTest extends BaseTest {
private void waitForVideoEnd(boolean expectSuccess) { private void waitForVideoEnd(boolean expectSuccess) {
final Task<Boolean> video = new Task<>(true); final Task<Boolean> video = new Task<>(true);
doEndTask(video, true).when(listener).onVideoTaken(any(File.class)); doEndTask(video, true).when(listener).onVideoTaken(any(VideoResult.class));
Boolean result = video.await(8000); Boolean result = video.await(8000);
if (expectSuccess) { if (expectSuccess) {
assertNotNull("Should end video", result); assertNotNull("Should end video", result);
@ -205,10 +205,8 @@ public class IntegrationTest extends BaseTest {
@Test @Test
public void testStartInitializesOptions() { public void testStartInitializesOptions() {
assertNull(camera.getCameraOptions()); assertNull(camera.getCameraOptions());
assertNull(camera.getExtraProperties());
waitForOpen(true); waitForOpen(true);
assertNotNull(camera.getCameraOptions()); assertNotNull(camera.getCameraOptions());
assertNotNull(camera.getExtraProperties());
} }
//endregion //endregion
@ -530,7 +528,7 @@ public class IntegrationTest extends BaseTest {
@Test @Test
public void testCapturePicture_size() throws Exception { public void testCapturePicture_size() throws Exception {
camera.setCropOutput(false); // TODO v2: might have to change this
waitForOpen(true); waitForOpen(true);
Size size = camera.getPictureSize(); Size size = camera.getPictureSize();
@ -566,7 +564,7 @@ public class IntegrationTest extends BaseTest {
@Test @Test
public void testCaptureSnapshot_size() throws Exception { public void testCaptureSnapshot_size() throws Exception {
camera.setCropOutput(false); // TODO v2: might have to change this
waitForOpen(true); waitForOpen(true);
Size size = camera.getPreviewSize(); Size size = camera.getPreviewSize();

@ -0,0 +1,47 @@
package com.otaliastudios.cameraview;
import android.hardware.Camera;
import android.location.Location;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import java.io.File;
import static org.junit.Assert.assertEquals;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class VideoResultTest extends BaseTest {
private VideoResult result = new VideoResult();
@Test
public void testResult() {
File file = Mockito.mock(File.class);
int rotation = 90;
Size size = new Size(20, 120);
VideoCodec codec = VideoCodec.H_263;
Location location = Mockito.mock(Location.class);
boolean isSnapshot = true;
result.file = file;
result.rotation = rotation;
result.size = size;
result.codec = codec;
result.location = location;
result.isSnapshot = isSnapshot;
assertEquals(result.getFile(), file);
assertEquals(result.getRotation(), rotation);
assertEquals(result.getSize(), size);
assertEquals(result.getCodec(), codec);
assertEquals(result.getLocation(), location);
assertEquals(result.isSnapshot(), isSnapshot);
}
}

@ -225,7 +225,6 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
LOG.w("onStop:", "Clean up.", "Exception while releasing camera.", e); LOG.w("onStop:", "Clean up.", "Exception while releasing camera.", e);
} }
} }
mExtraProperties = null;
mCameraOptions = null; mCameraOptions = null;
mCamera = null; mCamera = null;
mPreviewSize = null; mPreviewSize = null;
@ -636,15 +635,67 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
public void run() { public void run() {
if (mIsCapturingVideo) return; if (mIsCapturingVideo) return;
if (mSessionType == SessionType.VIDEO) { if (mSessionType == SessionType.VIDEO) {
mVideoFile = videoFile;
mIsCapturingVideo = true; mIsCapturingVideo = true;
initMediaRecorder();
// Create the video result
CamcorderProfile profile = getCamcorderProfile();
Size videoSize = new Size(profile.videoFrameWidth, profile.videoFrameHeight);
mVideoResult = new VideoResult();
mVideoResult.file = videoFile;
mVideoResult.isSnapshot = false;
mVideoResult.codec = mVideoCodec;
mVideoResult.location = mLocation;
mVideoResult.rotation = computeSensorToOutputOffset();
mVideoResult.size = mVideoResult.rotation % 180 == 0 ? videoSize : videoSize.flip();
// Initialize the media recorder
mCamera.unlock();
mMediaRecorder = new MediaRecorder();
mMediaRecorder.setCamera(mCamera);
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
if (mAudio == Audio.ON) {
// Must be called before setOutputFormat.
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
}
mMediaRecorder.setOutputFormat(profile.fileFormat);
mMediaRecorder.setVideoFrameRate(profile.videoFrameRate);
mMediaRecorder.setVideoSize(profile.videoFrameWidth, profile.videoFrameHeight);
mMediaRecorder.setVideoEncoder(mMapper.map(mVideoCodec));
mMediaRecorder.setVideoEncodingBitRate(profile.videoBitRate);
if (mAudio == Audio.ON) {
mMediaRecorder.setAudioChannels(profile.audioChannels);
mMediaRecorder.setAudioSamplingRate(profile.audioSampleRate);
mMediaRecorder.setAudioEncoder(profile.audioCodec);
mMediaRecorder.setAudioEncodingBitRate(profile.audioBitRate);
}
if (mLocation != null) {
mMediaRecorder.setLocation(
(float) mLocation.getLatitude(),
(float) mLocation.getLongitude());
}
mMediaRecorder.setOutputFile(mVideoResult.getFile().getAbsolutePath());
mMediaRecorder.setOrientationHint(mVideoResult.getRotation());
mMediaRecorder.setMaxFileSize(mVideoMaxSize);
mMediaRecorder.setMaxDuration(mVideoMaxDuration);
mMediaRecorder.setOnInfoListener(new MediaRecorder.OnInfoListener() {
@Override
public void onInfo(MediaRecorder mediaRecorder, int what, int extra) {
switch (what) {
case MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED:
case MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED:
stopVideoImmediately();
break;
}
}
});
// Not needed. mMediaRecorder.setPreviewDisplay(mPreview.getSurface());
try { try {
mMediaRecorder.prepare(); mMediaRecorder.prepare();
mMediaRecorder.start(); mMediaRecorder.start();
} catch (Exception e) { } catch (Exception e) {
LOG.e("Error while starting MediaRecorder. Swallowing.", e); LOG.e("Error while starting MediaRecorder. Swallowing.", e);
mVideoFile = null; mVideoResult = null;
mCamera.lock(); mCamera.lock();
stopVideoImmediately(); stopVideoImmediately();
} }
@ -679,9 +730,9 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
mMediaRecorder.release(); mMediaRecorder.release();
mMediaRecorder = null; mMediaRecorder = null;
} }
if (mVideoFile != null) { if (mVideoResult != null) {
mCameraCallbacks.dispatchOnVideoTaken(mVideoFile); mCameraCallbacks.dispatchOnVideoTaken(mVideoResult);
mVideoFile = null; mVideoResult = null;
} }
if (mCamera != null) { if (mCamera != null) {
// This is needed to restore FrameProcessor. No re-allocation needed though. // This is needed to restore FrameProcessor. No re-allocation needed though.
@ -716,31 +767,6 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
mMediaRecorder.setAudioEncoder(profile.audioCodec); mMediaRecorder.setAudioEncoder(profile.audioCodec);
mMediaRecorder.setAudioEncodingBitRate(profile.audioBitRate); mMediaRecorder.setAudioEncodingBitRate(profile.audioBitRate);
} }
if (mLocation != null) {
mMediaRecorder.setLocation(
(float) mLocation.getLatitude(),
(float) mLocation.getLongitude());
}
mMediaRecorder.setOutputFile(mVideoFile.getAbsolutePath());
mMediaRecorder.setOrientationHint(computeSensorToOutputOffset());
mMediaRecorder.setMaxFileSize(mVideoMaxSize);
mMediaRecorder.setMaxDuration(mVideoMaxDuration);
mMediaRecorder.setOnInfoListener(new MediaRecorder.OnInfoListener() {
@Override
public void onInfo(MediaRecorder mediaRecorder, int what, int extra) {
switch (what) {
case MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED:
case MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED:
stopVideoImmediately();
break;
}
}
});
// Not needed. mMediaRecorder.setPreviewDisplay(mPreview.getSurface());
} }
// ----------------- // -----------------

@ -54,7 +54,7 @@ abstract class CameraController implements
protected FrameManager mFrameManager; protected FrameManager mFrameManager;
protected SizeSelector mPictureSizeSelector; protected SizeSelector mPictureSizeSelector;
protected MediaRecorder mMediaRecorder; protected MediaRecorder mMediaRecorder;
protected File mVideoFile; protected VideoResult mVideoResult;
protected long mVideoMaxSize; protected long mVideoMaxSize;
protected int mVideoMaxDuration; protected int mVideoMaxDuration;
protected Size mPictureSize; protected Size mPictureSize;

@ -4,8 +4,6 @@ import android.graphics.PointF;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.UiThread; import android.support.annotation.UiThread;
import java.io.File;
public abstract class CameraListener { public abstract class CameraListener {
@ -65,18 +63,13 @@ public abstract class CameraListener {
/** /**
* Notifies that a video capture has just ended. The file parameter is the one that * Notifies that a video capture has just ended.
* was passed to {@link CameraView#takeVideo(File)}, if any.
* If not, the camera fallsback to:
* <code>
* new File(getContext().getExternalFilesDir(null), "video.mp4");
* </code>
* *
* @param video file hosting the mp4 video * @param result the video result
*/ */
@UiThread @UiThread
public void onVideoTaken(File video) { public void onVideoTaken(VideoResult result) {
// TODO v2: use a VideoResult.
} }

@ -1189,7 +1189,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
final int old = getVideoMaxDuration(); final int old = getVideoMaxDuration();
addCameraListener(new CameraListener() { addCameraListener(new CameraListener() {
@Override @Override
public void onVideoTaken(File video) { public void onVideoTaken(VideoResult result) {
setVideoMaxDuration(old); setVideoMaxDuration(old);
removeCameraListener(this); removeCameraListener(this);
} }
@ -1204,7 +1204,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
/** /**
* Stops capturing video, if there was a video record going on. * Stops capturing video, if there was a video record going on.
* This will fire {@link CameraListener#onVideoTaken(File)}. * This will fire {@link CameraListener#onVideoTaken(VideoResult)}.
*/ */
public void stopVideo() { public void stopVideo() {
mCameraController.stopVideo(); mCameraController.stopVideo();
@ -1389,7 +1389,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
void onShutter(boolean shouldPlaySound); void onShutter(boolean shouldPlaySound);
void processPicture(byte[] jpeg, boolean consistentWithView, boolean flipHorizontally); void processPicture(byte[] jpeg, boolean consistentWithView, boolean flipHorizontally);
void processSnapshot(YuvImage image, boolean consistentWithView, boolean flipHorizontally); void processSnapshot(YuvImage image, boolean consistentWithView, boolean flipHorizontally);
void dispatchOnVideoTaken(File file); void dispatchOnVideoTaken(VideoResult result);
void dispatchOnFocusStart(@Nullable Gesture trigger, PointF where); void dispatchOnFocusStart(@Nullable Gesture trigger, PointF where);
void dispatchOnFocusEnd(@Nullable Gesture trigger, boolean success, PointF where); void dispatchOnFocusEnd(@Nullable Gesture trigger, boolean success, PointF where);
void dispatchOnZoomChanged(final float newValue, final PointF[] fingers); void dispatchOnZoomChanged(final float newValue, final PointF[] fingers);
@ -1515,7 +1515,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
} }
@Override @Override
public void dispatchOnVideoTaken(final File video) { public void dispatchOnVideoTaken(final VideoResult video) {
mLogger.i("dispatchOnVideoTaken", video); mLogger.i("dispatchOnVideoTaken", video);
mUiHandler.post(new Runnable() { mUiHandler.post(new Runnable() {
@Override @Override

@ -0,0 +1,83 @@
package com.otaliastudios.cameraview;
import android.location.Location;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.io.File;
/**
* Wraps the result of a video recording started by {@link CameraView#takeVideo(File)}.
*/
public class VideoResult {
boolean isSnapshot;
Location location;
int rotation;
Size size;
File file;
VideoCodec codec;
VideoResult() {}
/**
* Returns whether this result comes from a snapshot.
*
* @return whether this is a snapshot
*/
public boolean isSnapshot() {
return isSnapshot;
}
/**
* Returns geographic information for this video, if any.
* If it was set, it is also present in the file metadata.
*
* @return a nullable Location
*/
@Nullable
public Location getLocation() {
return location;
}
/**
* Returns the clock-wise rotation that should be applied to the
* video frames before displaying. If it is non-zero, it is also present
* in the video metadata, so most reader will take care of it.
*
* @return the clock-wise rotation
*/
public int getRotation() {
return rotation;
}
/**
* Returns the size of the frames after the rotation is applied.
*
* @return the Size of this video
*/
@NonNull
public Size getSize() {
return size;
}
/**
* Returns the file where the video was saved.
*
* @return the File of this video
*/
@NonNull
public File getFile() {
return file;
}
/**
* Returns the codec that was used to encode the video frames.
*
* @return the video codec
*/
@NonNull
public VideoCodec getCodec() {
return codec;
}
}

@ -19,6 +19,7 @@ import com.otaliastudios.cameraview.CameraOptions;
import com.otaliastudios.cameraview.CameraView; import com.otaliastudios.cameraview.CameraView;
import com.otaliastudios.cameraview.SessionType; import com.otaliastudios.cameraview.SessionType;
import com.otaliastudios.cameraview.Size; import com.otaliastudios.cameraview.Size;
import com.otaliastudios.cameraview.VideoResult;
import java.io.File; import java.io.File;
@ -50,9 +51,9 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis
public void onPictureTaken(byte[] jpeg) { onPicture(jpeg); } public void onPictureTaken(byte[] jpeg) { onPicture(jpeg); }
@Override @Override
public void onVideoTaken(File video) { public void onVideoTaken(VideoResult result) {
super.onVideoTaken(video); super.onVideoTaken(result);
onVideo(video); onVideo(result.getFile());
} }
}); });

@ -17,7 +17,6 @@
android:keepScreenOn="true" android:keepScreenOn="true"
app:cameraPlaySounds="true" app:cameraPlaySounds="true"
app:cameraGrid="off" app:cameraGrid="off"
app:cameraCropOutput="false"
app:cameraFacing="back" app:cameraFacing="back"
app:cameraFlash="off" app:cameraFlash="off"
app:cameraAudio="on" app:cameraAudio="on"
@ -26,7 +25,6 @@
app:cameraGesturePinch="zoom" app:cameraGesturePinch="zoom"
app:cameraGestureScrollHorizontal="exposureCorrection" app:cameraGestureScrollHorizontal="exposureCorrection"
app:cameraGestureScrollVertical="none" app:cameraGestureScrollVertical="none"
app:cameraJpegQuality="100"
app:cameraSessionType="picture" /> app:cameraSessionType="picture" />
<!-- Controls --> <!-- Controls -->

Loading…
Cancel
Save