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.
|
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 {
|
if audioDeviceInput != nil {
|
||||||
// we already added the audio device, don't add it again
|
// we already added the audio device, don't add it again
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
removeAudioInput()
|
removeAudioInput()
|
||||||
|
|
||||||
|
ReactLogger.log(level: .info, message: "Adding audio input...")
|
||||||
captureSession.beginConfiguration()
|
captureSession.beginConfiguration()
|
||||||
guard let audioDevice = AVCaptureDevice.default(for: .audio) else {
|
guard let audioDevice = AVCaptureDevice.default(for: .audio) else {
|
||||||
throw CameraError.device(.microphoneUnavailable)
|
throw CameraError.device(.microphoneUnavailable)
|
||||||
@ -71,11 +72,12 @@ extension CameraView {
|
|||||||
/**
|
/**
|
||||||
Configures the CaptureSession and removes the audio device if it has been added before.
|
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 {
|
guard let audioInput = audioDeviceInput else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ReactLogger.log(level: .info, message: "Removing audio input...")
|
||||||
captureSession.beginConfiguration()
|
captureSession.beginConfiguration()
|
||||||
captureSession.removeInput(audioInput)
|
captureSession.removeInput(audioInput)
|
||||||
audioDeviceInput = nil
|
audioDeviceInput = nil
|
||||||
|
@ -17,7 +17,7 @@ extension CameraView {
|
|||||||
Configures the Capture Session.
|
Configures the Capture Session.
|
||||||
*/
|
*/
|
||||||
final func configureCaptureSession() {
|
final func configureCaptureSession() {
|
||||||
ReactLogger.logJS(level: .info, message: "Configuring Session...")
|
ReactLogger.log(level: .info, message: "Configuring Session...")
|
||||||
isReady = false
|
isReady = false
|
||||||
|
|
||||||
#if targetEnvironment(simulator)
|
#if targetEnvironment(simulator)
|
||||||
@ -113,14 +113,14 @@ extension CameraView {
|
|||||||
|
|
||||||
invokeOnInitialized()
|
invokeOnInitialized()
|
||||||
isReady = true
|
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.
|
Configures the Video Device with the given FPS, HDR and ColorSpace.
|
||||||
*/
|
*/
|
||||||
final func configureDevice() {
|
final func configureDevice() {
|
||||||
ReactLogger.logJS(level: .info, message: "Configuring Device...")
|
ReactLogger.log(level: .info, message: "Configuring Device...")
|
||||||
guard let device = videoDeviceInput?.device else {
|
guard let device = videoDeviceInput?.device else {
|
||||||
return invokeOnError(.session(.cameraNotReady))
|
return invokeOnError(.session(.cameraNotReady))
|
||||||
}
|
}
|
||||||
@ -159,7 +159,7 @@ extension CameraView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
device.unlockForConfiguration()
|
device.unlockForConfiguration()
|
||||||
ReactLogger.logJS(level: .info, message: "Device successfully configured!")
|
ReactLogger.log(level: .info, message: "Device successfully configured!")
|
||||||
} catch let error as NSError {
|
} catch let error as NSError {
|
||||||
return invokeOnError(.device(.configureError), cause: error)
|
return invokeOnError(.device(.configureError), cause: error)
|
||||||
}
|
}
|
||||||
@ -169,7 +169,7 @@ extension CameraView {
|
|||||||
Configures the Video Device to find the best matching Format.
|
Configures the Video Device to find the best matching Format.
|
||||||
*/
|
*/
|
||||||
final func configureFormat() {
|
final func configureFormat() {
|
||||||
ReactLogger.logJS(level: .info, message: "Configuring Format...")
|
ReactLogger.log(level: .info, message: "Configuring Format...")
|
||||||
guard let filter = self.format else {
|
guard let filter = self.format else {
|
||||||
// Format Filter was null. Ignore it.
|
// Format Filter was null. Ignore it.
|
||||||
return
|
return
|
||||||
@ -193,7 +193,7 @@ extension CameraView {
|
|||||||
try device.lockForConfiguration()
|
try device.lockForConfiguration()
|
||||||
device.activeFormat = format
|
device.activeFormat = format
|
||||||
device.unlockForConfiguration()
|
device.unlockForConfiguration()
|
||||||
ReactLogger.logJS(level: .info, message: "Format successfully configured!")
|
ReactLogger.log(level: .info, message: "Format successfully configured!")
|
||||||
} catch let error as NSError {
|
} catch let error as NSError {
|
||||||
return invokeOnError(.device(.configureError), cause: error)
|
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),
|
selector: #selector(sessionRuntimeError),
|
||||||
name: .AVCaptureSessionRuntimeError,
|
name: .AVCaptureSessionRuntimeError,
|
||||||
object: captureSession)
|
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,
|
NotificationCenter.default.addObserver(self,
|
||||||
selector: #selector(audioSessionInterrupted),
|
selector: #selector(audioSessionInterrupted),
|
||||||
name: AVAudioSession.interruptionNotification,
|
name: AVAudioSession.interruptionNotification,
|
||||||
@ -56,6 +64,12 @@ final class CameraView: UIView {
|
|||||||
NotificationCenter.default.removeObserver(self,
|
NotificationCenter.default.removeObserver(self,
|
||||||
name: .AVCaptureSessionRuntimeError,
|
name: .AVCaptureSessionRuntimeError,
|
||||||
object: captureSession)
|
object: captureSession)
|
||||||
|
NotificationCenter.default.removeObserver(self,
|
||||||
|
name: .AVCaptureSessionWasInterrupted,
|
||||||
|
object: captureSession)
|
||||||
|
NotificationCenter.default.removeObserver(self,
|
||||||
|
name: .AVCaptureSessionInterruptionEnded,
|
||||||
|
object: captureSession)
|
||||||
NotificationCenter.default.removeObserver(self,
|
NotificationCenter.default.removeObserver(self,
|
||||||
name: AVAudioSession.interruptionNotification,
|
name: AVAudioSession.interruptionNotification,
|
||||||
object: AVAudioSession.sharedInstance)
|
object: AVAudioSession.sharedInstance)
|
||||||
@ -234,7 +248,7 @@ final class CameraView: UIView {
|
|||||||
|
|
||||||
// pragma MARK: Event Invokers
|
// pragma MARK: Event Invokers
|
||||||
internal final func invokeOnError(_ error: CameraError, cause: NSError? = nil) {
|
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 }
|
guard let onError = self.onError else { return }
|
||||||
|
|
||||||
var causeDictionary: [String: Any]?
|
var causeDictionary: [String: Any]?
|
||||||
@ -254,7 +268,7 @@ final class CameraView: UIView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal final func invokeOnInitialized() {
|
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 }
|
guard let onInitialized = self.onInitialized else { return }
|
||||||
onInitialized([String: Any]())
|
onInitialized([String: Any]())
|
||||||
}
|
}
|
||||||
|
@ -15,21 +15,9 @@ let context = "VisionCamera"
|
|||||||
enum ReactLogger {
|
enum ReactLogger {
|
||||||
static func log(level: RCTLogLevel,
|
static func log(level: RCTLogLevel,
|
||||||
message: String,
|
message: String,
|
||||||
alsoLogToJS: Bool = false,
|
|
||||||
_ file: String = #file,
|
_ file: String = #file,
|
||||||
_ lineNumber: Int = #line,
|
_ lineNumber: Int = #line,
|
||||||
_ function: String = #function) {
|
_ function: String = #function) {
|
||||||
RCTDefaultLogFunction(level, RCTLogSource.native, file, lineNumber as NSNumber, "\(context).\(function): \(message)")
|
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