diff --git a/android/src/main/java/com/mrousavy/camera/CameraSession.kt b/android/src/main/java/com/mrousavy/camera/CameraSession.kt index f6ce758c0b..188b5a3d51 100644 --- a/android/src/main/java/com/mrousavy/camera/CameraSession.kt +++ b/android/src/main/java/com/mrousavy/camera/CameraSession.kt @@ -178,6 +178,7 @@ class CameraSession(private val context: Context, suspend fun takePhoto(qualityPrioritization: QualityPrioritization, flashMode: Flash, + enableShutterSound: Boolean, enableRedEyeReduction: Boolean, enableAutoStabilization: Boolean, outputOrientation: Orientation): CapturedPhoto { @@ -197,7 +198,7 @@ class CameraSession(private val context: Context, enableAutoStabilization, orientation) Log.i(TAG, "Photo capture 0/2 - starting capture...") - val result = captureSession.capture(captureRequest) + val result = captureSession.capture(captureRequest, enableShutterSound) val timestamp = result[CaptureResult.SENSOR_TIMESTAMP]!! Log.i(TAG, "Photo capture 1/2 complete - received metadata with timestamp $timestamp") try { diff --git a/android/src/main/java/com/mrousavy/camera/CameraView+TakePhoto.kt b/android/src/main/java/com/mrousavy/camera/CameraView+TakePhoto.kt index bf91a4f380..3e76d15fae 100644 --- a/android/src/main/java/com/mrousavy/camera/CameraView+TakePhoto.kt +++ b/android/src/main/java/com/mrousavy/camera/CameraView+TakePhoto.kt @@ -30,12 +30,14 @@ suspend fun CameraView.takePhoto(optionsMap: ReadableMap): WritableMap { val flash = options["flash"] as? String ?: "off" val enableAutoRedEyeReduction = options["enableAutoRedEyeReduction"] == true val enableAutoStabilization = options["enableAutoStabilization"] == true + val enableShutterSound = options["enableShutterSound"] as? Boolean ?: true val flashMode = Flash.fromUnionValue(flash) val qualityPrioritizationMode = QualityPrioritization.fromUnionValue(qualityPrioritization) val photo = cameraSession.takePhoto(qualityPrioritizationMode, flashMode, + enableShutterSound, enableAutoRedEyeReduction, enableAutoStabilization, outputOrientation) diff --git a/android/src/main/java/com/mrousavy/camera/extensions/CameraCaptureSession+capture.kt b/android/src/main/java/com/mrousavy/camera/extensions/CameraCaptureSession+capture.kt index 08663c5088..151b7b8bea 100644 --- a/android/src/main/java/com/mrousavy/camera/extensions/CameraCaptureSession+capture.kt +++ b/android/src/main/java/com/mrousavy/camera/extensions/CameraCaptureSession+capture.kt @@ -4,6 +4,7 @@ import android.hardware.camera2.CameraCaptureSession import android.hardware.camera2.CaptureFailure import android.hardware.camera2.CaptureRequest import android.hardware.camera2.TotalCaptureResult +import android.media.MediaActionSound import com.mrousavy.camera.CameraQueues import com.mrousavy.camera.CaptureAbortedError import com.mrousavy.camera.UnknownCaptureError @@ -11,7 +12,7 @@ import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException import kotlin.coroutines.suspendCoroutine -suspend fun CameraCaptureSession.capture(captureRequest: CaptureRequest): TotalCaptureResult { +suspend fun CameraCaptureSession.capture(captureRequest: CaptureRequest, enableShutterSound: Boolean): TotalCaptureResult { return suspendCoroutine { continuation -> this.capture(captureRequest, object: CameraCaptureSession.CaptureCallback() { override fun onCaptureCompleted( @@ -20,9 +21,19 @@ suspend fun CameraCaptureSession.capture(captureRequest: CaptureRequest): TotalC result: TotalCaptureResult ) { super.onCaptureCompleted(session, request, result) + continuation.resume(result) } + override fun onCaptureStarted(session: CameraCaptureSession, request: CaptureRequest, timestamp: Long, frameNumber: Long) { + super.onCaptureStarted(session, request, timestamp, frameNumber) + + if (enableShutterSound) { + val mediaActionSound = MediaActionSound() + mediaActionSound.play(MediaActionSound.SHUTTER_CLICK) + } + } + override fun onCaptureFailed( session: CameraCaptureSession, request: CaptureRequest, diff --git a/example/src/views/CaptureButton.tsx b/example/src/views/CaptureButton.tsx index bd03de43fc..7c390928af 100644 --- a/example/src/views/CaptureButton.tsx +++ b/example/src/views/CaptureButton.tsx @@ -64,6 +64,7 @@ const _CaptureButton: React.FC = ({ qualityPrioritization: 'speed', flash: flash, quality: 90, + enableShutterSound: false, }), [flash], ); diff --git a/ios/CameraView+TakePhoto.swift b/ios/CameraView+TakePhoto.swift index 9d39b6fc7b..f6a2b9cd0c 100644 --- a/ios/CameraView+TakePhoto.swift +++ b/ios/CameraView+TakePhoto.swift @@ -45,6 +45,9 @@ extension CameraView { photoSettings.flashMode = flashMode } + // shutter sound + let enableShutterSound = options["enableShutterSound"] as? Bool ?? true + // depth data photoSettings.isDepthDataDeliveryEnabled = photoOutput.isDepthDataDeliveryEnabled if #available(iOS 12.0, *) { @@ -75,7 +78,7 @@ extension CameraView { photoSettings.isAutoContentAwareDistortionCorrectionEnabled = enableAutoDistortionCorrection } - photoOutput.capturePhoto(with: photoSettings, delegate: PhotoCaptureDelegate(promise: promise)) + photoOutput.capturePhoto(with: photoSettings, delegate: PhotoCaptureDelegate(promise: promise, enableShutterSound: enableShutterSound)) // Assume that `takePhoto` is always called with the same parameters, so prepare the next call too. photoOutput.setPreparedPhotoSettingsArray([photoSettings], completionHandler: nil) diff --git a/ios/PhotoCaptureDelegate.swift b/ios/PhotoCaptureDelegate.swift index 1e3e70f311..11ccd46575 100644 --- a/ios/PhotoCaptureDelegate.swift +++ b/ios/PhotoCaptureDelegate.swift @@ -14,13 +14,22 @@ private var delegatesReferences: [NSObject] = [] class PhotoCaptureDelegate: NSObject, AVCapturePhotoCaptureDelegate { private let promise: Promise + private let enableShutterSound: Bool - required init(promise: Promise) { + required init(promise: Promise, enableShutterSound: Bool) { self.promise = promise + self.enableShutterSound = enableShutterSound super.init() delegatesReferences.append(self) } + func photoOutput(_: AVCapturePhotoOutput, willCapturePhotoFor _: AVCaptureResolvedPhotoSettings) { + if !enableShutterSound { + // disable system shutter sound (see https://stackoverflow.com/a/55235949/5281431) + AudioServicesDisposeSystemSoundID(1108) + } + } + func photoOutput(_: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { defer { delegatesReferences.removeAll(where: { $0 == self }) diff --git a/src/PhotoFile.ts b/src/PhotoFile.ts index 04769b2ee5..9c87a77510 100644 --- a/src/PhotoFile.ts +++ b/src/PhotoFile.ts @@ -38,6 +38,12 @@ export interface TakePhotoOptions { * @default false */ enableAutoDistortionCorrection?: boolean; + /** + * Whether to play the default shutter "click" sound when taking a picture or not. + * + * @default true + */ + enableShutterSound?: boolean; } /**