Automatically handle Audio interruptions (#113)
* Remove audio device when interruption begins * Remove ReactLogger:alsoLogToJS * Fix ReactLogger.logJS calls * Fix `AVCaptureSessionInterruptionReasonKey` cast
This commit is contained in:
parent
cd180dc73b
commit
4ea636e0d0
@ -48,13 +48,14 @@ extension CameraView {
|
||||
/**
|
||||
Configures the CaptureSession and adds the audio device if it has not already been added yet.
|
||||
*/
|
||||
private final func addAudioInput() throws {
|
||||
func addAudioInput() throws {
|
||||
if audioDeviceInput != nil {
|
||||
// we already added the audio device, don't add it again
|
||||
return
|
||||
}
|
||||
removeAudioInput()
|
||||
|
||||
ReactLogger.log(level: .info, message: "Adding audio input...")
|
||||
captureSession.beginConfiguration()
|
||||
guard let audioDevice = AVCaptureDevice.default(for: .audio) else {
|
||||
throw CameraError.device(.microphoneUnavailable)
|
||||
@ -71,11 +72,12 @@ extension CameraView {
|
||||
/**
|
||||
Configures the CaptureSession and removes the audio device if it has been added before.
|
||||
*/
|
||||
private final func removeAudioInput() {
|
||||
func removeAudioInput() {
|
||||
guard let audioInput = audioDeviceInput else {
|
||||
return
|
||||
}
|
||||
|
||||
ReactLogger.log(level: .info, message: "Removing audio input...")
|
||||
captureSession.beginConfiguration()
|
||||
captureSession.removeInput(audioInput)
|
||||
audioDeviceInput = nil
|
||||
|
@ -17,7 +17,7 @@ extension CameraView {
|
||||
Configures the Capture Session.
|
||||
*/
|
||||
final func configureCaptureSession() {
|
||||
ReactLogger.logJS(level: .info, message: "Configuring Session...")
|
||||
ReactLogger.log(level: .info, message: "Configuring Session...")
|
||||
isReady = false
|
||||
|
||||
#if targetEnvironment(simulator)
|
||||
@ -113,14 +113,14 @@ extension CameraView {
|
||||
|
||||
invokeOnInitialized()
|
||||
isReady = true
|
||||
ReactLogger.logJS(level: .info, message: "Session successfully configured!")
|
||||
ReactLogger.log(level: .info, message: "Session successfully configured!")
|
||||
}
|
||||
|
||||
/**
|
||||
Configures the Video Device with the given FPS, HDR and ColorSpace.
|
||||
*/
|
||||
final func configureDevice() {
|
||||
ReactLogger.logJS(level: .info, message: "Configuring Device...")
|
||||
ReactLogger.log(level: .info, message: "Configuring Device...")
|
||||
guard let device = videoDeviceInput?.device else {
|
||||
return invokeOnError(.session(.cameraNotReady))
|
||||
}
|
||||
@ -159,7 +159,7 @@ extension CameraView {
|
||||
}
|
||||
|
||||
device.unlockForConfiguration()
|
||||
ReactLogger.logJS(level: .info, message: "Device successfully configured!")
|
||||
ReactLogger.log(level: .info, message: "Device successfully configured!")
|
||||
} catch let error as NSError {
|
||||
return invokeOnError(.device(.configureError), cause: error)
|
||||
}
|
||||
@ -169,7 +169,7 @@ extension CameraView {
|
||||
Configures the Video Device to find the best matching Format.
|
||||
*/
|
||||
final func configureFormat() {
|
||||
ReactLogger.logJS(level: .info, message: "Configuring Format...")
|
||||
ReactLogger.log(level: .info, message: "Configuring Format...")
|
||||
guard let filter = self.format else {
|
||||
// Format Filter was null. Ignore it.
|
||||
return
|
||||
@ -193,7 +193,7 @@ extension CameraView {
|
||||
try device.lockForConfiguration()
|
||||
device.activeFormat = format
|
||||
device.unlockForConfiguration()
|
||||
ReactLogger.logJS(level: .info, message: "Format successfully configured!")
|
||||
ReactLogger.log(level: .info, message: "Format successfully configured!")
|
||||
} catch let error as NSError {
|
||||
return invokeOnError(.device(.configureError), cause: error)
|
||||
}
|
||||
@ -215,4 +215,40 @@ extension CameraView {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
func sessionInterruptionBegin(notification: Notification) {
|
||||
ReactLogger.log(level: .error, message: "Capture Session Interruption begin Notification!")
|
||||
guard let reasonNumber = notification.userInfo?[AVCaptureSessionInterruptionReasonKey] as? NSNumber else {
|
||||
return
|
||||
}
|
||||
let reason = AVCaptureSession.InterruptionReason(rawValue: reasonNumber.intValue)
|
||||
|
||||
switch reason {
|
||||
case .audioDeviceInUseByAnotherClient:
|
||||
// remove audio input so iOS thinks nothing is wrong and won't pause the session.
|
||||
removeAudioInput()
|
||||
default:
|
||||
// don't do anything, iOS will automatically pause session
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
func sessionInterruptionEnd(notification: Notification) {
|
||||
ReactLogger.log(level: .error, message: "Capture Session Interruption end Notification!")
|
||||
guard let reasonNumber = notification.userInfo?[AVCaptureSessionInterruptionReasonKey] as? NSNumber else {
|
||||
return
|
||||
}
|
||||
let reason = AVCaptureSession.InterruptionReason(rawValue: reasonNumber.intValue)
|
||||
|
||||
switch reason {
|
||||
case .audioDeviceInUseByAnotherClient:
|
||||
// add audio again because we removed it when we received the interruption.
|
||||
configureAudioSession()
|
||||
default:
|
||||
// don't do anything, iOS will automatically resume session
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -46,6 +46,14 @@ final class CameraView: UIView {
|
||||
selector: #selector(sessionRuntimeError),
|
||||
name: .AVCaptureSessionRuntimeError,
|
||||
object: captureSession)
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(sessionInterruptionBegin),
|
||||
name: .AVCaptureSessionWasInterrupted,
|
||||
object: captureSession)
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(sessionInterruptionEnd),
|
||||
name: .AVCaptureSessionInterruptionEnded,
|
||||
object: captureSession)
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(audioSessionInterrupted),
|
||||
name: AVAudioSession.interruptionNotification,
|
||||
@ -56,6 +64,12 @@ final class CameraView: UIView {
|
||||
NotificationCenter.default.removeObserver(self,
|
||||
name: .AVCaptureSessionRuntimeError,
|
||||
object: captureSession)
|
||||
NotificationCenter.default.removeObserver(self,
|
||||
name: .AVCaptureSessionWasInterrupted,
|
||||
object: captureSession)
|
||||
NotificationCenter.default.removeObserver(self,
|
||||
name: .AVCaptureSessionInterruptionEnded,
|
||||
object: captureSession)
|
||||
NotificationCenter.default.removeObserver(self,
|
||||
name: AVAudioSession.interruptionNotification,
|
||||
object: AVAudioSession.sharedInstance)
|
||||
@ -234,7 +248,7 @@ final class CameraView: UIView {
|
||||
|
||||
// pragma MARK: Event Invokers
|
||||
internal final func invokeOnError(_ error: CameraError, cause: NSError? = nil) {
|
||||
ReactLogger.log(level: .error, message: "Invoking onError(): \(error.message)", alsoLogToJS: true)
|
||||
ReactLogger.log(level: .error, message: "Invoking onError(): \(error.message)")
|
||||
guard let onError = self.onError else { return }
|
||||
|
||||
var causeDictionary: [String: Any]?
|
||||
@ -254,7 +268,7 @@ final class CameraView: UIView {
|
||||
}
|
||||
|
||||
internal final func invokeOnInitialized() {
|
||||
ReactLogger.log(level: .info, message: "Camera initialized!", alsoLogToJS: true)
|
||||
ReactLogger.log(level: .info, message: "Camera initialized!")
|
||||
guard let onInitialized = self.onInitialized else { return }
|
||||
onInitialized([String: Any]())
|
||||
}
|
||||
|
@ -15,21 +15,9 @@ let context = "VisionCamera"
|
||||
enum ReactLogger {
|
||||
static func log(level: RCTLogLevel,
|
||||
message: String,
|
||||
alsoLogToJS: Bool = false,
|
||||
_ file: String = #file,
|
||||
_ lineNumber: Int = #line,
|
||||
_ function: String = #function) {
|
||||
RCTDefaultLogFunction(level, RCTLogSource.native, file, lineNumber as NSNumber, "\(context).\(function): \(message)")
|
||||
if alsoLogToJS {
|
||||
ReactLogger.logJS(level: level, message: message, file, lineNumber, function)
|
||||
}
|
||||
}
|
||||
|
||||
static func logJS(level: RCTLogLevel,
|
||||
message: String,
|
||||
_ file: String = #file,
|
||||
_ lineNumber: Int = #line,
|
||||
_ function: String = #function) {
|
||||
RCTDefaultLogFunction(level, RCTLogSource.javaScript, file, lineNumber as NSNumber, "\(context).\(function): \(message)")
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user