From bcb2cfeb7f0a8b651b77d94f6bd71caa30ad6bbc Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Tue, 27 Feb 2018 15:57:09 +0100 Subject: [PATCH 1/8] Bump version (#166) --- CHANGELOG.md | 17 +++++++++++++++++ README.md | 16 +++++++++------- cameraview/build.gradle | 2 +- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d14d5504..1c855c2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +### v1.4.2 + +- Add prefix to XML resources so they don't collide, thanks to [@RocketRider][RocketRider] ([#162][162]) +- Add `videoMaxSize` API and XML attribute, to set max size video in bytes, thanks to [@chaitanyaraghav][chaitanyaraghav] ([#104][104]) +- Improved the preview size selection, thanks to [@YeungKC][YeungKC] ([#133][133]) +- Improved the playSounds attribute, was playing incorrectly, thanks to [@xp-vit][xp-vit] ([#143][143]) + +https://github.com/natario1/CameraView/compare/v1.4.1...v1.4.2 + ### v1.4.1 - Fixed a bug that would flip the front camera preview on some devices ([#112][112]) @@ -56,7 +65,11 @@ https://github.com/natario1/CameraView/compare/v1.2.3...v1.3.0 [v-gar]: https://github.com/v-gar [andrewmunn]: https://github.com/andrewmunn +[chaitanyaraghav]: https://github.com/chaitanyaraghav +[YeungKC]: https://github.com/YeungKC [RobertoMorelos]: https://github.com/RobertoMorelos +[RocketRider]: https://github.com/RocketRider +[xp-vit]: https://github.com/xp-vit [73]: https://github.com/natario1/CameraView/pull/73 [80]: https://github.com/natario1/CameraView/pull/80 @@ -70,5 +83,9 @@ https://github.com/natario1/CameraView/compare/v1.2.3...v1.3.0 [97]: https://github.com/natario1/CameraView/pull/97 [99]: https://github.com/natario1/CameraView/pull/99 [101]: https://github.com/natario1/CameraView/pull/101 +[104]: https://github.com/natario1/CameraView/pull/104 [105]: https://github.com/natario1/CameraView/pull/105 [112]: https://github.com/natario1/CameraView/pull/112 +[133]: https://github.com/natario1/CameraView/pull/133 +[143]: https://github.com/natario1/CameraView/pull/143 +[162]: https://github.com/natario1/CameraView/pull/162 diff --git a/README.md b/README.md index f1462704..4a46c8da 100644 --- a/README.md +++ b/README.md @@ -12,16 +12,18 @@ addressing most of the common issues and needs, and still leaving you with flexi See [CHANGELOG](https://github.com/natario1/CameraView/blob/master/CHANGELOG.md). ```groovy -compile 'com.otaliastudios:cameraview:1.4.1' +compile 'com.otaliastudios:cameraview:1.4.2' ``` -If you're facing gradle issues while installing it, make sure your project dependencies have the jcenter() repository correctly confgured: + +Make sure your project repositories include the `jcenter()`: + ```groovy allprojects { - + repositories { - + jcenter() - + } - +} - ``` + repositories { + jcenter() + } +} +```

diff --git a/cameraview/build.gradle b/cameraview/build.gradle index dfe4f5af..0f265ced 100644 --- a/cameraview/build.gradle +++ b/cameraview/build.gradle @@ -3,7 +3,7 @@ apply plugin: 'com.github.dcendents.android-maven' apply plugin: 'com.jfrog.bintray' // Required by bintray -version = '1.4.1' +version = '1.4.2' group = 'com.otaliastudios' //region android dependencies From ef1a4ecc65916e4e8d097ff91d8ccfceea858eb8 Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Tue, 27 Feb 2018 20:41:55 +0100 Subject: [PATCH 2/8] Check focus area max number before applying (#167) --- .../src/main/java/com/otaliastudios/cameraview/Camera1.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java index 39d01df2..893dab91 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java @@ -38,8 +38,10 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera if (!isCameraAvailable()) return; mCamera.cancelAutoFocus(); Camera.Parameters params = mCamera.getParameters(); - params.setFocusAreas(null); - params.setMeteringAreas(null); + int maxAF = params.getMaxNumFocusAreas(); + int maxAE = params.getMaxNumMeteringAreas(); + if (maxAF > 0) params.setFocusAreas(null); + if (maxAE > 0) params.setMeteringAreas(null); applyDefaultFocus(params); // Revert to internal focus. mCamera.setParameters(params); } From 07f607838574cfe45b9fabeabc6943626e60f5bf Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Wed, 28 Feb 2018 12:48:26 +0100 Subject: [PATCH 3/8] Demo app - Lock orientation to a fixed value (#168) --- demo/src/main/AndroidManifest.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/demo/src/main/AndroidManifest.xml b/demo/src/main/AndroidManifest.xml index 02c5b2b0..91853314 100644 --- a/demo/src/main/AndroidManifest.xml +++ b/demo/src/main/AndroidManifest.xml @@ -16,7 +16,8 @@ android:name=".CameraActivity" android:theme="@style/Theme.MainActivity" android:hardwareAccelerated="true" - android:configChanges="orientation|screenLayout|keyboardHidden"> + android:configChanges="orientation|screenLayout|keyboardHidden" + android:screenOrientation="portrait"> From 9b8cc16d96e82b405e6e532802f8758c67765332 Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Thu, 1 Mar 2018 00:41:55 +0100 Subject: [PATCH 4/8] Add discrete granularity to pinch and scroll events (#170) * Add discrete granularity to pinch and scroll events * Better tests --- .../cameraview/CameraViewTest.java | 26 +++++++++++++++---- .../otaliastudios/cameraview/CameraView.java | 10 ++++--- .../cameraview/GestureLayout.java | 25 +++++++++++++++--- .../cameraview/PinchGestureLayout.java | 16 +++--------- .../cameraview/ScrollGestureLayout.java | 26 +++++-------------- 5 files changed, 59 insertions(+), 44 deletions(-) diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java index 38618c01..b3f14516 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java @@ -206,6 +206,7 @@ public class CameraViewTest extends BaseTest { @Test public void testGestureAction_zoom() { mockController.mockStarted(true); + mockController.mZoomChanged = false; MotionEvent event = MotionEvent.obtain(0L, 0L, 0, 0f, 0f, 0); ui(new Runnable() { @Override @@ -214,10 +215,18 @@ public class CameraViewTest extends BaseTest { public boolean onTouchEvent(MotionEvent event) { return true; } }; cameraView.mPinchGestureLayout.setGestureType(Gesture.PINCH); + cameraView.mapGesture(Gesture.PINCH, GestureAction.ZOOM); + } }); - mockController.mZoomChanged = false; - cameraView.mapGesture(Gesture.PINCH, GestureAction.ZOOM); + + // If factor is 0, we return the same value. The controller should not be notified. + cameraView.mPinchGestureLayout.mFactor = 0f; + cameraView.dispatchTouchEvent(event); + assertFalse(mockController.mZoomChanged); + + // For larger factors, the value is scaled. The controller should be notified. + cameraView.mPinchGestureLayout.mFactor = 1f; cameraView.dispatchTouchEvent(event); assertTrue(mockController.mZoomChanged); } @@ -230,7 +239,7 @@ public class CameraViewTest extends BaseTest { when(o.getExposureCorrectionMaxValue()).thenReturn(10f); mockController.setMockCameraOptions(o); mockController.mockStarted(true); - + mockController.mExposureCorrectionChanged = false; MotionEvent event = MotionEvent.obtain(0L, 0L, 0, 0f, 0f, 0); ui(new Runnable() { @Override @@ -239,10 +248,17 @@ public class CameraViewTest extends BaseTest { public boolean onTouchEvent(MotionEvent event) { return true; } }; cameraView.mScrollGestureLayout.setGestureType(Gesture.SCROLL_HORIZONTAL); + cameraView.mapGesture(Gesture.SCROLL_HORIZONTAL, GestureAction.EXPOSURE_CORRECTION); } }); - mockController.mExposureCorrectionChanged = false; - cameraView.mapGesture(Gesture.SCROLL_HORIZONTAL, GestureAction.EXPOSURE_CORRECTION); + + // If factor is 0, we return the same value. The controller should not be notified. + cameraView.mScrollGestureLayout.mFactor = 0f; + cameraView.dispatchTouchEvent(event); + assertFalse(mockController.mExposureCorrectionChanged); + + // For larger factors, the value is scaled. The controller should be notified. + cameraView.mScrollGestureLayout.mFactor = 1f; cameraView.dispatchTouchEvent(event); assertTrue(mockController.mExposureCorrectionChanged); } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java index aaa5a23e..46e35d49 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java @@ -505,7 +505,9 @@ public class CameraView extends FrameLayout { case ZOOM: oldValue = mCameraController.getZoomValue(); newValue = source.scaleValue(oldValue, 0, 1); - mCameraController.setZoom(newValue, points, true); + if (newValue != oldValue) { + mCameraController.setZoom(newValue, points, true); + } break; case EXPOSURE_CORRECTION: @@ -513,8 +515,10 @@ public class CameraView extends FrameLayout { float minValue = options.getExposureCorrectionMinValue(); float maxValue = options.getExposureCorrectionMaxValue(); newValue = source.scaleValue(oldValue, minValue, maxValue); - float[] bounds = new float[]{minValue, maxValue}; - mCameraController.setExposureCorrection(newValue, bounds, points, true); + if (newValue != oldValue) { + float[] bounds = new float[]{minValue, maxValue}; + mCameraController.setExposureCorrection(newValue, bounds, points, true); + } break; } } diff --git a/cameraview/src/main/views/com/otaliastudios/cameraview/GestureLayout.java b/cameraview/src/main/views/com/otaliastudios/cameraview/GestureLayout.java index 6f1fcc36..5932198b 100644 --- a/cameraview/src/main/views/com/otaliastudios/cameraview/GestureLayout.java +++ b/cameraview/src/main/views/com/otaliastudios/cameraview/GestureLayout.java @@ -7,12 +7,12 @@ import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; -/** - * Using Views to address the need to draw stuff, but think about it. - * Simple classes could be enough. - */ abstract class GestureLayout extends FrameLayout { + // The number of possible values between minValue and maxValue, for the scaleValue method. + // We could make this non-static (e.g. larger granularity for exposure correction). + private final static int GRANULARITY = 50; + protected boolean mEnabled; protected Gesture mType; protected PointF[] mPoints; @@ -48,5 +48,22 @@ abstract class GestureLayout extends FrameLayout { return mPoints; } + // Implementors should call capValue at the end. public abstract float scaleValue(float currValue, float minValue, float maxValue); + + // Checks for newValue to be between minValue and maxValue, + // and checks that it is 'far enough' from the oldValue, in order + // to reduce useless updates. + protected static float capValue(float oldValue, float newValue, float minValue, float maxValue) { + if (newValue < minValue) newValue = minValue; + if (newValue > maxValue) newValue = maxValue; + + float distance = (maxValue - minValue) / (float) GRANULARITY; + float half = distance / 2; + if (newValue >= oldValue - half && newValue <= oldValue + half) { + // Too close! Return the oldValue. + return oldValue; + } + return newValue; + } } diff --git a/cameraview/src/main/views/com/otaliastudios/cameraview/PinchGestureLayout.java b/cameraview/src/main/views/com/otaliastudios/cameraview/PinchGestureLayout.java index 08c0cd62..1cb8acdb 100644 --- a/cameraview/src/main/views/com/otaliastudios/cameraview/PinchGestureLayout.java +++ b/cameraview/src/main/views/com/otaliastudios/cameraview/PinchGestureLayout.java @@ -3,12 +3,8 @@ package com.otaliastudios.cameraview; import android.content.Context; import android.graphics.PointF; import android.os.Build; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.util.AttributeSet; import android.view.MotionEvent; import android.view.ScaleGestureDetector; -import android.view.View; class PinchGestureLayout extends GestureLayout { @@ -16,7 +12,7 @@ class PinchGestureLayout extends GestureLayout { ScaleGestureDetector mDetector; private boolean mNotify; - private float mAdditionFactor = 0; + /* tests */ float mFactor = 0; public PinchGestureLayout(Context context) { @@ -32,7 +28,7 @@ class PinchGestureLayout extends GestureLayout { @Override public boolean onScale(ScaleGestureDetector detector) { mNotify = true; - mAdditionFactor = ((detector.getScaleFactor() - 1) * ADD_SENSITIVITY); + mFactor = ((detector.getScaleFactor() - 1) * ADD_SENSITIVITY); return true; } }); @@ -75,7 +71,7 @@ class PinchGestureLayout extends GestureLayout { @Override public float scaleValue(float currValue, float minValue, float maxValue) { - float add = mAdditionFactor; + float add = mFactor; // ^ This works well if minValue = 0, maxValue = 1. // Account for the different range: add *= (maxValue - minValue); @@ -87,10 +83,6 @@ class PinchGestureLayout extends GestureLayout { } else if (add < 0) { add *= (currValue - minValue); } Nope, I don't like this, it slows everything down. */ - - float newValue = currValue + add; - if (newValue < minValue) newValue = minValue; - if (newValue > maxValue) newValue = maxValue; - return newValue; + return capValue(currValue, currValue + add, minValue, maxValue); } } diff --git a/cameraview/src/main/views/com/otaliastudios/cameraview/ScrollGestureLayout.java b/cameraview/src/main/views/com/otaliastudios/cameraview/ScrollGestureLayout.java index 09cb84f9..f8e3b17a 100644 --- a/cameraview/src/main/views/com/otaliastudios/cameraview/ScrollGestureLayout.java +++ b/cameraview/src/main/views/com/otaliastudios/cameraview/ScrollGestureLayout.java @@ -1,16 +1,9 @@ package com.otaliastudios.cameraview; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; import android.content.Context; import android.graphics.PointF; -import android.util.Log; import android.view.GestureDetector; -import android.view.LayoutInflater; import android.view.MotionEvent; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.ImageView; class ScrollGestureLayout extends GestureLayout { @@ -19,7 +12,7 @@ class ScrollGestureLayout extends GestureLayout { private GestureDetector mDetector; private boolean mNotify; - private float mDistance; + /* tests */ float mFactor; public ScrollGestureLayout(Context context) { @@ -47,8 +40,8 @@ class ScrollGestureLayout extends GestureLayout { horizontal = mType == Gesture.SCROLL_HORIZONTAL; } mPoints[1].set(e2.getX(), e2.getY()); - mDistance = horizontal ? (distanceX / getWidth()) : (distanceY / getHeight()); - mDistance = horizontal ? -mDistance : mDistance; // When vertical, up = positive + mFactor = horizontal ? (distanceX / getWidth()) : (distanceY / getHeight()); + mFactor = horizontal ? -mFactor : mFactor; // When vertical, up = positive mNotify = true; return true; } @@ -80,21 +73,14 @@ class ScrollGestureLayout extends GestureLayout { @Override public float scaleValue(float currValue, float minValue, float maxValue) { - float delta = mDistance; // -1 ... 1 + float delta = mFactor; // -1 ... 1 // ^ This works well if minValue = 0, maxValue = 1. // Account for the different range: delta *= (maxValue - minValue); // -(max-min) ... (max-min) + delta *= 2; // Add some sensitivity. - // Add some sensitivity. - delta *= 2; - - // Cap - float newValue = currValue + delta; - if (newValue < minValue) newValue = minValue; - if (newValue > maxValue) newValue = maxValue; - LOG.i("curr="+currValue, "min="+minValue, "max="+maxValue, "out="+newValue); - return newValue; + return capValue(currValue, currValue + delta, minValue, maxValue); } } From c1973b0d719fa799d06d5439f8bfad8312eceaa4 Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Thu, 1 Mar 2018 19:14:35 +0100 Subject: [PATCH 5/8] Add setVideoMaxDuration() API (#172) * Add setVideoMaxDuration() API * Better tests --- README.md | 53 +++++++--- .../cameraview/CameraViewTest.java | 14 +++ .../cameraview/IntegrationTest.java | 23 ++++- .../cameraview/MockCameraController.java | 5 - .../com/otaliastudios/cameraview/Camera1.java | 36 +++---- .../com/otaliastudios/cameraview/Camera2.java | 5 - .../cameraview/CameraController.java | 22 ++++- .../otaliastudios/cameraview/CameraView.java | 97 +++++++++++++------ cameraview/src/main/res/values/attrs.xml | 2 + 9 files changed, 173 insertions(+), 84 deletions(-) diff --git a/README.md b/README.md index 4a46c8da..2e3703f3 100644 --- a/README.md +++ b/README.md @@ -139,8 +139,8 @@ ensure lower quality output. ### Capturing Video -To capture video just call `CameraView.startRecordingVideo(file)` to start, and -`CameraView.stopRecordingVideo()` to finish. Make sure you setup a `CameraListener` to handle +To capture video just call `CameraView.startCapturingVideo(file)` to start, and +`CameraView.stopCapturingVideo()` to finish. Make sure you setup a `CameraListener` to handle the video callback. ```java @@ -154,20 +154,16 @@ camera.addCameraListener(new CameraListener() { // Select output file. Make sure you have write permissions. File file = ...; +camera.startCapturingVideo(file); -// Record a 2500 ms video: -camera.startRecordingVideo(file, 2500); - -// Full version -camera.startRecordingVideo(file); -camera.postDelayed(new Runnable() { - @Override - public void run() { - // This will trigger onVideoTaken(). - camera.stopRecordingVideo(); - } -}, 2500); +// Later... stop recording. This will trigger onVideoTaken(). +camera.stopCapturingVideo(); +// You can also use one of the video constraints: +// videoMaxSize and videoMaxDuration will automatically stop recording when satisfied. +camera.setVideoMaxSize(100000); +camera.setVideoMaxDuration(5000); +camera.startCapturingVideo(file); ``` ### Other camera events @@ -406,7 +402,9 @@ Most camera parameters can be controlled through XML attributes or linked method app:cameraWhiteBalance="auto" app:cameraHdr="off" app:cameraAudio="on" - app:cameraPlaySounds="true"/> + app:cameraPlaySounds="true" + app:cameraVideoMaxSize="0" + app:cameraVideoMaxDuration="0"/> ``` |XML Attribute|Method|Values|Default Value| @@ -422,6 +420,8 @@ Most camera parameters can be controlled through XML attributes or linked method |[`cameraHdr`](#camerahdr)|`setHdr()`|`off` `on`|`off`| |[`cameraAudio`](#cameraaudio)|`setAudio()`|`off` `on`|`on`| |[`cameraPlaySounds`](#cameraplaysounds)|`setPlaySounds()`|`true` `false`|`true`| +|[`cameraVideoMaxSize`](#cameravideomaxsize)|`setVideoMaxSize()`|number|`0`| +|[`cameraVideoMaxDuration`](#cameravideomaxduration)|`setVideoMaxDuration()`|number|`0`| #### cameraSessionType @@ -547,6 +547,28 @@ cameraView.setPlaySounds(true); cameraView.setPlaySounds(false); ``` +#### cameraVideoMaxSize + +Defines the maximum size in bytes for recorded video files. +Once this size is reached, the recording will automatically stop. +Defaults to unlimited size. Use 0 or negatives to disable. + +```java +cameraView.setVideoMaxSize(100000); +cameraView.setVideoMaxSize(0); // Disable +``` + +#### cameraVideoMaxDuration + +Defines the maximum duration in milliseconds for video recordings. +Once this duration is reached, the recording will automatically stop. +Defaults to unlimited duration. Use 0 or negatives to disable. + +```java +cameraView.setVideoMaxDuration(100000); +cameraView.setVideoMaxDuration(0); // Disable +``` + ## Frame Processing We support frame processors that will receive data from the camera preview stream: @@ -606,7 +628,6 @@ Other APIs not mentioned above are provided, and are well documented and comment |`getPreviewSize()`|Returns the size of the preview surface. If CameraView was not constrained in its layout phase (e.g. it was `wrap_content`), this will return the same aspect ratio of CameraView.| |`getSnapshotSize()`|Returns `getPreviewSize()`, since a snapshot is a preview frame.| |`getPictureSize()`|Returns the size of the output picture. The aspect ratio is consistent with `getPreviewSize()`.| -|`setVideoMaxSize(long)`|Set a max file size (in bytes) for a video recording. There is no file size limit by default unless set by the user.| Take also a look at public methods in `CameraUtils`, `CameraOptions`, `ExtraProperties`. diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java index b3f14516..818cc67d 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java @@ -96,6 +96,8 @@ public class CameraViewTest extends BaseTest { assertEquals(cameraView.getLocation(), null); assertEquals(cameraView.getExposureCorrection(), 0f, 0f); assertEquals(cameraView.getZoom(), 0f, 0f); + assertEquals(cameraView.getVideoMaxDuration(), 0, 0); + assertEquals(cameraView.getVideoMaxSize(), 0, 0); // Self managed assertEquals(cameraView.getPlaySounds(), CameraView.DEFAULT_PLAY_SOUNDS); @@ -567,6 +569,18 @@ public class CameraViewTest extends BaseTest { assertEquals(result, source); } + @Test + public void testVideoMaxSize() { + cameraView.setVideoMaxSize(5000); + assertEquals(cameraView.getVideoMaxSize(), 5000); + } + + @Test + public void testVideoMaxDuration() { + cameraView.setVideoMaxDuration(5000); + assertEquals(cameraView.getVideoMaxDuration(), 5000); + } + //endregion //region Lists of listeners and processors diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/IntegrationTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/IntegrationTest.java index f1c5c2dc..4e42ce62 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/IntegrationTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/IntegrationTest.java @@ -5,6 +5,7 @@ import android.graphics.Bitmap; import android.graphics.PointF; import android.hardware.Camera; import android.os.Build; +import android.os.Handler; import android.support.test.filters.MediumTest; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; @@ -129,9 +130,9 @@ public class IntegrationTest extends BaseTest { doEndTask(video, true).when(listener).onVideoTaken(any(File.class)); Boolean result = video.await(8000); if (expectSuccess) { - assertNotNull("Can take video", result); + assertNotNull("Should end video", result); } else { - assertNull("Should not take video", result); + assertNull("Should not end video", result); } } @@ -463,6 +464,24 @@ public class IntegrationTest extends BaseTest { waitForVideoEnd(false); } + @Test + public void testEndVideo_withMaxSize() { + camera.setSessionType(SessionType.VIDEO); + camera.setVideoMaxSize(500*1000); // 0.5 mb + waitForOpen(true); + waitForVideoStart(); + waitForVideoEnd(true); + } + + @Test + public void testEndVideo_withMaxDuration() { + camera.setSessionType(SessionType.VIDEO); + camera.setVideoMaxDuration(4000); + waitForOpen(true); + waitForVideoStart(); + waitForVideoEnd(true); + } + //endregion //region startAutoFocus diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/MockCameraController.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/MockCameraController.java index a9e16af4..5a4b8f08 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/MockCameraController.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/MockCameraController.java @@ -125,11 +125,6 @@ public class MockCameraController extends CameraController { public void onBufferAvailable(byte[] buffer) { } - @Override - void setVideoMaxSize(long videoMaxSizeInBytes) { - - } - @Override void setPlaySounds(boolean playSounds) { diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java index 893dab91..ed0bede5 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java @@ -696,31 +696,28 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera } if (mLocation != null) { - mMediaRecorder.setLocation((float) mLocation.getLatitude(), + mMediaRecorder.setLocation( + (float) mLocation.getLatitude(), (float) mLocation.getLongitude()); } mMediaRecorder.setOutputFile(mVideoFile.getAbsolutePath()); mMediaRecorder.setOrientationHint(computeSensorToOutputOffset()); - //If the user sets a max file size, set it to the max file size - if (mVideoMaxSizeInBytes > 0) { - mMediaRecorder.setMaxFileSize(mVideoMaxSizeInBytes); - - //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: { - endVideoImmediately(); - break; - } - } + 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: + endVideoImmediately(); + break; } - }); - } + } + }); // Not needed. mMediaRecorder.setPreviewDisplay(mPreview.getSurface()); } @@ -879,11 +876,6 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera return result; } - @Override - void setVideoMaxSize(long videoMaxSizeInBytes) { - mVideoMaxSizeInBytes = videoMaxSizeInBytes; - } - @Override void setPlaySounds(boolean playSounds) { final boolean old = mPlaySounds; diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera2.java b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera2.java index 9b827890..396fe150 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera2.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera2.java @@ -115,11 +115,6 @@ class Camera2 extends CameraController { } - @Override - void setVideoMaxSize(long videoMaxSizeInBytes) { - - } - @Override void setPlaySounds(boolean playSounds) { diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java index d98f9e96..5dd2350e 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java @@ -55,6 +55,8 @@ abstract class CameraController implements protected SizeSelector mPictureSizeSelector; protected MediaRecorder mMediaRecorder; protected File mVideoFile; + protected long mVideoMaxSize; + protected int mVideoMaxDuration; protected Size mPictureSize; protected Size mPreviewSize; protected int mPreviewFormat; @@ -67,7 +69,6 @@ abstract class CameraController implements protected boolean mIsCapturingVideo = false; protected int mState = STATE_STOPPED; - protected long mVideoMaxSizeInBytes = 0; // Used for testing. Task mZoomTask = new Task<>(); @@ -276,6 +277,15 @@ abstract class CameraController implements mPictureSizeSelector = selector; } + final void setVideoMaxSize(long videoMaxSizeBytes) { + mVideoMaxSize = videoMaxSizeBytes; + } + + final void setVideoMaxDuration(int videoMaxDurationMillis) { + mVideoMaxDuration = videoMaxDurationMillis; + } + + //endregion //region Abstract setters and APIs @@ -320,8 +330,6 @@ abstract class CameraController implements abstract void startAutoFocus(@Nullable Gesture gesture, PointF point); - abstract void setVideoMaxSize(long videoMaxSizeInBytes); - abstract void setPlaySounds(boolean playSounds); //endregion @@ -354,6 +362,14 @@ abstract class CameraController implements return mVideoQuality; } + final long getVideoMaxSize() { + return mVideoMaxSize; + } + + final int getVideoMaxDuration() { + return mVideoMaxDuration; + } + final SessionType getSessionType() { return mSessionType; } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java index 46e35d49..9e0705e1 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java @@ -107,6 +107,8 @@ public class CameraView extends FrameLayout { SessionType sessionType = SessionType.fromValue(a.getInteger(R.styleable.CameraView_cameraSessionType, SessionType.DEFAULT.value())); Hdr hdr = Hdr.fromValue(a.getInteger(R.styleable.CameraView_cameraHdr, Hdr.DEFAULT.value())); Audio audio = Audio.fromValue(a.getInteger(R.styleable.CameraView_cameraAudio, Audio.DEFAULT.value())); + long videoMaxSize = (long) a.getFloat(R.styleable.CameraView_cameraVideoMaxSize, 0); + int videoMaxDuration = a.getInteger(R.styleable.CameraView_cameraVideoMaxDuration, 0); // Size selectors List constraints = new ArrayList<>(3); @@ -145,9 +147,6 @@ public class CameraView extends FrameLayout { GestureAction scrollHorizontalGesture = GestureAction.fromValue(a.getInteger(R.styleable.CameraView_cameraGestureScrollHorizontal, GestureAction.DEFAULT_SCROLL_HORIZONTAL.value())); GestureAction scrollVerticalGesture = GestureAction.fromValue(a.getInteger(R.styleable.CameraView_cameraGestureScrollVertical, GestureAction.DEFAULT_SCROLL_VERTICAL.value())); - //Get max size - float cameraVideoMaxSize = a.getFloat(R.styleable.CameraView_cameraVideoMaxSize, -1); - a.recycle(); // Components @@ -182,6 +181,8 @@ public class CameraView extends FrameLayout { setHdr(hdr); setAudio(audio); setPictureSize(selector); + setVideoMaxSize(videoMaxSize); + setVideoMaxDuration(videoMaxDuration); // Apply gestures mapGesture(Gesture.TAP, tapGesture); @@ -190,11 +191,6 @@ public class CameraView extends FrameLayout { mapGesture(Gesture.SCROLL_HORIZONTAL, scrollHorizontalGesture); mapGesture(Gesture.SCROLL_VERTICAL, scrollVerticalGesture); - //Set camera video maxSize - if(cameraVideoMaxSize > 0) { - setVideoMaxSize((long)cameraVideoMaxSize); - } - if (!isInEditMode()) { mOrientationHelper = new OrientationHelper(context, mCameraCallbacks); } @@ -1212,8 +1208,7 @@ public class CameraView extends FrameLayout { /** - * Starts recording a video with selected options, in a file called - * "video.mp4" in the default folder. + * Starts recording a video, in a file called "video.mp4" in the default folder. * This is discouraged, please use {@link #startCapturingVideo(File)} instead. * * @deprecated see {@link #startCapturingVideo(File)} @@ -1225,7 +1220,7 @@ public class CameraView extends FrameLayout { /** - * Starts recording a video with selected options. Video will be written to the given file, + * Starts recording a video. Video will be written to the given file, * so callers should ensure they have appropriate permissions to write to the file. * * @param file a file where the video will be saved @@ -1246,27 +1241,29 @@ public class CameraView extends FrameLayout { /** - * Starts recording a video with selected options. Video will be written to the given file, + * Starts recording a video. Video will be written to the given file, * so callers should ensure they have appropriate permissions to write to the file. - * Recording will be automatically stopped after durationMillis, unless - * {@link #stopCapturingVideo()} is not called meanwhile. + * Recording will be automatically stopped after the given duration, overriding + * temporarily any duration limit set by {@link #setVideoMaxDuration(int)}. * * @param file a file where the video will be saved - * @param durationMillis video max duration + * @param durationMillis recording max duration * - * @throws IllegalArgumentException if durationMillis is less than 500 milliseconds + * @deprecated use {@link #setVideoMaxDuration(int)} instead. */ + @Deprecated public void startCapturingVideo(File file, long durationMillis) { - if (durationMillis < 500) { - throw new IllegalArgumentException("Video duration can't be < 500 milliseconds"); - } - startCapturingVideo(file); - mUiHandler.postDelayed(new Runnable() { + // TODO: v2: change signature to int, or remove (better). + final int old = getVideoMaxDuration(); + addCameraListener(new CameraListener() { @Override - public void run() { - stopCapturingVideo(); + public void onVideoTaken(File video) { + setVideoMaxDuration(old); + removeCameraListener(this); } - }, durationMillis); + }); + setVideoMaxDuration((int) durationMillis); + startCapturingVideo(file); } @@ -1343,9 +1340,6 @@ public class CameraView extends FrameLayout { } } - //endregion - - //region Sounds @SuppressLint("NewApi") private void playSound(int soundType) { @@ -1355,6 +1349,7 @@ public class CameraView extends FrameLayout { } } + /** * Controls whether CameraView should play sound effects on certain * events (picture taken, focus complete). Note that: @@ -1368,6 +1363,7 @@ public class CameraView extends FrameLayout { mCameraController.setPlaySounds(playSounds); } + /** * Gets the current sound effect behavior. * @@ -1378,16 +1374,55 @@ 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. + * Sets the maximum size in bytes for recorded video files. + * Once this size is reached, the recording will automatically stop. + * Defaults to unlimited size. Use 0 or negatives to disable. * - * @param videoMaxSizeInBytes The maximum size of videos in bytes + * @param videoMaxSizeInBytes The maximum video size in bytes */ - public void setVideoMaxSize(long videoMaxSizeInBytes){ + public void setVideoMaxSize(long videoMaxSizeInBytes) { mCameraController.setVideoMaxSize(videoMaxSizeInBytes); } + + /** + * Returns the maximum size in bytes for recorded video files, or 0 + * if no size was set. + * + * @see #setVideoMaxSize(long) + * @return the maximum size in bytes + */ + public long getVideoMaxSize() { + return mCameraController.getVideoMaxSize(); + } + + + /** + * Sets the maximum duration in milliseconds for video recordings. + * Once this duration is reached, the recording will automatically stop. + * Defaults to unlimited duration. Use 0 or negatives to disable. + * + * @param videoMaxDurationMillis The maximum video duration in milliseconds + */ + public void setVideoMaxDuration(int videoMaxDurationMillis) { + mCameraController.setVideoMaxDuration(videoMaxDurationMillis); + } + + + /** + * Returns the maximum duration in milliseconds for video recordings, or 0 + * if no limit was set. + * + * @see #setVideoMaxDuration(int) + * @return the maximum duration in milliseconds + */ + public int getVideoMaxDuration() { + return mCameraController.getVideoMaxDuration(); + } + + /** * Returns true if the camera is currently recording a video * @return boolean indicating if the camera is recording a video diff --git a/cameraview/src/main/res/values/attrs.xml b/cameraview/src/main/res/values/attrs.xml index d52abdd1..f38db8ea 100644 --- a/cameraview/src/main/res/values/attrs.xml +++ b/cameraview/src/main/res/values/attrs.xml @@ -112,6 +112,8 @@ + +