Skip to content

Commit

Permalink
Merge pull request react-native-camera#2748 from cristianoccazinsp/fi…
Browse files Browse the repository at this point in the history
…x-android-shutter-sound

Fix android shutter sound
  • Loading branch information
MateusAndrade committed Mar 16, 2020
2 parents fb2105c + 18d9347 commit 0afc608
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 54 deletions.
48 changes: 48 additions & 0 deletions android/src/main/java/com/google/android/cameraview/Camera1.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import android.hardware.Camera;
import android.media.CamcorderProfile;
import android.media.MediaRecorder;
import android.media.MediaActionSound;
import android.os.Build;
import android.os.Handler;
import androidx.collection.SparseArrayCompat;
Expand Down Expand Up @@ -82,6 +83,9 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,

Camera mCamera;

// do not instantiate this every time since it allocates unnecessary resources
MediaActionSound sound = new MediaActionSound();

private Camera.Parameters mCameraParameters;

private final Camera.CameraInfo mCameraInfo = new Camera.CameraInfo();
Expand Down Expand Up @@ -123,6 +127,8 @@ class Camera1 extends CameraViewImpl implements MediaRecorder.OnInfoListener,

private boolean mIsScanning;

private Boolean mPlaySoundOnCapture = false;

private boolean mustUpdateSurface;
private boolean surfaceWasDestroyed;

Expand Down Expand Up @@ -750,6 +756,10 @@ public void onPictureTaken(byte[] data, Camera camera) {
// this shouldn't be needed and messes up autoFocusPointOfInterest
// camera.cancelAutoFocus();

if(mPlaySoundOnCapture){
sound.play(MediaActionSound.SHUTTER_CLICK);
}

if (options.hasKey("pauseAfterCapture") && !options.getBoolean("pauseAfterCapture")) {
camera.startPreview();
mIsPreviewActive = true;
Expand Down Expand Up @@ -1048,6 +1058,8 @@ void adjustCameraParameters() {
setZoomInternal(mZoom);
setWhiteBalanceInternal(mWhiteBalance);
setScanningInternal(mIsScanning);
setPlaySoundInternal(mPlaySoundOnCapture);

try{
mCamera.setParameters(mCameraParameters);
}
Expand Down Expand Up @@ -1426,6 +1438,42 @@ private void setScanningInternal(boolean isScanning) {
}
}

private void setPlaySoundInternal(boolean playSoundOnCapture){
mPlaySoundOnCapture = playSoundOnCapture;
if(mCamera != null){
try{
// Always disable shutter sound, and play our own.
// This is because not all devices honor this value when set to true
boolean res = mCamera.enableShutterSound(false);

// if we fail to disable the shutter sound
// set mPlaySoundOnCapture to false since it means
// we cannot change it and the system will play it
// playing the sound ourselves also makes it consistent with Camera2
if(!res){
mPlaySoundOnCapture = false;
}
}
catch(Exception ex){
Log.e("CAMERA_1::", "setPlaySoundInternal failed", ex);
mPlaySoundOnCapture = false;
}
}
}

@Override
void setPlaySoundOnCapture(boolean playSoundOnCapture) {
if (playSoundOnCapture == mPlaySoundOnCapture) {
return;
}
setPlaySoundInternal(playSoundOnCapture);
}

@Override
public boolean getPlaySoundOnCapture(){
return mPlaySoundOnCapture;
}

@Override
public void onPreviewFrame(byte[] data, Camera camera) {
Camera.Size previewSize = mCameraParameters.getPreviewSize();
Expand Down
86 changes: 61 additions & 25 deletions android/src/main/java/com/google/android/cameraview/Camera2.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import android.media.Image;
import android.media.ImageReader;
import android.media.MediaRecorder;
import android.media.MediaActionSound;
import androidx.annotation.NonNull;
import android.util.Log;
import android.util.SparseIntArray;
Expand Down Expand Up @@ -212,6 +213,8 @@ public void onImageAvailable(ImageReader reader) {

CameraDevice mCamera;

MediaActionSound sound = new MediaActionSound();

CameraCaptureSession mCaptureSession;

CaptureRequest.Builder mPreviewRequestBuilder;
Expand Down Expand Up @@ -262,6 +265,8 @@ public void onImageAvailable(ImageReader reader) {

private boolean mIsScanning;

private Boolean mPlaySoundOnCapture = false;

private Surface mPreviewSurface;

private Rect mInitialCropRegion;
Expand Down Expand Up @@ -300,6 +305,7 @@ public void onSurfaceDestroyed() {
boolean start() {
if (!chooseCameraIdByFacing()) {
mAspectRatio = mInitialRatio;
mCallback.onMountError();
return false;
}
collectCameraInfo();
Expand Down Expand Up @@ -675,6 +681,16 @@ public int getWhiteBalance() {
return mWhiteBalance;
}

@Override
void setPlaySoundOnCapture(boolean playSoundOnCapture) {
mPlaySoundOnCapture = playSoundOnCapture;
}

@Override
public boolean getPlaySoundOnCapture(){
return mPlaySoundOnCapture;
}

@Override
void setScanning(boolean isScanning) {
if (mIsScanning == isScanning) {
Expand Down Expand Up @@ -716,6 +732,33 @@ void setDeviceOrientation(int deviceOrientation) {
//mPreview.setDisplayOrientation(deviceOrientation); // this is not needed and messes up the display orientation
}


// This is a helper method to query Camera2 legacy status so we don't need
// to instantiate and set all its props in order to check if it is legacy or not
// and then fallback to Camera1. This way, legacy devices can fall back to Camera1 right away
// This method makes sure all cameras are not legacy, so further checks are not needed.
public static boolean isLegacy(Context context){
try{
CameraManager manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
String[] ids = manager.getCameraIdList();
for (String id : ids) {
CameraCharacteristics characteristics = manager.getCameraCharacteristics(id);
Integer level = characteristics.get(
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
if (level == null ||
level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
Log.w(TAG, "Camera2 can only run in legacy mode and should not be used.");
return true;
}
}
return false;
}
catch(CameraAccessException ex){
Log.e(TAG, "Failed to check camera legacy status, returning true.", ex);
return true;
}
}

/**
* <p>Chooses a camera ID by the specified camera facing ({@link #mFacing}).</p>
* <p>This rewrites {@link #mCameraId}, {@link #mCameraCharacteristics}, and optionally
Expand All @@ -727,19 +770,16 @@ private boolean chooseCameraIdByFacing() {
int internalFacing = INTERNAL_FACINGS.get(mFacing);
final String[] ids = mCameraManager.getCameraIdList();
if (ids.length == 0) { // No camera
throw new RuntimeException("No camera available.");
Log.e(TAG, "No cameras available.");
return false;
}
for (String id : ids) {
CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(id);
Integer level = characteristics.get(
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
if (level == null ||
level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
continue;
}

Integer internal = characteristics.get(CameraCharacteristics.LENS_FACING);
if (internal == null) {
throw new NullPointerException("Unexpected state: LENS_FACING null");
Log.e(TAG, "Unexpected state: LENS_FACING null");
continue;
}
if (internal == internalFacing) {
mCameraId = id;
Expand All @@ -750,15 +790,11 @@ private boolean chooseCameraIdByFacing() {
// Not found
mCameraId = ids[0];
mCameraCharacteristics = mCameraManager.getCameraCharacteristics(mCameraId);
Integer level = mCameraCharacteristics.get(
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
if (level == null ||
level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
return false;
}

Integer internal = mCameraCharacteristics.get(CameraCharacteristics.LENS_FACING);
if (internal == null) {
throw new NullPointerException("Unexpected state: LENS_FACING null");
Log.e(TAG, "Unexpected state: LENS_FACING null");
return false;
}
for (int i = 0, count = INTERNAL_FACINGS.size(); i < count; i++) {
if (INTERNAL_FACINGS.valueAt(i) == internal) {
Expand All @@ -771,7 +807,8 @@ private boolean chooseCameraIdByFacing() {
mFacing = Constants.FACING_BACK;
return true;
} catch (CameraAccessException e) {
throw new RuntimeException("Failed to get a list of camera devices", e);
Log.e(TAG, "Failed to get a list of camera devices", e);
return false;
}
}
else{
Expand All @@ -781,17 +818,11 @@ private boolean chooseCameraIdByFacing() {
// for legacy hardware
mCameraCharacteristics = mCameraManager.getCameraCharacteristics(_mCameraId);

Integer level = mCameraCharacteristics.get(
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
if (level == null ||
level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
return false;
}

// set our facing variable so orientation also works as expected
Integer internal = mCameraCharacteristics.get(CameraCharacteristics.LENS_FACING);
if (internal == null) {
throw new NullPointerException("Unexpected state: LENS_FACING null");
Log.e(TAG, "Unexpected state: LENS_FACING null");
return false;
}
for (int i = 0, count = INTERNAL_FACINGS.size(); i < count; i++) {
if (INTERNAL_FACINGS.valueAt(i) == internal) {
Expand All @@ -804,7 +835,8 @@ private boolean chooseCameraIdByFacing() {
return true;
}
catch(Exception e){
throw new RuntimeException("Failed to get camera characteristics", e);
Log.e(TAG, "Failed to get camera characteristics", e);
return false;
}
}
}
Expand Down Expand Up @@ -905,6 +937,7 @@ void startCaptureSession() {
mCamera.createCaptureSession(Arrays.asList(surface, mStillImageReader.getSurface(),
mScanImageReader.getSurface()), mSessionCallback, null);
} catch (CameraAccessException e) {
Log.e(TAG, "Failed to start capture session", e);
mCallback.onMountError();
}
}
Expand Down Expand Up @@ -1284,6 +1317,9 @@ public void onCaptureCompleted(@NonNull CameraCaptureSession session,
&& !mCaptureCallback.getOptions().getBoolean("pauseAfterCapture")) {
unlockFocus();
}
if (mPlaySoundOnCapture) {
sound.play(MediaActionSound.SHUTTER_CLICK);
}
}
}, null);
} catch (CameraAccessException e) {
Expand Down
47 changes: 33 additions & 14 deletions android/src/main/java/com/google/android/cameraview/CameraView.java
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ public CameraView(Context context, AttributeSet attrs, int defStyleAttr, boolean
// Internal setup
final PreviewImpl preview = createPreviewImpl(context);
mCallbacks = new CallbackBridge();
if (fallbackToOldApi || Build.VERSION.SDK_INT < 21) {
if (fallbackToOldApi || Build.VERSION.SDK_INT < 21 || Camera2.isLegacy(context)) {
mImpl = new Camera1(mCallbacks, preview, mBgHandler);
} else if (Build.VERSION.SDK_INT < 23) {
mImpl = new Camera2(mCallbacks, preview, context, mBgHandler);
Expand Down Expand Up @@ -256,6 +256,7 @@ protected Parcelable onSaveInstanceState() {
state.focusDepth = getFocusDepth();
state.zoom = getZoom();
state.whiteBalance = getWhiteBalance();
state.playSoundOnCapture = getPlaySoundOnCapture();
state.scanning = getScanning();
state.pictureSize = getPictureSize();
return state;
Expand All @@ -278,6 +279,7 @@ protected void onRestoreInstanceState(Parcelable state) {
setFocusDepth(ss.focusDepth);
setZoom(ss.zoom);
setWhiteBalance(ss.whiteBalance);
setPlaySoundOnCapture(ss.playSoundOnCapture);
setScanning(ss.scanning);
setPictureSize(ss.pictureSize);
}
Expand All @@ -290,7 +292,7 @@ public void setUsingCamera2Api(boolean useCamera2) {
boolean wasOpened = isCameraOpened();
Parcelable state = onSaveInstanceState();

if (useCamera2) {
if (useCamera2 && !Camera2.isLegacy(mContext)) {
if (wasOpened) {
stop();
}
Expand All @@ -311,25 +313,30 @@ public void setUsingCamera2Api(boolean useCamera2) {
}
mImpl = new Camera1(mCallbacks, mImpl.mPreview, mBgHandler);
}
start();
if(wasOpened){
start();
}
}

/**
* Open a camera device and start showing camera preview. This is typically called from
* {@link Activity#onResume()}.
*/
public void start() {
if (!mImpl.start()) {
if (mImpl.getView() != null) {
this.removeView(mImpl.getView());
}
//store the state and restore this state after fall back to Camera1
Parcelable state = onSaveInstanceState();
// Camera2 uses legacy hardware layer; fall back to Camera1
mImpl = new Camera1(mCallbacks, createPreviewImpl(getContext()), mBgHandler);
onRestoreInstanceState(state);
mImpl.start();
}
mImpl.start();

// this fallback is no longer needed and was too buggy/slow
// if (!mImpl.start()) {
// if (mImpl.getView() != null) {
// this.removeView(mImpl.getView());
// }
// //store the state and restore this state after fall back to Camera1
// Parcelable state = onSaveInstanceState();
// // Camera2 uses legacy hardware layer; fall back to Camera1
// mImpl = new Camera1(mCallbacks, createPreviewImpl(getContext()), mBgHandler);
// onRestoreInstanceState(state);
// mImpl.start();
// }
}

/**
Expand Down Expand Up @@ -585,6 +592,14 @@ public int getWhiteBalance() {
return mImpl.getWhiteBalance();
}

public void setPlaySoundOnCapture(boolean playSoundOnCapture) {
mImpl.setPlaySoundOnCapture(playSoundOnCapture);
}

public boolean getPlaySoundOnCapture() {
return mImpl.getPlaySoundOnCapture();
}

public void setScanning(boolean isScanning) { mImpl.setScanning(isScanning);}

public boolean getScanning() { return mImpl.getScanning(); }
Expand Down Expand Up @@ -736,6 +751,8 @@ protected static class SavedState extends BaseSavedState {

int whiteBalance;

boolean playSoundOnCapture;

boolean scanning;

Size pictureSize;
Expand All @@ -752,6 +769,7 @@ public SavedState(Parcel source, ClassLoader loader) {
focusDepth = source.readFloat();
zoom = source.readFloat();
whiteBalance = source.readInt();
playSoundOnCapture = source.readByte() != 0;
scanning = source.readByte() != 0;
pictureSize = source.readParcelable(loader);
}
Expand All @@ -772,6 +790,7 @@ public void writeToParcel(Parcel out, int flags) {
out.writeFloat(focusDepth);
out.writeFloat(zoom);
out.writeInt(whiteBalance);
out.writeByte((byte) (playSoundOnCapture ? 1 : 0));
out.writeByte((byte) (scanning ? 1 : 0));
out.writeParcelable(pictureSize, flags);
}
Expand Down
Loading

0 comments on commit 0afc608

Please sign in to comment.