Error when Audio Input is in use by another app (#111)
* Remove Audio Device if it failed to configure * Add `audio-in-use-by-other-app` error * Try removing on interruption * Format code * Make error more clear
This commit is contained in:
parent
12f6ab5217
commit
1558dd2f15
@ -154,6 +154,7 @@ enum FormatError {
|
|||||||
enum SessionError {
|
enum SessionError {
|
||||||
case cameraNotReady
|
case cameraNotReady
|
||||||
case audioSessionSetupFailed(reason: String)
|
case audioSessionSetupFailed(reason: String)
|
||||||
|
case audioInUseByOtherApp
|
||||||
|
|
||||||
// MARK: Internal
|
// MARK: Internal
|
||||||
|
|
||||||
@ -163,6 +164,8 @@ enum SessionError {
|
|||||||
return "camera-not-ready"
|
return "camera-not-ready"
|
||||||
case .audioSessionSetupFailed:
|
case .audioSessionSetupFailed:
|
||||||
return "audio-session-setup-failed"
|
return "audio-session-setup-failed"
|
||||||
|
case .audioInUseByOtherApp:
|
||||||
|
return "audio-in-use-by-other-app"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,6 +175,8 @@ enum SessionError {
|
|||||||
return "The Camera is not ready yet! Wait for the onInitialized() callback!"
|
return "The Camera is not ready yet! Wait for the onInitialized() callback!"
|
||||||
case let .audioSessionSetupFailed(reason):
|
case let .audioSessionSetupFailed(reason):
|
||||||
return "The audio session failed to setup! \(reason)"
|
return "The audio session failed to setup! \(reason)"
|
||||||
|
case .audioInUseByOtherApp:
|
||||||
|
return "The audio session is already in use by another app with higher priority!"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ extension CameraView {
|
|||||||
final func configureAudioSession() {
|
final func configureAudioSession() {
|
||||||
let start = DispatchTime.now()
|
let start = DispatchTime.now()
|
||||||
do {
|
do {
|
||||||
setAutomaticallyConfiguresAudioSession(false)
|
try addAudioInput()
|
||||||
let audioSession = AVAudioSession.sharedInstance()
|
let audioSession = AVAudioSession.sharedInstance()
|
||||||
if audioSession.category != .playAndRecord {
|
if audioSession.category != .playAndRecord {
|
||||||
// allow background music playback
|
// allow background music playback
|
||||||
@ -30,25 +30,57 @@ extension CameraView {
|
|||||||
// activate current audio session because camera is active
|
// activate current audio session because camera is active
|
||||||
try audioSession.setActive(true)
|
try audioSession.setActive(true)
|
||||||
} catch let error as NSError {
|
} catch let error as NSError {
|
||||||
|
switch error.code {
|
||||||
|
case 561_017_449:
|
||||||
|
self.invokeOnError(.session(.audioInUseByOtherApp), cause: error)
|
||||||
|
default:
|
||||||
self.invokeOnError(.session(.audioSessionSetupFailed(reason: error.description)), cause: error)
|
self.invokeOnError(.session(.audioSessionSetupFailed(reason: error.description)), cause: error)
|
||||||
setAutomaticallyConfiguresAudioSession(true)
|
}
|
||||||
|
self.removeAudioInput()
|
||||||
}
|
}
|
||||||
let end = DispatchTime.now()
|
let end = DispatchTime.now()
|
||||||
let nanoTime = end.uptimeNanoseconds - start.uptimeNanoseconds
|
let nanoTime = end.uptimeNanoseconds - start.uptimeNanoseconds
|
||||||
ReactLogger.log(level: .info, message: "Configured Audio session in \(Double(nanoTime) / 1_000_000)ms!")
|
ReactLogger.log(level: .info, message: "Configured Audio session in \(Double(nanoTime) / 1_000_000)ms!")
|
||||||
}
|
}
|
||||||
|
|
||||||
private final func setAutomaticallyConfiguresAudioSession(_ automaticallyConfiguresAudioSession: Bool) {
|
/**
|
||||||
if captureSession.automaticallyConfiguresApplicationAudioSession != automaticallyConfiguresAudioSession {
|
Configures the CaptureSession and adds the audio device if it has not already been added yet.
|
||||||
|
*/
|
||||||
|
private final func addAudioInput() throws {
|
||||||
|
if audioDeviceInput != nil {
|
||||||
|
// we already added the audio device, don't add it again
|
||||||
|
return
|
||||||
|
}
|
||||||
|
removeAudioInput()
|
||||||
captureSession.beginConfiguration()
|
captureSession.beginConfiguration()
|
||||||
captureSession.automaticallyConfiguresApplicationAudioSession = automaticallyConfiguresAudioSession
|
guard let audioDevice = AVCaptureDevice.default(for: .audio) else {
|
||||||
|
throw CameraError.device(.microphoneUnavailable)
|
||||||
|
}
|
||||||
|
audioDeviceInput = try AVCaptureDeviceInput(device: audioDevice)
|
||||||
|
guard captureSession.canAddInput(audioDeviceInput!) else {
|
||||||
|
throw CameraError.parameter(.unsupportedInput(inputDescriptor: "audio-input"))
|
||||||
|
}
|
||||||
|
captureSession.addInput(audioDeviceInput!)
|
||||||
|
captureSession.automaticallyConfiguresApplicationAudioSession = true
|
||||||
captureSession.commitConfiguration()
|
captureSession.commitConfiguration()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Configures the CaptureSession and removes the audio device if it has been added before.
|
||||||
|
*/
|
||||||
|
private final func removeAudioInput() {
|
||||||
|
guard let audioInput = audioDeviceInput else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
captureSession.beginConfiguration()
|
||||||
|
captureSession.removeInput(audioInput)
|
||||||
|
audioDeviceInput = nil
|
||||||
|
captureSession.commitConfiguration()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
func audioSessionInterrupted(notification: Notification) {
|
func audioSessionInterrupted(notification: Notification) {
|
||||||
ReactLogger.log(level: .error, message: "The Audio Session was interrupted!")
|
ReactLogger.log(level: .error, message: "Audio Session Interruption Notification!")
|
||||||
guard let userInfo = notification.userInfo,
|
guard let userInfo = notification.userInfo,
|
||||||
let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
|
let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
|
||||||
let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
|
let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
|
||||||
@ -56,13 +88,15 @@ extension CameraView {
|
|||||||
}
|
}
|
||||||
switch type {
|
switch type {
|
||||||
case .began:
|
case .began:
|
||||||
// TODO: Should we also disable the camera here? I think it will throw a runtime error
|
// Something interrupted our Audio Session, stop recording audio.
|
||||||
// disable audio session
|
ReactLogger.log(level: .error, message: "The Audio Session was interrupted!")
|
||||||
try? AVAudioSession.sharedInstance().setActive(false)
|
removeAudioInput()
|
||||||
case .ended:
|
case .ended:
|
||||||
|
ReactLogger.log(level: .error, message: "The Audio Session interruption has ended.")
|
||||||
guard let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else { return }
|
guard let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else { return }
|
||||||
let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
|
let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
|
||||||
if options.contains(.shouldResume) {
|
if options.contains(.shouldResume) {
|
||||||
|
ReactLogger.log(level: .error, message: "Resuming interrupted Audio Session...")
|
||||||
// restart audio session because interruption is over
|
// restart audio session because interruption is over
|
||||||
configureAudioSession()
|
configureAudioSession()
|
||||||
} else {
|
} else {
|
||||||
|
@ -73,24 +73,6 @@ extension CameraView {
|
|||||||
return invokeOnError(.device(.invalid))
|
return invokeOnError(.device(.invalid))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Microphone (Audio Input)
|
|
||||||
do {
|
|
||||||
if let audioDeviceInput = self.audioDeviceInput {
|
|
||||||
captureSession.removeInput(audioDeviceInput)
|
|
||||||
}
|
|
||||||
guard let audioDevice = AVCaptureDevice.default(for: .audio) else {
|
|
||||||
return invokeOnError(.device(.microphoneUnavailable))
|
|
||||||
}
|
|
||||||
|
|
||||||
audioDeviceInput = try AVCaptureDeviceInput(device: audioDevice)
|
|
||||||
guard captureSession.canAddInput(audioDeviceInput!) else {
|
|
||||||
return invokeOnError(.parameter(.unsupportedInput(inputDescriptor: "audio-input")))
|
|
||||||
}
|
|
||||||
captureSession.addInput(audioDeviceInput!)
|
|
||||||
} catch {
|
|
||||||
return invokeOnError(.device(.invalid))
|
|
||||||
}
|
|
||||||
|
|
||||||
// OUTPUTS
|
// OUTPUTS
|
||||||
if let photoOutput = self.photoOutput {
|
if let photoOutput = self.photoOutput {
|
||||||
captureSession.removeOutput(photoOutput)
|
captureSession.removeOutput(photoOutput)
|
||||||
|
@ -20,7 +20,7 @@ export type FormatError =
|
|||||||
| 'format/invalid-low-light-boost'
|
| 'format/invalid-low-light-boost'
|
||||||
| 'format/invalid-format'
|
| 'format/invalid-format'
|
||||||
| 'format/invalid-preset';
|
| 'format/invalid-preset';
|
||||||
export type SessionError = 'session/camera-not-ready' | 'session/audio-session-setup-failed';
|
export type SessionError = 'session/camera-not-ready' | 'session/audio-session-setup-failed' | 'session/audio-in-use-by-other-app';
|
||||||
export type CaptureError =
|
export type CaptureError =
|
||||||
| 'capture/invalid-photo-format'
|
| 'capture/invalid-photo-format'
|
||||||
| 'capture/encoder-error'
|
| 'capture/encoder-error'
|
||||||
|
Loading…
Reference in New Issue
Block a user