Fix/unknown runtime error (#71)

* Add a few more log statements

* Log one more prop

* Configure audio session before activating camera
This commit is contained in:
Marc Rousavy 2021-03-17 16:37:31 +01:00 committed by GitHub
parent 64fcf9f069
commit c568b7cf40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 54 additions and 21 deletions

View File

@ -7,7 +7,7 @@
--disable wrapMultilineStatementBraces --disable wrapMultilineStatementBraces
--enable organizeDeclarations --enable organizeDeclarations
--lifecycle didSetProps,requiresMainQueueSetup,view,methodQueue,getCameraView --lifecycle didSetProps,requiresMainQueueSetup,view,methodQueue,getCameraView,removeFromSuperview
--enable markTypes --enable markTypes

View File

@ -57,9 +57,11 @@ final class CameraView: UIView {
// pragma MARK: Props updating // pragma MARK: Props updating
override final func didSetProps(_ changedProps: [String]!) { override final func didSetProps(_ changedProps: [String]!) {
ReactLogger.log(level: .info, message: "Updating \(changedProps.count) prop(s)...")
let shouldReconfigure = changedProps.contains { propsThatRequireReconfiguration.contains($0) } let shouldReconfigure = changedProps.contains { propsThatRequireReconfiguration.contains($0) }
let shouldReconfigureFormat = shouldReconfigure || changedProps.contains("format") let shouldReconfigureFormat = shouldReconfigure || changedProps.contains("format")
let shouldReconfigureDevice = shouldReconfigureFormat || changedProps.contains { propsThatRequireDeviceReconfiguration.contains($0) } let shouldReconfigureDevice = shouldReconfigureFormat || changedProps.contains { propsThatRequireDeviceReconfiguration.contains($0) }
ReactLogger.log(level: .info, message: "Reconfiguring \(shouldReconfigure ? "everything" : (shouldReconfigureFormat ? "format" : shouldReconfigureDevice ? "device" : "only dynamics"))...")
let willReconfigure = shouldReconfigure || shouldReconfigureFormat || shouldReconfigureDevice let willReconfigure = shouldReconfigure || shouldReconfigureFormat || shouldReconfigureDevice
@ -88,9 +90,14 @@ final class CameraView: UIView {
if shouldCheckActive && self.captureSession.isRunning != self.isActive { if shouldCheckActive && self.captureSession.isRunning != self.isActive {
if self.isActive { if self.isActive {
ReactLogger.log(level: .info, message: "Starting Session...")
self.configureAudioSession()
self.captureSession.startRunning() self.captureSession.startRunning()
ReactLogger.log(level: .info, message: "Starting Session!")
} else { } else {
ReactLogger.log(level: .info, message: "Stopping Session...")
self.captureSession.stopRunning() self.captureSession.stopRunning()
ReactLogger.log(level: .info, message: "Stopped Session!")
} }
} }
@ -104,6 +111,12 @@ final class CameraView: UIView {
} }
} }
override func removeFromSuperview() {
ReactLogger.log(level: .info, message: "Removing Camera View...")
captureSession.stopRunning()
super.removeFromSuperview()
}
// MARK: Internal // MARK: Internal
// pragma MARK: Setup // pragma MARK: Setup
@ -171,13 +184,9 @@ final class CameraView: UIView {
return layer as! AVCaptureVideoPreviewLayer return layer as! AVCaptureVideoPreviewLayer
} }
override func removeFromSuperview() {
captureSession.stopRunning()
super.removeFromSuperview()
}
@objc @objc
func sessionRuntimeError(notification: Notification) { func sessionRuntimeError(notification: Notification) {
ReactLogger.log(level: .error, message: "Unexpected Camera Runtime Error occured!")
guard let error = notification.userInfo?[AVCaptureSessionErrorKey] as? AVError else { guard let error = notification.userInfo?[AVCaptureSessionErrorKey] as? AVError else {
return return
} }
@ -228,7 +237,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: error.localizedDescription, alsoLogToJS: true) ReactLogger.log(level: .error, message: "Invoking onError(): \(error.message)", alsoLogToJS: true)
guard let onError = self.onError else { return } guard let onError = self.onError else { return }
var causeDictionary: [String: Any]? var causeDictionary: [String: Any]?
@ -248,7 +257,7 @@ final class CameraView: UIView {
} }
internal final func invokeOnInitialized() { internal final func invokeOnInitialized() {
ReactLogger.log(level: .info, message: "Camera onInitialized()", alsoLogToJS: true) ReactLogger.log(level: .info, message: "Camera initialized!", alsoLogToJS: true)
guard let onInitialized = self.onInitialized else { return } guard let onInitialized = self.onInitialized else { return }
onInitialized([String: Any]()) onInitialized([String: Any]())
} }
@ -258,10 +267,41 @@ final class CameraView: UIView {
private let captureSession = AVCaptureSession() private let captureSession = AVCaptureSession()
// pragma MARK: Session, Device and Format Configuration // pragma MARK: Session, Device and Format Configuration
/**
Configures the Audio session to allow background-music playback while recording.
*/
private final func configureAudioSession() {
let start = DispatchTime.now()
let audioSession = AVAudioSession.sharedInstance()
do {
if captureSession.automaticallyConfiguresApplicationAudioSession {
captureSession.beginConfiguration()
captureSession.automaticallyConfiguresApplicationAudioSession = false
captureSession.commitConfiguration()
}
if audioSession.category != .playAndRecord {
// allow background music playback
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playAndRecord, options: [.mixWithOthers, .allowBluetooth, .defaultToSpeaker])
}
// activate current audio session because camera is active
try AVAudioSession.sharedInstance().setActive(true)
} catch let error as NSError {
self.invokeOnError(.session(.audioSessionSetupFailed(reason: error.localizedDescription)), cause: error)
captureSession.beginConfiguration()
captureSession.automaticallyConfiguresApplicationAudioSession = true
captureSession.commitConfiguration()
}
let end = DispatchTime.now()
let nanoTime = end.uptimeNanoseconds - start.uptimeNanoseconds
ReactLogger.log(level: .info, message: "Configured Audio session in \(Double(nanoTime) / 1_000_000)ms!")
}
/** /**
Configures the Capture Session. Configures the Capture Session.
*/ */
private final func configureCaptureSession() { private final func configureCaptureSession() {
ReactLogger.logJS(level: .info, message: "Configuring Session...")
isReady = false isReady = false
#if targetEnvironment(simulator) #if targetEnvironment(simulator)
@ -299,17 +339,6 @@ final class CameraView: UIView {
} }
// INPUTS // INPUTS
// Audio Setup
do {
captureSession.automaticallyConfiguresApplicationAudioSession = false
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playAndRecord, options: [.mixWithOthers, .allowBluetooth, .defaultToSpeaker])
try AVAudioSession.sharedInstance().setActive(true)
} catch let error as NSError {
// not critical, so don't return
invokeOnError(.session(.audioSessionSetupFailed(reason: error.description)))
captureSession.automaticallyConfiguresApplicationAudioSession = true // fallback to auto-setup
}
// Video Input // Video Input
do { do {
if let videoDeviceInput = self.videoDeviceInput { if let videoDeviceInput = self.videoDeviceInput {
@ -408,15 +437,16 @@ final class CameraView: UIView {
metadataOutput!.metadataObjectTypes = objectTypes metadataOutput!.metadataObjectTypes = objectTypes
} }
ReactLogger.log(level: .info, message: "Camera initialized!")
invokeOnInitialized() invokeOnInitialized()
isReady = true isReady = true
ReactLogger.logJS(level: .info, message: "Session successfully configured!")
} }
/** /**
Configures the Video Device to find the best matching Format. Configures the Video Device to find the best matching Format.
*/ */
private final func configureFormat() { private final func configureFormat() {
ReactLogger.logJS(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
@ -440,6 +470,7 @@ final class CameraView: UIView {
try device.lockForConfiguration() try device.lockForConfiguration()
device.activeFormat = format device.activeFormat = format
device.unlockForConfiguration() device.unlockForConfiguration()
ReactLogger.logJS(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)
} }
@ -449,6 +480,7 @@ final class CameraView: UIView {
Configures the Video Device with the given FPS, HDR and ColorSpace. Configures the Video Device with the given FPS, HDR and ColorSpace.
*/ */
private final func configureDevice() { private final func configureDevice() {
ReactLogger.logJS(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))
} }
@ -487,6 +519,7 @@ final class CameraView: UIView {
} }
device.unlockForConfiguration() device.unlockForConfiguration()
ReactLogger.logJS(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)
} }

View File

@ -8,7 +8,7 @@
import Foundation import Foundation
let context = "Camera" let context = "VisionCamera"
// MARK: - ReactLogger // MARK: - ReactLogger