2021-03-26 09:20:57 -06:00
|
|
|
//
|
|
|
|
// CameraView+AVAudioSession.swift
|
|
|
|
// VisionCamera
|
|
|
|
//
|
|
|
|
// Created by Marc Rousavy on 26.03.21.
|
2021-06-01 05:07:57 -06:00
|
|
|
// Copyright © 2021 mrousavy. All rights reserved.
|
2021-03-26 09:20:57 -06:00
|
|
|
//
|
|
|
|
|
|
|
|
import AVFoundation
|
2021-03-26 09:28:08 -06:00
|
|
|
import Foundation
|
2021-03-26 09:20:57 -06:00
|
|
|
|
|
|
|
/**
|
|
|
|
Extension for CameraView that sets up the AVAudioSession.
|
|
|
|
*/
|
|
|
|
extension CameraView {
|
2021-03-26 09:28:08 -06:00
|
|
|
/**
|
2021-06-03 06:16:02 -06:00
|
|
|
Configures the Audio Capture Session with an audio input and audio data output.
|
2021-03-26 09:28:08 -06:00
|
|
|
*/
|
|
|
|
final func configureAudioSession() {
|
2021-06-03 06:16:02 -06:00
|
|
|
ReactLogger.log(level: .info, message: "Configuring Audio Session...")
|
2021-03-29 05:18:02 -06:00
|
|
|
|
2021-06-03 06:16:02 -06:00
|
|
|
audioCaptureSession.beginConfiguration()
|
|
|
|
defer {
|
|
|
|
audioCaptureSession.commitConfiguration()
|
2021-03-26 09:28:08 -06:00
|
|
|
}
|
2021-03-29 05:18:02 -06:00
|
|
|
|
2021-06-03 06:16:02 -06:00
|
|
|
audioCaptureSession.automaticallyConfiguresApplicationAudioSession = false
|
2021-06-07 05:08:40 -06:00
|
|
|
let enableAudio = audio?.boolValue == true
|
|
|
|
|
|
|
|
// check microphone permission
|
|
|
|
if enableAudio {
|
|
|
|
let audioPermissionStatus = AVCaptureDevice.authorizationStatus(for: .audio)
|
|
|
|
if audioPermissionStatus != .authorized {
|
2021-06-09 03:14:49 -06:00
|
|
|
invokeOnError(.permission(.microphone))
|
|
|
|
return
|
2021-06-07 05:08:40 -06:00
|
|
|
}
|
|
|
|
}
|
2021-03-26 09:28:08 -06:00
|
|
|
|
2021-06-03 06:16:02 -06:00
|
|
|
// Audio Input
|
|
|
|
do {
|
2021-12-10 01:44:54 -07:00
|
|
|
if let audioDeviceInput = audioDeviceInput {
|
2021-06-03 06:16:02 -06:00
|
|
|
audioCaptureSession.removeInput(audioDeviceInput)
|
|
|
|
self.audioDeviceInput = nil
|
|
|
|
}
|
2021-06-07 05:08:40 -06:00
|
|
|
if enableAudio {
|
|
|
|
ReactLogger.log(level: .info, message: "Adding Audio input...")
|
|
|
|
guard let audioDevice = AVCaptureDevice.default(for: .audio) else {
|
2021-06-09 03:14:49 -06:00
|
|
|
invokeOnError(.device(.microphoneUnavailable))
|
|
|
|
return
|
2021-06-07 05:08:40 -06:00
|
|
|
}
|
|
|
|
audioDeviceInput = try AVCaptureDeviceInput(device: audioDevice)
|
|
|
|
guard audioCaptureSession.canAddInput(audioDeviceInput!) else {
|
2021-06-09 03:14:49 -06:00
|
|
|
invokeOnError(.parameter(.unsupportedInput(inputDescriptor: "audio-input")))
|
|
|
|
return
|
2021-06-07 05:08:40 -06:00
|
|
|
}
|
|
|
|
audioCaptureSession.addInput(audioDeviceInput!)
|
2021-06-03 06:16:02 -06:00
|
|
|
}
|
|
|
|
} catch let error as NSError {
|
2021-06-09 03:14:49 -06:00
|
|
|
invokeOnError(.device(.microphoneUnavailable), cause: error)
|
|
|
|
return
|
2021-03-29 03:32:00 -06:00
|
|
|
}
|
2021-03-29 05:18:02 -06:00
|
|
|
|
2021-06-03 06:16:02 -06:00
|
|
|
// Audio Output
|
2021-12-10 01:44:54 -07:00
|
|
|
if let audioOutput = audioOutput {
|
2021-06-03 06:16:02 -06:00
|
|
|
audioCaptureSession.removeOutput(audioOutput)
|
|
|
|
self.audioOutput = nil
|
2021-03-29 03:32:00 -06:00
|
|
|
}
|
2021-06-07 05:08:40 -06:00
|
|
|
if enableAudio {
|
|
|
|
ReactLogger.log(level: .info, message: "Adding Audio Data output...")
|
|
|
|
audioOutput = AVCaptureAudioDataOutput()
|
|
|
|
guard audioCaptureSession.canAddOutput(audioOutput!) else {
|
2021-06-09 03:14:49 -06:00
|
|
|
invokeOnError(.parameter(.unsupportedOutput(outputDescriptor: "audio-output")))
|
|
|
|
return
|
2021-06-07 05:08:40 -06:00
|
|
|
}
|
2023-07-20 07:30:04 -06:00
|
|
|
audioOutput!.setSampleBufferDelegate(self, queue: CameraQueues.audioQueue)
|
2021-06-07 05:08:40 -06:00
|
|
|
audioCaptureSession.addOutput(audioOutput!)
|
2021-03-29 03:32:00 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-06-03 06:16:02 -06:00
|
|
|
Configures the Audio session and activates it. If the session was active it will shortly be deactivated before configuration.
|
|
|
|
|
|
|
|
The Audio Session will be configured to allow background music, haptics (vibrations) and system sound playback while recording.
|
|
|
|
Background audio is allowed to play on speakers or bluetooth speakers.
|
2021-03-29 03:32:00 -06:00
|
|
|
*/
|
2021-06-03 06:16:02 -06:00
|
|
|
final func activateAudioSession() {
|
|
|
|
ReactLogger.log(level: .info, message: "Activating Audio Session...")
|
|
|
|
|
|
|
|
do {
|
|
|
|
try AVAudioSession.sharedInstance().updateCategory(AVAudioSession.Category.playAndRecord,
|
|
|
|
options: [.mixWithOthers,
|
|
|
|
.allowBluetoothA2DP,
|
|
|
|
.defaultToSpeaker,
|
|
|
|
.allowAirPlay])
|
2022-10-14 04:30:22 -06:00
|
|
|
|
|
|
|
if #available(iOS 14.5, *) {
|
|
|
|
// prevents the audio session from being interrupted by a phone call
|
|
|
|
try AVAudioSession.sharedInstance().setPrefersNoInterruptionsFromSystemAlerts(true)
|
|
|
|
}
|
|
|
|
|
2021-06-03 06:16:02 -06:00
|
|
|
audioCaptureSession.startRunning()
|
|
|
|
} catch let error as NSError {
|
|
|
|
switch error.code {
|
|
|
|
case 561_017_449:
|
|
|
|
self.invokeOnError(.session(.audioInUseByOtherApp), cause: error)
|
|
|
|
default:
|
|
|
|
self.invokeOnError(.session(.audioSessionFailedToActivate), cause: error)
|
|
|
|
}
|
2021-03-26 09:28:08 -06:00
|
|
|
}
|
2021-06-03 06:16:02 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
final func deactivateAudioSession() {
|
|
|
|
ReactLogger.log(level: .info, message: "Deactivating Audio Session...")
|
2021-03-29 05:18:02 -06:00
|
|
|
|
2021-06-03 06:16:02 -06:00
|
|
|
audioCaptureSession.stopRunning()
|
2021-03-26 09:28:08 -06:00
|
|
|
}
|
|
|
|
|
2021-03-26 09:20:57 -06:00
|
|
|
@objc
|
|
|
|
func audioSessionInterrupted(notification: Notification) {
|
2021-03-29 03:32:00 -06:00
|
|
|
ReactLogger.log(level: .error, message: "Audio Session Interruption Notification!")
|
2021-03-26 09:20:57 -06:00
|
|
|
guard let userInfo = notification.userInfo,
|
|
|
|
let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
|
|
|
|
let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
|
|
|
|
return
|
|
|
|
}
|
2021-03-29 05:18:02 -06:00
|
|
|
|
2021-09-06 08:27:16 -06:00
|
|
|
// TODO: Add JS-Event for Audio Session interruptions?
|
2021-03-26 09:20:57 -06:00
|
|
|
switch type {
|
|
|
|
case .began:
|
2021-03-29 03:32:00 -06:00
|
|
|
// Something interrupted our Audio Session, stop recording audio.
|
2021-09-06 08:27:16 -06:00
|
|
|
ReactLogger.log(level: .error, message: "The Audio Session was interrupted!")
|
2021-03-26 09:20:57 -06:00
|
|
|
case .ended:
|
2021-06-03 06:16:02 -06:00
|
|
|
ReactLogger.log(level: .info, message: "The Audio Session interruption has ended.")
|
2021-03-26 09:20:57 -06:00
|
|
|
guard let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else { return }
|
|
|
|
let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
|
|
|
|
if options.contains(.shouldResume) {
|
2021-06-03 06:16:02 -06:00
|
|
|
if isRecording {
|
2023-07-20 07:30:04 -06:00
|
|
|
CameraQueues.audioQueue.async {
|
2021-09-06 08:27:16 -06:00
|
|
|
ReactLogger.log(level: .info, message: "Resuming interrupted Audio Session...")
|
2021-06-03 06:16:02 -06:00
|
|
|
// restart audio session because interruption is over
|
|
|
|
self.activateAudioSession()
|
|
|
|
}
|
|
|
|
}
|
2021-03-26 09:20:57 -06:00
|
|
|
} else {
|
2021-09-06 08:27:16 -06:00
|
|
|
ReactLogger.log(level: .error, message: "Cannot resume interrupted Audio Session!")
|
2021-03-26 09:20:57 -06:00
|
|
|
}
|
2023-02-15 09:24:33 -07:00
|
|
|
@unknown default:
|
|
|
|
()
|
2021-03-26 09:20:57 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|