diff --git a/README.md b/README.md index 9c58db39..3f139f44 100644 --- a/README.md +++ b/README.md @@ -295,7 +295,8 @@ Most camera parameters can be controlled through XML attributes or linked method app:cameraJpegQuality="100" app:cameraVideoQuality="480p" app:cameraWhiteBalance="auto" - app:cameraHdr="off" /> + app:cameraHdr="off" + app:cameraAudio="on"/> ``` |XML Attribute|Method|Values|Default Value| @@ -309,6 +310,7 @@ Most camera parameters can be controlled through XML attributes or linked method |[`cameraVideoQuality`](#cameravideoquality)|`setVideoQuality()`|`lowest` `highest` `maxQvga` `max480p` `max720p` `max1080p` `max2160p`|`max480p`| |[`cameraWhiteBalance`](#camerawhitebalance)|`setWhiteBalance()`|`auto` `incandescent` `fluorescent` `daylight` `cloudy`|`auto`| |[`cameraHdr`](#camerahdr)|`setHdr()`|`off` `on`|`off`| +|[`cameraAudio`](#cameraaudio)|`setAudio()`|`off` `on`|`on`| #### cameraSessionType @@ -404,6 +406,15 @@ cameraView.setHdr(Hdr.OFF); cameraView.setHdr(Hdr.ON); ``` +#### cameraAudio + +Turns on or off audio stream. + +```java +cameraView.setAudio(Audio.OFF); +cameraView.setAudio(Audio.ON); +``` + ## Other APIs Other APIs not mentioned above are provided, and are well documented and commented in code. @@ -435,15 +446,15 @@ Take also a look at public methods in `CameraUtils`, `CameraOptions`, `ExtraProp `CameraView` needs two permissions: - `android.permission.CAMERA` : required for capturing pictures and videos -- `android.permission.RECORD_AUDIO` : required for capturing videos +- `android.permission.RECORD_AUDIO` : required for capturing videos with `Audio.ON` (the default) -You can handle permissions yourself and then call `CameraView.start()` once they are acquired. If they are not, `CameraView` will request permissions to the user based on the `sessionType` that was set. In that case, you can restart the camera if you have a successful response from `onRequestPermissionResults()`. +You can handle permissions yourself and then call `CameraView.start()` once they are acquired. If they are not, `CameraView` will request permissions to the user based on whether they are needed. In that case, you can restart the camera if you have a successful response from `onRequestPermissionResults()`. ## Manifest file The library manifest file is not strict and only asks for camera permissions. This means that: -- If you wish to record videos, you should also add `android.permission.RECORD_AUDIO` to required permissions +- If you wish to record videos with `Audio.ON` (the default), you should also add `android.permission.RECORD_AUDIO` to required permissions ```xml diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java index ac856644..82e819f5 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/CameraViewTest.java @@ -49,7 +49,7 @@ public class CameraViewTest extends BaseTest { } @Override - protected boolean checkPermissions(SessionType sessionType) { + protected boolean checkPermissions(SessionType sessionType, Audio audio) { return hasPermissions; } }; diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/MockCameraController.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/MockCameraController.java index 5ab2ff9d..8aeda053 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/MockCameraController.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/MockCameraController.java @@ -81,6 +81,11 @@ public class MockCameraController extends CameraController { mHdr = hdr; } + @Override + void setAudio(Audio audio) { + mAudio = audio; + } + @Override void setLocation(Location location) { mLocation = location; diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java index ed76dd0c..a1975322 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera1.java @@ -308,6 +308,17 @@ class Camera1 extends CameraController { return false; } + + @Override + void setAudio(Audio audio) { + if (mAudio != audio) { + if (mIsCapturingVideo) { + LOG.w("Changing audio mode while recording. Changes will take place starting from next video"); + } + mAudio = audio; + } + } + @Override void setFlash(Flash flash) { Flash old = mFlash; @@ -617,13 +628,25 @@ class Camera1 extends CameraController { mMediaRecorder.setCamera(mCamera); mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); - mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); + + CamcorderProfile profile = getCamcorderProfile(mVideoQuality); + if (mAudio == Audio.ON) { + mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); + mMediaRecorder.setProfile(profile); + } else { + // Set all values contained in profile except audio settings + mMediaRecorder.setOutputFormat(profile.fileFormat); + mMediaRecorder.setVideoEncoder(profile.videoCodec); + mMediaRecorder.setVideoEncodingBitRate(profile.videoBitRate); + mMediaRecorder.setVideoFrameRate(profile.videoFrameRate); + mMediaRecorder.setVideoSize(profile.videoFrameWidth, profile.videoFrameHeight); + } + if (mLocation != null) { mMediaRecorder.setLocation((float) mLocation.getLatitude(), (float) mLocation.getLongitude()); } - mMediaRecorder.setProfile(getCamcorderProfile(mVideoQuality)); mMediaRecorder.setOutputFile(mVideoFile.getAbsolutePath()); mMediaRecorder.setOrientationHint(computeExifRotation()); // Not needed. mMediaRecorder.setPreviewDisplay(mPreview.getSurface()); diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera2.java b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera2.java index 531a2139..575e59a0 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/Camera2.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/Camera2.java @@ -81,6 +81,11 @@ class Camera2 extends CameraController { } + @Override + void setAudio(Audio audio) { + + } + @Override void setLocation(Location location) { diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java index c12f7f9d..a55cdea1 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraController.java @@ -29,6 +29,7 @@ abstract class CameraController implements Preview.SurfaceCallback { protected SessionType mSessionType; protected Hdr mHdr; protected Location mLocation; + protected Audio mAudio; protected Size mCaptureSize; protected Size mPreviewSize; @@ -204,6 +205,8 @@ abstract class CameraController implements Preview.SurfaceCallback { // If closed, keep. If opened, check supported and apply. abstract void setLocation(Location location); + abstract void setAudio(Audio audio); + // Throw if capturing. If in video session, recompute capture size, and, if needed, preview size. abstract void setVideoQuality(VideoQuality videoQuality); @@ -266,6 +269,10 @@ abstract class CameraController implements Preview.SurfaceCallback { return mLocation; } + final Audio getAudio() { + return mAudio; + } + final Size getCaptureSize() { return mCaptureSize; } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java index de7d5b1a..bfc95b99 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java @@ -100,6 +100,7 @@ public class CameraView extends FrameLayout { VideoQuality videoQuality = VideoQuality.fromValue(a.getInteger(R.styleable.CameraView_cameraVideoQuality, VideoQuality.DEFAULT.value())); 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())); // Gestures GestureAction tapGesture = GestureAction.fromValue(a.getInteger(R.styleable.CameraView_cameraGestureTap, GestureAction.DEFAULT_TAP.value())); @@ -138,6 +139,7 @@ public class CameraView extends FrameLayout { setWhiteBalance(whiteBalance); setGrid(grid); setHdr(hdr); + setAudio(audio); // Apply gestures mapGesture(Gesture.TAP, tapGesture); @@ -488,7 +490,7 @@ public class CameraView extends FrameLayout { public void start() { if (!isEnabled()) return; - if (checkPermissions(getSessionType())) { + if (checkPermissions(getSessionType(), getAudio())) { // Update display orientation for current CameraController mOrientationHelper.enable(getContext()); mCameraController.start(); @@ -502,31 +504,21 @@ public class CameraView extends FrameLayout { * @return true if we can go on, false otherwise. */ @SuppressLint("NewApi") - protected boolean checkPermissions(SessionType sessionType) { - checkPermissionsManifestOrThrow(sessionType); - boolean api23 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M; - int cameraCheck, audioCheck; - if (!api23) { - cameraCheck = PackageManager.PERMISSION_GRANTED; - audioCheck = PackageManager.PERMISSION_GRANTED; - } else { - cameraCheck = getContext().checkSelfPermission(Manifest.permission.CAMERA); - audioCheck = getContext().checkSelfPermission(Manifest.permission.RECORD_AUDIO); - } - switch (sessionType) { - case VIDEO: - if (cameraCheck != PackageManager.PERMISSION_GRANTED || audioCheck != PackageManager.PERMISSION_GRANTED) { - requestPermissions(true, true); - return false; - } - break; + protected boolean checkPermissions(SessionType sessionType, Audio audio) { + checkPermissionsManifestOrThrow(sessionType, audio); + // Manifest is OK at this point. Let's check runtime permissions. + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return true; - case PICTURE: - if (cameraCheck != PackageManager.PERMISSION_GRANTED) { - requestPermissions(true, false); - return false; - } - break; + Context c = getContext(); + boolean needsCamera = true; + boolean needsAudio = sessionType == SessionType.VIDEO && audio == Audio.ON; + + needsCamera = needsCamera && c.checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED; + needsAudio = needsAudio && c.checkSelfPermission(Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED; + + if (needsCamera || needsAudio) { + requestPermissions(needsCamera, needsAudio); + return false; } return true; } @@ -537,8 +529,8 @@ public class CameraView extends FrameLayout { * If the developer did not add this to its manifest, throw and fire warnings. * (Hoping this is not caught elsewhere... we should test). */ - private void checkPermissionsManifestOrThrow(SessionType sessionType) { - if (sessionType == SessionType.VIDEO) { + private void checkPermissionsManifestOrThrow(SessionType sessionType, Audio audio) { + if (sessionType == SessionType.VIDEO && audio == Audio.ON) { try { PackageManager manager = getContext().getPackageManager(); PackageInfo info = manager.getPackageInfo(getContext().getPackageName(), PackageManager.GET_PERMISSIONS); @@ -866,6 +858,43 @@ public class CameraView extends FrameLayout { } + /** + * Controls the audio mode. + * + * @see Audio#OFF + * @see Audio#ON + * + * @param audio desired audio value + */ + public void setAudio(Audio audio) { + + if (audio == getAudio() || isStopped()) { + // Check did took place, or will happen on start(). + mCameraController.setAudio(audio); + + } else if (checkPermissions(getSessionType(), audio)) { + // Camera is running. Pass. + mCameraController.setAudio(audio); + + } else { + // This means that the audio permission is being asked. + // Stop the camera so it can be restarted by the developer onPermissionResult. + // Developer must also set the audio value again... + // Not ideal but good for now. + stop(); + } + } + + + /** + * Gets the current audio value. + * @return the current audio value + */ + public Audio getAudio() { + return mCameraController.getAudio(); + } + + /** * Starts an autofocus process at the given coordinates, with respect * to the view width and height. @@ -897,7 +926,7 @@ public class CameraView extends FrameLayout { // Check did took place, or will happen on start(). mCameraController.setSessionType(sessionType); - } else if (checkPermissions(sessionType)) { + } else if (checkPermissions(sessionType, getAudio())) { // Camera is running. CameraImpl setSessionType will do the trick. mCameraController.setSessionType(sessionType); diff --git a/cameraview/src/main/options/com/otaliastudios/cameraview/Audio.java b/cameraview/src/main/options/com/otaliastudios/cameraview/Audio.java new file mode 100644 index 00000000..e1f4f6af --- /dev/null +++ b/cameraview/src/main/options/com/otaliastudios/cameraview/Audio.java @@ -0,0 +1,42 @@ +package com.otaliastudios.cameraview; + + +/** + * Audio values indicate whether to record audio stream when record video. + * + * @see CameraView#setAudio(Audio) + */ +public enum Audio { + + /** + * No Audio. + */ + OFF(0), + + /** + * With Audio. + */ + ON(1); + + final static Audio DEFAULT = ON; + + private int value; + + Audio(int value) { + this.value = value; + } + + int value() { + return value; + } + + static Audio fromValue(int value) { + Audio[] list = Audio.values(); + for (Audio action : list) { + if (action.value() == value) { + return action; + } + } + return null; + } +} diff --git a/cameraview/src/main/res/values/attrs.xml b/cameraview/src/main/res/values/attrs.xml index b2f41d80..30050a1e 100644 --- a/cameraview/src/main/res/values/attrs.xml +++ b/cameraview/src/main/res/values/attrs.xml @@ -91,6 +91,11 @@ + + + + +