diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewCallbacksTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewCallbacksTest.java index 894a6501..d1d51fcd 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewCallbacksTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewCallbacksTest.java @@ -19,16 +19,13 @@ import org.mockito.stubbing.Stubber; import static junit.framework.Assert.assertNotNull; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyFloat; import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -137,11 +134,11 @@ public class CameraViewCallbacksTest extends BaseTest { @Test public void testDispatchOnVideoTaken() { - completeTask().when(listener).onVideoTaken(null); - camera.mCameraCallbacks.dispatchOnVideoTaken(null); + completeTask().when(listener).onVideoTaken(null, false); + camera.mCameraCallbacks.dispatchOnVideoTaken(null, false); assertNotNull(task.await(200)); - verify(listener, times(1)).onVideoTaken(null); + verify(listener, times(1)).onVideoTaken(null, false); } @Test diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/IntegrationTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/IntegrationTest.java index 60b8fe72..1c18fbe0 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/IntegrationTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/IntegrationTest.java @@ -3,7 +3,6 @@ package com.otaliastudios.cameraview; import android.graphics.Bitmap; import android.graphics.PointF; -import android.media.MediaRecorder; import android.support.test.filters.MediumTest; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; @@ -20,8 +19,13 @@ import java.io.File; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import static org.mockito.Mockito.*; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; /** @@ -120,7 +124,7 @@ public class IntegrationTest extends BaseTest { private void waitForVideoEnd(boolean expectSuccess) { final Task video = new Task<>(true); - doEndTask(video, true).when(listener).onVideoTaken(any(File.class)); + doEndTask(video, true).when(listener).onVideoTaken(any(File.class), false); Boolean result = video.await(8000); if (expectSuccess) { assertNotNull("Can take video", result); diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/MockCameraController.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/MockCameraController.java index d777e45a..d1e8b257 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/MockCameraController.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/MockCameraController.java @@ -131,4 +131,14 @@ public class MockCameraController extends CameraController { @Override public void onBufferAvailable(byte[] buffer) { } + + @Override + void setMaxFileSize(long maxFileSizeInBytes) { + + } + + @Override + boolean isRecordingVideo() { + return false; + } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java index e3199eee..abd6cf31 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java @@ -9,7 +9,6 @@ import android.hardware.Camera; import android.location.Location; import android.media.CamcorderProfile; import android.media.MediaRecorder; -import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.WorkerThread; @@ -18,7 +17,6 @@ import android.view.SurfaceHolder; import java.io.File; import java.io.IOException; import java.util.ArrayList; -import java.util.Collections; import java.util.List; @@ -52,6 +50,8 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera private boolean mIsBound = false; private boolean mIsCapturingImage = false; private boolean mIsCapturingVideo = false; + private long mMaxFileSizeInBytes = 0; + private boolean mHasReachedMaxSize = false; Camera1(CameraView.CameraCallbacks callback) { super(callback); @@ -700,9 +700,11 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera mMediaRecorder = null; } if (mVideoFile != null) { - mCameraCallbacks.dispatchOnVideoTaken(mVideoFile); + mCameraCallbacks.dispatchOnVideoTaken(mVideoFile, mHasReachedMaxSize); mVideoFile = null; } + + mHasReachedMaxSize = false; } @WorkerThread @@ -736,6 +738,26 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera mMediaRecorder.setOutputFile(mVideoFile.getAbsolutePath()); mMediaRecorder.setOrientationHint(computeExifRotation()); + + //If the user sets a max file size, set it to the max file size + if(mMaxFileSizeInBytes > 0) { + mMediaRecorder.setMaxFileSize(mMaxFileSizeInBytes); + + //Attach a listener to the media recorder to listen for file size notifications + mMediaRecorder.setOnInfoListener(new MediaRecorder.OnInfoListener() { + @Override + public void onInfo(MediaRecorder mediaRecorder, int i, int i1) { + switch (i){ + case MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED:{ + mHasReachedMaxSize = true; + endVideo(); + break; + } + } + + } + }); + } // Not needed. mMediaRecorder.setPreviewDisplay(mPreview.getSurface()); } @@ -893,4 +915,18 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera LOG.i("size:", "sizesFromList:", result); return result; } + + @Override + void setMaxFileSize(long maxFileSizeInBytes) { + mMaxFileSizeInBytes = maxFileSizeInBytes; + } + + // ----------------- + // Additional helper info + + @Override + boolean isRecordingVideo() { + return mIsCapturingVideo; + } } + diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera2.java b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera2.java index 67f4637b..ca870058 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera2.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera2.java @@ -1,23 +1,12 @@ package com.otaliastudios.cameraview; import android.annotation.TargetApi; -import android.content.Context; -import android.graphics.ImageFormat; import android.graphics.PointF; -import android.hardware.camera2.CameraAccessException; -import android.hardware.camera2.CameraCharacteristics; -import android.hardware.camera2.CameraDevice; -import android.hardware.camera2.CameraManager; -import android.hardware.camera2.params.StreamConfigurationMap; import android.location.Location; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.annotation.WorkerThread; import java.io.File; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; @TargetApi(21) class Camera2 extends CameraController { @@ -130,4 +119,14 @@ class Camera2 extends CameraController { public void onBufferAvailable(byte[] buffer) { } + + @Override + void setMaxFileSize(long maxFileSizeInBytes) { + + } + + @Override + boolean isRecordingVideo() { + return false; + } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java index 4fc29f8b..71d87bae 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java @@ -315,6 +315,10 @@ abstract class CameraController implements abstract void startAutoFocus(@Nullable Gesture gesture, PointF point); + abstract void setMaxFileSize(long maxFileSizeInBytes); + + abstract boolean isRecordingVideo(); + //endregion //region final getters diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraListener.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraListener.java index 17e2e905..d1b430d2 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraListener.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraListener.java @@ -73,9 +73,11 @@ public abstract class CameraListener { * * * @param video file hosting the mp4 video + * @param hasReachedMaxFileSize returns true if the user has set a max file size for videos and + * if the video ended because of the file size limit. */ @UiThread - public void onVideoTaken(File video) { + public void onVideoTaken(File video, boolean hasReachedMaxFileSize) { } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java index 2ff7a8bf..531d8221 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java @@ -1232,7 +1232,7 @@ public class CameraView extends FrameLayout { /** * Stops capturing video, if there was a video record going on. - * This will fire {@link CameraListener#onVideoTaken(File)}. + * This will fire {@link CameraListener#onVideoTaken(File, boolean)}. */ public void stopCapturingVideo() { mCameraController.endVideo(); @@ -1334,6 +1334,24 @@ public class CameraView extends FrameLayout { return mPlaySounds; } + /** + * Set a max file size (in bytes) for a video recording. There is no file size limit by default + * unless set by the user. + * + * @param maxFileSizeInBytes The maximum size of videos in bytes + */ + public void setMaxFileSize(long maxFileSizeInBytes){ + mCameraController.setMaxFileSize(maxFileSizeInBytes); + } + + /** + * Returns true if the camera is currently recording a video + * @return boolean indicating if the camera is recording a video + */ + public boolean isRecordingVideo(){ + return mCameraController.isRecordingVideo(); + } + //endregion //region Callbacks and dispatching @@ -1345,7 +1363,7 @@ public class CameraView extends FrameLayout { void onShutter(boolean shouldPlaySound); void processImage(byte[] jpeg, boolean consistentWithView, boolean flipHorizontally); void processSnapshot(YuvImage image, boolean consistentWithView, boolean flipHorizontally); - void dispatchOnVideoTaken(File file); + void dispatchOnVideoTaken(File file, boolean hasReachedMaxFileSize); void dispatchOnFocusStart(@Nullable Gesture trigger, PointF where); void dispatchOnFocusEnd(@Nullable Gesture trigger, boolean success, PointF where); void dispatchOnZoomChanged(final float newValue, final PointF[] fingers); @@ -1492,13 +1510,13 @@ public class CameraView extends FrameLayout { } @Override - public void dispatchOnVideoTaken(final File video) { + public void dispatchOnVideoTaken(final File video, final boolean hasReachedMaxFileSize) { mLogger.i("dispatchOnVideoTaken", video); mUiHandler.post(new Runnable() { @Override public void run() { for (CameraListener listener : mListeners) { - listener.onVideoTaken(video); + listener.onVideoTaken(video, hasReachedMaxFileSize); } } }); 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 705eef62..1043c849 100644 --- a/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java +++ b/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java @@ -7,24 +7,18 @@ import android.os.Bundle; import android.support.annotation.NonNull; import android.support.design.widget.BottomSheetBehavior; import android.support.v7.app.AppCompatActivity; -import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.WindowManager; import android.widget.Toast; -import com.otaliastudios.cameraview.Audio; import com.otaliastudios.cameraview.CameraListener; import com.otaliastudios.cameraview.CameraLogger; import com.otaliastudios.cameraview.CameraOptions; import com.otaliastudios.cameraview.CameraView; -import com.otaliastudios.cameraview.Flash; -import com.otaliastudios.cameraview.Grid; import com.otaliastudios.cameraview.SessionType; import com.otaliastudios.cameraview.Size; -import com.otaliastudios.cameraview.VideoQuality; -import com.otaliastudios.cameraview.WhiteBalance; import java.io.File; @@ -53,7 +47,12 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis camera.addCameraListener(new CameraListener() { public void onCameraOpened(CameraOptions options) { onOpened(); } public void onPictureTaken(byte[] jpeg) { onPicture(jpeg); } - public void onVideoTaken(File video) { onVideo(video); } + + @Override + public void onVideoTaken(File video, boolean hasReachedMaxFileSize) { + super.onVideoTaken(video, hasReachedMaxFileSize); + onVideo(video); + } }); findViewById(R.id.edit).setOnClickListener(this);