Merge branch 'master' into master

pull/165/head
Nick Hope 7 years ago committed by GitHub
commit 14aaac0823
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 32
      CHANGELOG.md
  2. 81
      README.md
  3. 2
      cameraview/build.gradle
  4. 49
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java
  5. 23
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/IntegrationTest.java
  6. 36
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/MapperTest.java
  7. 5
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/MockCameraController.java
  8. 46
      cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java
  9. 5
      cameraview/src/main/java/com/otaliastudios/cameraview/Camera2.java
  10. 31
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java
  11. 135
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
  12. 11
      cameraview/src/main/java/com/otaliastudios/cameraview/Mapper.java
  13. 49
      cameraview/src/main/options/com/otaliastudios/cameraview/VideoCodec.java
  14. 8
      cameraview/src/main/res/values/attrs.xml
  15. 25
      cameraview/src/main/views/com/otaliastudios/cameraview/GestureLayout.java
  16. 16
      cameraview/src/main/views/com/otaliastudios/cameraview/PinchGestureLayout.java
  17. 26
      cameraview/src/main/views/com/otaliastudios/cameraview/ScrollGestureLayout.java
  18. 3
      demo/src/main/AndroidManifest.xml

@ -1,3 +1,22 @@
## v1.5.0
- New: set encoder for video recordings with `cameraVideoCodec` ([#174][174])
- New: set max duration for videos with `cameraVideoMaxDuration` ([#172][172])
- Enhancement: reduced lag with continuous gestures (ev, zoom) ([#170][170])
- Bug: tap to focus was crashing on some devices ([#167][167])
- Bug: capturePicture was breaking if followed by another event soon after ([#173][173])
https://github.com/natario1/CameraView/compare/v1.4.2...v1.5.0
### 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 +75,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 +93,14 @@ 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
[167]: https://github.com/natario1/CameraView/pull/167
[170]: https://github.com/natario1/CameraView/pull/170
[172]: https://github.com/natario1/CameraView/pull/172
[173]: https://github.com/natario1/CameraView/pull/173
[174]: https://github.com/natario1/CameraView/pull/174

@ -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.5.0'
```
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()
}
}
```
<p>
@ -137,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
@ -152,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
@ -401,10 +399,13 @@ Most camera parameters can be controlled through XML attributes or linked method
app:cameraCropOutput="false"
app:cameraJpegQuality="100"
app:cameraVideoQuality="480p"
app:cameraVideoCodec="deviceDefault"
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|
@ -416,10 +417,13 @@ Most camera parameters can be controlled through XML attributes or linked method
|[`cameraCropOutput`](#cameracropoutput)|`setCropOutput()`|`true` `false`|`false`|
|[`cameraJpegQuality`](#camerajpegquality)|`setJpegQuality()`|`0 < n <= 100`|`100`|
|[`cameraVideoQuality`](#cameravideoquality)|`setVideoQuality()`|`lowest` `highest` `maxQvga` `max480p` `max720p` `max1080p` `max2160p`|`max480p`|
|[`cameraVideoCodec`](#cameravideocodec)|`setVideoCodec()`|`deviceDefault` `h263` `h264`|`deviceDefault`|
|[`cameraWhiteBalance`](#camerawhitebalance)|`setWhiteBalance()`|`auto` `incandescent` `fluorescent` `daylight` `cloudy`|`auto`|
|[`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
@ -502,6 +506,16 @@ cameraView.setVideoQuality(VideoQuality.MAX_1080P);
cameraView.setVideoQuality(VideoQuality.MAX_2160P);
```
#### cameraVideoCodec
Sets the encoder for video recordings.
```java
cameraView.setVideoCodec(VideoCodec.DEVICE_DEFAULT);
cameraView.setVideoCodec(VideoCodec.H_263);
cameraView.setVideoCodec(VideoCodec.H_264);
```
#### cameraWhiteBalance
Sets the desired white balance for the current session.
@ -545,6 +559,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:
@ -604,7 +640,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`.

@ -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.5.0'
group = 'com.otaliastudios'
//region android dependencies

@ -93,9 +93,12 @@ public class CameraViewTest extends BaseTest {
assertEquals(cameraView.getHdr(), Hdr.DEFAULT);
assertEquals(cameraView.getAudio(), Audio.DEFAULT);
assertEquals(cameraView.getVideoQuality(), VideoQuality.DEFAULT);
assertEquals(cameraView.getVideoCodec(), VideoCodec.DEFAULT);
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);
@ -206,6 +209,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 +218,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 +242,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 +251,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);
}
@ -542,6 +561,14 @@ public class CameraViewTest extends BaseTest {
assertEquals(cameraView.getVideoQuality(), VideoQuality.LOWEST);
}
@Test
public void testVideoCodec() {
cameraView.set(VideoCodec.H_263);
assertEquals(cameraView.getVideoCodec(), VideoCodec.H_263);
cameraView.set(VideoCodec.H_264);
assertEquals(cameraView.getVideoCodec(), VideoCodec.H_264);
}
@Test
public void testPictureSizeSelector() {
SizeSelector source = SizeSelectors.minHeight(50);
@ -551,6 +578,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

@ -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

@ -0,0 +1,36 @@
package com.otaliastudios.cameraview;
import android.hardware.Camera;
import android.media.MediaRecorder;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.assertEquals;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class MapperTest extends BaseTest {
private Mapper mapper = new Mapper() {
<T> T map(Flash flash) { return null; }
<T> T map(Facing facing) { return null; }
<T> T map(WhiteBalance whiteBalance) { return null; }
<T> T map(Hdr hdr) { return null; }
<T> Flash unmapFlash(T cameraConstant) { return null; }
<T> Facing unmapFacing(T cameraConstant) { return null; }
<T> WhiteBalance unmapWhiteBalance(T cameraConstant) { return null; }
<T> Hdr unmapHdr(T cameraConstant) { return null; }
};
@Test
public void testMap() {
assertEquals(mapper.map(VideoCodec.DEVICE_DEFAULT), MediaRecorder.VideoEncoder.DEFAULT);
assertEquals(mapper.map(VideoCodec.H_263), MediaRecorder.VideoEncoder.H263);
assertEquals(mapper.map(VideoCodec.H_264), MediaRecorder.VideoEncoder.H264);
}
}

@ -125,11 +125,6 @@ public class MockCameraController extends CameraController {
public void onBufferAvailable(byte[] buffer) {
}
@Override
void setVideoMaxSize(long videoMaxSizeInBytes) {
}
@Override
void setPlaySounds(boolean playSounds) {

@ -43,8 +43,10 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
}
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);
}
@ -233,6 +235,8 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
mPreviewSize = null;
mPictureSize = null;
mIsBound = false;
mIsCapturingImage = false;
mIsCapturingVideo = false;
LOG.w("onStop:", "Clean up.", "Returning.");
if (error != null) throw new CameraException(error);
}
@ -690,7 +694,7 @@ class Camera1 extends CameraController implements Camera.PreviewCallback, Camera
mMediaRecorder.setOutputFormat(profile.fileFormat);
mMediaRecorder.setVideoFrameRate(profile.videoFrameRate);
mMediaRecorder.setVideoSize(profile.videoFrameWidth, profile.videoFrameHeight);
mMediaRecorder.setVideoEncoder(profile.videoCodec);
mMediaRecorder.setVideoEncoder(mMapper.map(mVideoCodec));
mMediaRecorder.setVideoEncodingBitRate(profile.videoBitRate);
if (mAudio == Audio.ON) {
mMediaRecorder.setAudioChannels(profile.audioChannels);
@ -700,31 +704,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());
}
@ -884,11 +885,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;

@ -115,11 +115,6 @@ class Camera2 extends CameraController {
}
@Override
void setVideoMaxSize(long videoMaxSizeInBytes) {
}
@Override
void setPlaySounds(boolean playSounds) {

@ -39,6 +39,7 @@ abstract class CameraController implements
protected Flash mFlash;
protected WhiteBalance mWhiteBalance;
protected VideoQuality mVideoQuality;
protected VideoCodec mVideoCodec;
protected SessionType mSessionType;
protected Hdr mHdr;
protected Location mLocation;
@ -55,6 +56,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 +70,6 @@ abstract class CameraController implements
protected boolean mIsCapturingVideo = false;
protected int mState = STATE_STOPPED;
protected long mVideoMaxSizeInBytes = 0;
// Used for testing.
Task<Void> mZoomTask = new Task<>();
@ -276,6 +278,19 @@ abstract class CameraController implements
mPictureSizeSelector = selector;
}
final void setVideoMaxSize(long videoMaxSizeBytes) {
mVideoMaxSize = videoMaxSizeBytes;
}
final void setVideoMaxDuration(int videoMaxDurationMillis) {
mVideoMaxDuration = videoMaxDurationMillis;
}
final void setVideoCodec(VideoCodec codec) {
mVideoCodec = codec;
}
//endregion
//region Abstract setters and APIs
@ -320,8 +335,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 +367,18 @@ abstract class CameraController implements
return mVideoQuality;
}
final VideoCodec getVideoCodec() {
return mVideoCodec;
}
final long getVideoMaxSize() {
return mVideoMaxSize;
}
final int getVideoMaxDuration() {
return mVideoMaxDuration;
}
final SessionType getSessionType() {
return mSessionType;
}

@ -107,6 +107,9 @@ 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()));
VideoCodec codec = VideoCodec.fromValue(a.getInteger(R.styleable.CameraView_cameraVideoCodec, VideoCodec.DEFAULT.value()));
long videoMaxSize = (long) a.getFloat(R.styleable.CameraView_cameraVideoMaxSize, 0);
int videoMaxDuration = a.getInteger(R.styleable.CameraView_cameraVideoMaxDuration, 0);
// Size selectors
List<SizeSelector> constraints = new ArrayList<>(3);
@ -145,9 +148,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 +182,9 @@ public class CameraView extends FrameLayout {
setHdr(hdr);
setAudio(audio);
setPictureSize(selector);
setVideoCodec(codec);
setVideoMaxSize(videoMaxSize);
setVideoMaxDuration(videoMaxDuration);
// Apply gestures
mapGesture(Gesture.TAP, tapGesture);
@ -190,11 +193,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);
}
@ -505,7 +503,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 +513,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;
}
}
@ -652,6 +654,8 @@ public class CameraView extends FrameLayout {
setVideoQuality((VideoQuality) control);
} else if (control instanceof WhiteBalance) {
setWhiteBalance((WhiteBalance) control);
} else if (control instanceof VideoCodec) {
setVideoCodec((VideoCodec) control);
}
}
@ -1208,8 +1212,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)}
@ -1221,7 +1224,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
@ -1242,27 +1245,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);
}
@ -1339,9 +1344,6 @@ public class CameraView extends FrameLayout {
}
}
//endregion
//region Sounds
@SuppressLint("NewApi")
private void playSound(int soundType) {
@ -1351,6 +1353,7 @@ public class CameraView extends FrameLayout {
}
}
/**
* Controls whether CameraView should play sound effects on certain
* events (picture taken, focus complete). Note that:
@ -1364,6 +1367,7 @@ public class CameraView extends FrameLayout {
mCameraController.setPlaySounds(playSounds);
}
/**
* Gets the current sound effect behavior.
*
@ -1374,16 +1378,79 @@ 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 encoder for video recordings.
* Defaults to {@link VideoCodec#DEVICE_DEFAULT}.
*
* @see VideoCodec#DEVICE_DEFAULT
* @see VideoCodec#H_263
* @see VideoCodec#H_264
*
* @param videoMaxSizeInBytes The maximum size of videos in bytes
* @param codec requested video codec
*/
public void setVideoCodec(VideoCodec codec) {
mCameraController.setVideoCodec(codec);
}
/**
* Gets the current encoder for video recordings.
* @return the current video codec
*/
public void setVideoMaxSize(long videoMaxSizeInBytes){
public VideoCodec getVideoCodec() {
return mCameraController.getVideoCodec();
}
/**
* 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 video size in bytes
*/
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

@ -1,6 +1,7 @@
package com.otaliastudios.cameraview;
import android.hardware.Camera;
import android.media.MediaRecorder;
import android.os.Build;
import java.util.HashMap;
@ -16,6 +17,16 @@ abstract class Mapper {
abstract <T> WhiteBalance unmapWhiteBalance(T cameraConstant);
abstract <T> Hdr unmapHdr(T cameraConstant);
int map(VideoCodec codec) {
switch (codec) {
case DEVICE_DEFAULT: return MediaRecorder.VideoEncoder.DEFAULT;
case H_263: return MediaRecorder.VideoEncoder.H263;
case H_264: return MediaRecorder.VideoEncoder.H264;
default: return MediaRecorder.VideoEncoder.DEFAULT;
}
}
static class Mapper1 extends Mapper {
private static final HashMap<Flash, String> FLASH = new HashMap<>();

@ -0,0 +1,49 @@
package com.otaliastudios.cameraview;
/**
* Constants for selecting the encoder of video recordings.
* https://developer.android.com/guide/topics/media/media-formats.html#video-formats
*
* @see CameraView#setVideoCodec(VideoCodec)
*/
public enum VideoCodec implements Control {
/**
* Let the device choose its codec.
*/
DEVICE_DEFAULT(0),
/**
* The H.263 codec.
*/
H_263(1),
/**
* The H.264 codec.
*/
H_264(2);
static final VideoCodec DEFAULT = DEVICE_DEFAULT;
private int value;
VideoCodec(int value) {
this.value = value;
}
int value() {
return value;
}
static VideoCodec fromValue(int value) {
VideoCodec[] list = VideoCodec.values();
for (VideoCodec action : list) {
if (action.value() == value) {
return action;
}
}
return null;
}
}

@ -112,6 +112,14 @@
<attr name="cameraVideoMaxSize" format="float" />
<attr name="cameraVideoMaxDuration" format="integer" />
<attr name="cameraVideoCodec" format="enum">
<enum name="deviceDefault" value="0" />
<enum name="h263" value="1" />
<enum name="h264" value="2" />
</attr>
<!-- deprecated attr name="cameraZoomMode" format="enum">
<enum name="off" value="0" />
<enum name="pinch" value="1" />

@ -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;
}
}

@ -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);
}
}

@ -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);
}
}

@ -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">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />

Loading…
Cancel
Save