* fix: Add more logs * perf: Init audio AVAssetWriter in parallel * chore: Log Audio Session activation * perf: Init Audio Session asynchronously * chore: Log when category changed * fix: Revert lazy audio initialization * Update Podfile.lock * Pass `sourceFormatHint` to video and audio AVAssetWriter * fix: Remove `sourceFormatHint` from Video Asset Writer * Use default options for Audio Asset Writer * Update Podfile.lock * Revert "Use default options for Audio Asset Writer" This reverts commit e575a14c5342ddc7f9db557d5e3915328ed9e798. * Add time logs * fix: Don't synchronize audio buffers, they are already in sync * shouldOptimizeForNetworkUse = false * fix: Only update `latestTimestamp` once video buffer has been written * perf: Use `AVAssetWriterInput` instead of `AVAssetWriterInputPixelBufferAdaptor` * fix: Fix Audio not being synchronized with Video * Remove logs add comments * Format * feat: Set `.videoRecording` AVAudioSession mode * Refactor `startRecording()` a bit * Format * chore: Throw error directly instead of double-checking
		
			
				
	
	
		
			97 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
			
		
		
	
	
			97 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
| //
 | |
| //  CameraSession+Audio.swift
 | |
| //  VisionCamera
 | |
| //
 | |
| //  Created by Marc Rousavy on 11.10.23.
 | |
| //  Copyright © 2023 mrousavy. All rights reserved.
 | |
| //
 | |
| 
 | |
| import AVFoundation
 | |
| import Foundation
 | |
| 
 | |
| extension CameraSession {
 | |
|   /**
 | |
|    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.
 | |
|    */
 | |
|   final func activateAudioSession() throws {
 | |
|     ReactLogger.log(level: .info, message: "Activating Audio Session...")
 | |
| 
 | |
|     do {
 | |
|       let audioSession = AVAudioSession.sharedInstance()
 | |
| 
 | |
|       try audioSession.updateCategory(AVAudioSession.Category.playAndRecord,
 | |
|                                       mode: .videoRecording,
 | |
|                                       options: [.mixWithOthers,
 | |
|                                                 .allowBluetoothA2DP,
 | |
|                                                 .defaultToSpeaker,
 | |
|                                                 .allowAirPlay])
 | |
| 
 | |
|       if #available(iOS 14.5, *) {
 | |
|         // prevents the audio session from being interrupted by a phone call
 | |
|         try audioSession.setPrefersNoInterruptionsFromSystemAlerts(true)
 | |
|       }
 | |
| 
 | |
|       if #available(iOS 13.0, *) {
 | |
|         // allow system sounds (notifications, calls, music) to play while recording
 | |
|         try audioSession.setAllowHapticsAndSystemSoundsDuringRecording(true)
 | |
|       }
 | |
| 
 | |
|       audioCaptureSession.startRunning()
 | |
|       ReactLogger.log(level: .info, message: "Audio Session activated!")
 | |
|     } catch let error as NSError {
 | |
|       ReactLogger.log(level: .error, message: "Failed to activate audio session! Error \(error.code): \(error.description)")
 | |
|       switch error.code {
 | |
|       case 561_017_449:
 | |
|         throw CameraError.session(.audioInUseByOtherApp)
 | |
|       default:
 | |
|         throw CameraError.session(.audioSessionFailedToActivate)
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   final func deactivateAudioSession() {
 | |
|     ReactLogger.log(level: .info, message: "Deactivating Audio Session...")
 | |
| 
 | |
|     audioCaptureSession.stopRunning()
 | |
|     ReactLogger.log(level: .info, message: "Audio Session deactivated!")
 | |
|   }
 | |
| 
 | |
|   @objc
 | |
|   func audioSessionInterrupted(notification: Notification) {
 | |
|     ReactLogger.log(level: .error, message: "Audio Session Interruption Notification!")
 | |
|     guard let userInfo = notification.userInfo,
 | |
|           let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
 | |
|           let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
 | |
|       return
 | |
|     }
 | |
| 
 | |
|     // TODO: Add JS-Event for Audio Session interruptions?
 | |
|     switch type {
 | |
|     case .began:
 | |
|       // Something interrupted our Audio Session, stop recording audio.
 | |
|       ReactLogger.log(level: .error, message: "The Audio Session was interrupted!")
 | |
|     case .ended:
 | |
|       ReactLogger.log(level: .info, message: "The Audio Session interruption has ended.")
 | |
|       guard let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else { return }
 | |
|       let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
 | |
|       if options.contains(.shouldResume) {
 | |
|         // Try resuming if possible
 | |
|         if isRecording {
 | |
|           CameraQueues.audioQueue.async {
 | |
|             ReactLogger.log(level: .info, message: "Resuming interrupted Audio Session...")
 | |
|             // restart audio session because interruption is over
 | |
|             try? self.activateAudioSession()
 | |
|           }
 | |
|         }
 | |
|       } else {
 | |
|         ReactLogger.log(level: .error, message: "Cannot resume interrupted Audio Session!")
 | |
|       }
 | |
|     @unknown default:
 | |
|       ()
 | |
|     }
 | |
|   }
 | |
| }
 |