Extract AVCaptureSession
and AVAudioSession
setup to extensions
This commit is contained in:
parent
56c67f25c7
commit
9404b93dc3
@ -279,7 +279,7 @@ PODS:
|
|||||||
- react-native-video/Video (= 5.1.1)
|
- react-native-video/Video (= 5.1.1)
|
||||||
- react-native-video/Video (5.1.1):
|
- react-native-video/Video (5.1.1):
|
||||||
- React-Core
|
- React-Core
|
||||||
- react-native-vision-camera (1.0.3):
|
- react-native-vision-camera (1.0.4):
|
||||||
- React-Core
|
- React-Core
|
||||||
- React-perflogger (0.64.0)
|
- React-perflogger (0.64.0)
|
||||||
- React-RCTActionSheet (0.64.0):
|
- React-RCTActionSheet (0.64.0):
|
||||||
@ -554,9 +554,9 @@ EXTERNAL SOURCES:
|
|||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
|
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
|
||||||
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
|
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
|
||||||
DoubleConversion: cde416483dac037923206447da6e1454df403714
|
DoubleConversion: cf9b38bf0b2d048436d9a82ad2abe1404f11e7de
|
||||||
FBLazyVector: 49cbe4b43e445b06bf29199b6ad2057649e4c8f5
|
FBLazyVector: 49cbe4b43e445b06bf29199b6ad2057649e4c8f5
|
||||||
FBReactNativeSpec: e800dc469340da7e8e47f45145f69d75a7b06874
|
FBReactNativeSpec: 06c29ba6920affcab9cda6154497386d21f43410
|
||||||
Flipper: d3da1aa199aad94455ae725e9f3aa43f3ec17021
|
Flipper: d3da1aa199aad94455ae725e9f3aa43f3ec17021
|
||||||
Flipper-DoubleConversion: 38631e41ef4f9b12861c67d17cb5518d06badc41
|
Flipper-DoubleConversion: 38631e41ef4f9b12861c67d17cb5518d06badc41
|
||||||
Flipper-Folly: f7a3caafbd74bda4827954fd7a6e000e36355489
|
Flipper-Folly: f7a3caafbd74bda4827954fd7a6e000e36355489
|
||||||
@ -564,7 +564,7 @@ SPEC CHECKSUMS:
|
|||||||
Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9
|
Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9
|
||||||
Flipper-RSocket: 602921fee03edacf18f5d6f3d3594ba477f456e5
|
Flipper-RSocket: 602921fee03edacf18f5d6f3d3594ba477f456e5
|
||||||
FlipperKit: 8a20b5c5fcf9436cac58551dc049867247f64b00
|
FlipperKit: 8a20b5c5fcf9436cac58551dc049867247f64b00
|
||||||
glog: 40a13f7840415b9a77023fbcae0f1e6f43192af3
|
glog: 73c2498ac6884b13ede40eda8228cb1eee9d9d62
|
||||||
hermes-engine: 7d97ba46a1e29bacf3e3c61ecb2804a5ddd02d4f
|
hermes-engine: 7d97ba46a1e29bacf3e3c61ecb2804a5ddd02d4f
|
||||||
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
|
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
|
||||||
OpenSSL-Universal: 1aa4f6a6ee7256b83db99ec1ccdaa80d10f9af9b
|
OpenSSL-Universal: 1aa4f6a6ee7256b83db99ec1ccdaa80d10f9af9b
|
||||||
@ -583,7 +583,7 @@ SPEC CHECKSUMS:
|
|||||||
react-native-cameraroll: 1965db75c851b15e77a22ca0ac78e32af6b571ae
|
react-native-cameraroll: 1965db75c851b15e77a22ca0ac78e32af6b571ae
|
||||||
react-native-slider: e99fc201cefe81270fc9d81714a7a0f5e566b168
|
react-native-slider: e99fc201cefe81270fc9d81714a7a0f5e566b168
|
||||||
react-native-video: 0bb76b6d6b77da3009611586c7dbf817b947f30e
|
react-native-video: 0bb76b6d6b77da3009611586c7dbf817b947f30e
|
||||||
react-native-vision-camera: 83bc97de3bc01be3a99037dd4cf6c672aef632b7
|
react-native-vision-camera: d0d6fdd334f1536d016b3ca92064f25a6312e09c
|
||||||
React-perflogger: 9c547d8f06b9bf00cb447f2b75e8d7f19b7e02af
|
React-perflogger: 9c547d8f06b9bf00cb447f2b75e8d7f19b7e02af
|
||||||
React-RCTActionSheet: 3080b6e12e0e1a5b313c8c0050699b5c794a1b11
|
React-RCTActionSheet: 3080b6e12e0e1a5b313c8c0050699b5c794a1b11
|
||||||
React-RCTAnimation: 3f96f21a497ae7dabf4d2f150ee43f906aaf516f
|
React-RCTAnimation: 3f96f21a497ae7dabf4d2f150ee43f906aaf516f
|
||||||
|
75
ios/CameraView+AVAudioSession.swift
Normal file
75
ios/CameraView+AVAudioSession.swift
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
//
|
||||||
|
// CameraView+AVAudioSession.swift
|
||||||
|
// VisionCamera
|
||||||
|
//
|
||||||
|
// Created by Marc Rousavy on 26.03.21.
|
||||||
|
// Copyright © 2021 Facebook. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import AVFoundation
|
||||||
|
|
||||||
|
/**
|
||||||
|
Extension for CameraView that sets up the AVAudioSession.
|
||||||
|
*/
|
||||||
|
extension CameraView {
|
||||||
|
@objc
|
||||||
|
func audioSessionInterrupted(notification: Notification) {
|
||||||
|
ReactLogger.log(level: .error, message: "The Audio Session was interrupted!")
|
||||||
|
guard let userInfo = notification.userInfo,
|
||||||
|
let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
|
||||||
|
let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch type {
|
||||||
|
case .began:
|
||||||
|
// TODO: Should we also disable the camera here? I think it will throw a runtime error
|
||||||
|
// disable audio session
|
||||||
|
try? AVAudioSession.sharedInstance().setActive(false)
|
||||||
|
break
|
||||||
|
case .ended:
|
||||||
|
guard let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else { return }
|
||||||
|
let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
|
||||||
|
if options.contains(.shouldResume) {
|
||||||
|
// restart audio session because interruption is over
|
||||||
|
configureAudioSession()
|
||||||
|
} else {
|
||||||
|
ReactLogger.log(level: .error, message: "Cannot resume interrupted Audio Session!")
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final func setAutomaticallyConfiguresAudioSession(_ automaticallyConfiguresAudioSession: Bool) {
|
||||||
|
if captureSession.automaticallyConfiguresApplicationAudioSession != automaticallyConfiguresAudioSession {
|
||||||
|
captureSession.beginConfiguration()
|
||||||
|
captureSession.automaticallyConfiguresApplicationAudioSession = automaticallyConfiguresAudioSession
|
||||||
|
captureSession.commitConfiguration()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Configures the Audio session to allow background-music playback while recording.
|
||||||
|
*/
|
||||||
|
internal final func configureAudioSession() {
|
||||||
|
let start = DispatchTime.now()
|
||||||
|
do {
|
||||||
|
setAutomaticallyConfiguresAudioSession(false)
|
||||||
|
let audioSession = AVAudioSession.sharedInstance()
|
||||||
|
if audioSession.category != .playAndRecord {
|
||||||
|
// allow background music playback
|
||||||
|
try audioSession.setCategory(AVAudioSession.Category.playAndRecord, options: [.mixWithOthers, .allowBluetoothA2DP, .defaultToSpeaker])
|
||||||
|
}
|
||||||
|
// TODO: Use https://developer.apple.com/documentation/avfaudio/avaudiosession/3726094-setprefersnointerruptionsfromsys
|
||||||
|
audioSession.trySetAllowHaptics(true)
|
||||||
|
// activate current audio session because camera is active
|
||||||
|
try audioSession.setActive(true)
|
||||||
|
} catch let error as NSError {
|
||||||
|
self.invokeOnError(.session(.audioSessionSetupFailed(reason: error.description)), cause: error)
|
||||||
|
setAutomaticallyConfiguresAudioSession(true)
|
||||||
|
}
|
||||||
|
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!")
|
||||||
|
}
|
||||||
|
}
|
261
ios/CameraView+AVCaptureSession.swift
Normal file
261
ios/CameraView+AVCaptureSession.swift
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
//
|
||||||
|
// CameraView+AVCaptureSession.swift
|
||||||
|
// VisionCamera
|
||||||
|
//
|
||||||
|
// Created by Marc Rousavy on 26.03.21.
|
||||||
|
// Copyright © 2021 Facebook. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import AVFoundation
|
||||||
|
|
||||||
|
/**
|
||||||
|
Extension for CameraView that sets up the AVCaptureSession, Device and Format.
|
||||||
|
*/
|
||||||
|
extension CameraView {
|
||||||
|
@objc
|
||||||
|
func sessionRuntimeError(notification: Notification) {
|
||||||
|
ReactLogger.log(level: .error, message: "Unexpected Camera Runtime Error occured!")
|
||||||
|
guard let error = notification.userInfo?[AVCaptureSessionErrorKey] as? AVError else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
invokeOnError(.unknown(message: error._nsError.description), cause: error._nsError)
|
||||||
|
|
||||||
|
if isActive {
|
||||||
|
// restart capture session after an error occured
|
||||||
|
queue.async {
|
||||||
|
self.captureSession.startRunning()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
Configures the Capture Session.
|
||||||
|
*/
|
||||||
|
internal final func configureCaptureSession() {
|
||||||
|
ReactLogger.logJS(level: .info, message: "Configuring Session...")
|
||||||
|
isReady = false
|
||||||
|
|
||||||
|
#if targetEnvironment(simulator)
|
||||||
|
return invokeOnError(.device(.notAvailableOnSimulator))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
guard cameraId != nil else {
|
||||||
|
return invokeOnError(.device(.noDevice))
|
||||||
|
}
|
||||||
|
let cameraId = self.cameraId! as String
|
||||||
|
|
||||||
|
ReactLogger.log(level: .info, message: "Initializing Camera with device \(cameraId)...")
|
||||||
|
captureSession.beginConfiguration()
|
||||||
|
defer {
|
||||||
|
captureSession.commitConfiguration()
|
||||||
|
}
|
||||||
|
|
||||||
|
if let preset = self.preset {
|
||||||
|
var sessionPreset: AVCaptureSession.Preset?
|
||||||
|
do {
|
||||||
|
sessionPreset = try AVCaptureSession.Preset(withString: preset)
|
||||||
|
} catch let EnumParserError.unsupportedOS(supportedOnOS: os) {
|
||||||
|
return invokeOnError(.parameter(.unsupportedOS(unionName: "Preset", receivedValue: preset, supportedOnOs: os)))
|
||||||
|
} catch {
|
||||||
|
return invokeOnError(.parameter(.invalid(unionName: "Preset", receivedValue: preset)))
|
||||||
|
}
|
||||||
|
if sessionPreset != nil {
|
||||||
|
if captureSession.canSetSessionPreset(sessionPreset!) {
|
||||||
|
captureSession.sessionPreset = sessionPreset!
|
||||||
|
} else {
|
||||||
|
// non-fatal error, so continue with configuration
|
||||||
|
invokeOnError(.format(.invalidPreset(preset: preset)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// INPUTS
|
||||||
|
// Video Input
|
||||||
|
do {
|
||||||
|
if let videoDeviceInput = self.videoDeviceInput {
|
||||||
|
captureSession.removeInput(videoDeviceInput)
|
||||||
|
}
|
||||||
|
guard let videoDevice = AVCaptureDevice(uniqueID: cameraId) else {
|
||||||
|
return invokeOnError(.device(.invalid))
|
||||||
|
}
|
||||||
|
zoom = NSNumber(value: Double(videoDevice.neutralZoomPercent))
|
||||||
|
videoDeviceInput = try AVCaptureDeviceInput(device: videoDevice)
|
||||||
|
guard captureSession.canAddInput(videoDeviceInput!) else {
|
||||||
|
return invokeOnError(.parameter(.unsupportedInput(inputDescriptor: "video-input")))
|
||||||
|
}
|
||||||
|
captureSession.addInput(videoDeviceInput!)
|
||||||
|
} catch {
|
||||||
|
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
|
||||||
|
if let photoOutput = self.photoOutput {
|
||||||
|
captureSession.removeOutput(photoOutput)
|
||||||
|
}
|
||||||
|
// Photo Output
|
||||||
|
photoOutput = AVCapturePhotoOutput()
|
||||||
|
photoOutput!.isDepthDataDeliveryEnabled = photoOutput!.isDepthDataDeliverySupported && enableDepthData
|
||||||
|
if let enableHighResolutionCapture = self.enableHighResolutionCapture?.boolValue {
|
||||||
|
photoOutput!.isHighResolutionCaptureEnabled = enableHighResolutionCapture
|
||||||
|
}
|
||||||
|
if #available(iOS 12.0, *) {
|
||||||
|
photoOutput!.isPortraitEffectsMatteDeliveryEnabled = photoOutput!.isPortraitEffectsMatteDeliverySupported && self.enablePortraitEffectsMatteDelivery
|
||||||
|
}
|
||||||
|
guard captureSession.canAddOutput(photoOutput!) else {
|
||||||
|
return invokeOnError(.parameter(.unsupportedOutput(outputDescriptor: "photo-output")))
|
||||||
|
}
|
||||||
|
captureSession.addOutput(photoOutput!)
|
||||||
|
if videoDeviceInput!.device.position == .front {
|
||||||
|
photoOutput!.mirror()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Video Output
|
||||||
|
if let movieOutput = self.movieOutput {
|
||||||
|
captureSession.removeOutput(movieOutput)
|
||||||
|
}
|
||||||
|
movieOutput = AVCaptureMovieFileOutput()
|
||||||
|
guard captureSession.canAddOutput(movieOutput!) else {
|
||||||
|
return invokeOnError(.parameter(.unsupportedOutput(outputDescriptor: "movie-output")))
|
||||||
|
}
|
||||||
|
captureSession.addOutput(movieOutput!)
|
||||||
|
if videoDeviceInput!.device.position == .front {
|
||||||
|
movieOutput!.mirror()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Barcode Scanning
|
||||||
|
if let metadataOutput = self.metadataOutput {
|
||||||
|
captureSession.removeOutput(metadataOutput)
|
||||||
|
}
|
||||||
|
if let scannableCodes = self.scannableCodes {
|
||||||
|
// scannableCodes prop is not nil, so enable barcode scanning.
|
||||||
|
guard onCodeScanned != nil else {
|
||||||
|
return invokeOnError(.parameter(.invalidCombination(provided: "scannableCodes", missing: "onCodeScanned")))
|
||||||
|
}
|
||||||
|
metadataOutput = AVCaptureMetadataOutput()
|
||||||
|
guard captureSession.canAddOutput(metadataOutput!) else {
|
||||||
|
return invokeOnError(.parameter(.unsupportedOutput(outputDescriptor: "metadata-output")))
|
||||||
|
}
|
||||||
|
captureSession.addOutput(metadataOutput!)
|
||||||
|
metadataOutput!.setMetadataObjectsDelegate(self, queue: queue)
|
||||||
|
var objectTypes: [AVMetadataObject.ObjectType] = []
|
||||||
|
scannableCodes.forEach { code in
|
||||||
|
do {
|
||||||
|
objectTypes.append(try AVMetadataObject.ObjectType(withString: code))
|
||||||
|
} catch let EnumParserError.unsupportedOS(supportedOnOS: os) {
|
||||||
|
invokeOnError(.parameter(.unsupportedOS(unionName: "CodeType", receivedValue: code, supportedOnOs: os)))
|
||||||
|
} catch {
|
||||||
|
invokeOnError(.parameter(.invalid(unionName: "CodeType", receivedValue: code)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
metadataOutput!.metadataObjectTypes = objectTypes
|
||||||
|
}
|
||||||
|
|
||||||
|
invokeOnInitialized()
|
||||||
|
isReady = true
|
||||||
|
ReactLogger.logJS(level: .info, message: "Session successfully configured!")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Configures the Video Device to find the best matching Format.
|
||||||
|
*/
|
||||||
|
internal final func configureFormat() {
|
||||||
|
ReactLogger.logJS(level: .info, message: "Configuring Format...")
|
||||||
|
guard let filter = self.format else {
|
||||||
|
// Format Filter was null. Ignore it.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let device = videoDeviceInput?.device else {
|
||||||
|
return invokeOnError(.session(.cameraNotReady))
|
||||||
|
}
|
||||||
|
|
||||||
|
if device.activeFormat.matchesFilter(filter) {
|
||||||
|
ReactLogger.log(level: .info, message: "Active format already matches filter.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// get matching format
|
||||||
|
let matchingFormats = device.formats.filter { $0.matchesFilter(filter) }.sorted { $0.isBetterThan($1) }
|
||||||
|
guard let format = matchingFormats.first else {
|
||||||
|
return invokeOnError(.format(.invalidFormat))
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
try device.lockForConfiguration()
|
||||||
|
device.activeFormat = format
|
||||||
|
device.unlockForConfiguration()
|
||||||
|
ReactLogger.logJS(level: .info, message: "Format successfully configured!")
|
||||||
|
} catch let error as NSError {
|
||||||
|
return invokeOnError(.device(.configureError), cause: error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Configures the Video Device with the given FPS, HDR and ColorSpace.
|
||||||
|
*/
|
||||||
|
internal final func configureDevice() {
|
||||||
|
ReactLogger.logJS(level: .info, message: "Configuring Device...")
|
||||||
|
guard let device = videoDeviceInput?.device else {
|
||||||
|
return invokeOnError(.session(.cameraNotReady))
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
try device.lockForConfiguration()
|
||||||
|
|
||||||
|
if let fps = self.fps?.int32Value {
|
||||||
|
let duration = CMTimeMake(value: 1, timescale: fps)
|
||||||
|
device.activeVideoMinFrameDuration = duration
|
||||||
|
device.activeVideoMaxFrameDuration = duration
|
||||||
|
} else {
|
||||||
|
device.activeVideoMinFrameDuration = CMTime.invalid
|
||||||
|
device.activeVideoMaxFrameDuration = CMTime.invalid
|
||||||
|
}
|
||||||
|
if hdr != nil {
|
||||||
|
if hdr == true && !device.activeFormat.isVideoHDRSupported {
|
||||||
|
return invokeOnError(.format(.invalidHdr))
|
||||||
|
}
|
||||||
|
if !device.automaticallyAdjustsVideoHDREnabled {
|
||||||
|
if device.isVideoHDREnabled != hdr!.boolValue {
|
||||||
|
device.isVideoHDREnabled = hdr!.boolValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if lowLightBoost != nil {
|
||||||
|
if lowLightBoost == true && !device.isLowLightBoostSupported {
|
||||||
|
return invokeOnError(.device(.lowLightBoostNotSupported))
|
||||||
|
}
|
||||||
|
if device.automaticallyEnablesLowLightBoostWhenAvailable != lowLightBoost!.boolValue {
|
||||||
|
device.automaticallyEnablesLowLightBoostWhenAvailable = lowLightBoost!.boolValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if colorSpace != nil, let avColorSpace = try? AVCaptureColorSpace(string: String(colorSpace!)) {
|
||||||
|
device.activeColorSpace = avColorSpace
|
||||||
|
}
|
||||||
|
|
||||||
|
device.unlockForConfiguration()
|
||||||
|
ReactLogger.logJS(level: .info, message: "Device successfully configured!")
|
||||||
|
} catch let error as NSError {
|
||||||
|
return invokeOnError(.device(.configureError), cause: error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -136,6 +136,8 @@ final class CameraView: UIView {
|
|||||||
override class var layerClass: AnyClass {
|
override class var layerClass: AnyClass {
|
||||||
return AVCaptureVideoPreviewLayer.self
|
return AVCaptureVideoPreviewLayer.self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal let captureSession = AVCaptureSession()
|
||||||
|
|
||||||
// pragma MARK: Exported Properties
|
// pragma MARK: Exported Properties
|
||||||
// props that require reconfiguring
|
// props that require reconfiguring
|
||||||
@ -197,49 +199,6 @@ final class CameraView: UIView {
|
|||||||
return layer as! AVCaptureVideoPreviewLayer
|
return layer as! AVCaptureVideoPreviewLayer
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
|
||||||
func sessionRuntimeError(notification: Notification) {
|
|
||||||
ReactLogger.log(level: .error, message: "Unexpected Camera Runtime Error occured!")
|
|
||||||
guard let error = notification.userInfo?[AVCaptureSessionErrorKey] as? AVError else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if isActive {
|
|
||||||
// restart capture session after an error occured
|
|
||||||
queue.async {
|
|
||||||
self.captureSession.startRunning()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
invokeOnError(.unknown(message: error.description), cause: error as NSError)
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc
|
|
||||||
func audioSessionInterrupted(notification: Notification) {
|
|
||||||
ReactLogger.log(level: .error, message: "The Audio Session was interrupted!")
|
|
||||||
guard let userInfo = notification.userInfo,
|
|
||||||
let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
|
|
||||||
let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch type {
|
|
||||||
case .began:
|
|
||||||
// TODO: Should we also disable the camera here? I think it will throw a runtime error
|
|
||||||
// disable audio session
|
|
||||||
try? AVAudioSession.sharedInstance().setActive(false)
|
|
||||||
break
|
|
||||||
case .ended:
|
|
||||||
guard let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else { return }
|
|
||||||
let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
|
|
||||||
if options.contains(.shouldResume) {
|
|
||||||
// restart audio session because interruption is over
|
|
||||||
configureAudioSession()
|
|
||||||
} else {
|
|
||||||
ReactLogger.log(level: .error, message: "Cannot resume interrupted Audio Session!")
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal final func setTorchMode(_ torchMode: String) {
|
internal final func setTorchMode(_ torchMode: String) {
|
||||||
guard let device = videoDeviceInput?.device else {
|
guard let device = videoDeviceInput?.device else {
|
||||||
return invokeOnError(.session(.cameraNotReady))
|
return invokeOnError(.session(.cameraNotReady))
|
||||||
@ -302,269 +261,4 @@ final class CameraView: UIView {
|
|||||||
onInitialized([String: Any]())
|
onInitialized([String: Any]())
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Private
|
|
||||||
|
|
||||||
private let captureSession = AVCaptureSession()
|
|
||||||
|
|
||||||
private final func setAutomaticallyConfiguresAudioSession(_ automaticallyConfiguresAudioSession: Bool) {
|
|
||||||
if captureSession.automaticallyConfiguresApplicationAudioSession != automaticallyConfiguresAudioSession {
|
|
||||||
captureSession.beginConfiguration()
|
|
||||||
captureSession.automaticallyConfiguresApplicationAudioSession = automaticallyConfiguresAudioSession
|
|
||||||
captureSession.commitConfiguration()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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()
|
|
||||||
do {
|
|
||||||
setAutomaticallyConfiguresAudioSession(false)
|
|
||||||
let audioSession = AVAudioSession.sharedInstance()
|
|
||||||
if audioSession.category != .playAndRecord {
|
|
||||||
// allow background music playback
|
|
||||||
try audioSession.setCategory(AVAudioSession.Category.playAndRecord, options: [.mixWithOthers, .allowBluetoothA2DP, .defaultToSpeaker])
|
|
||||||
}
|
|
||||||
// TODO: Use https://developer.apple.com/documentation/avfaudio/avaudiosession/3726094-setprefersnointerruptionsfromsys
|
|
||||||
audioSession.trySetAllowHaptics(true)
|
|
||||||
// activate current audio session because camera is active
|
|
||||||
try audioSession.setActive(true)
|
|
||||||
} catch let error as NSError {
|
|
||||||
self.invokeOnError(.session(.audioSessionSetupFailed(reason: error.description)), cause: error)
|
|
||||||
setAutomaticallyConfiguresAudioSession(true)
|
|
||||||
}
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
private final func configureCaptureSession() {
|
|
||||||
ReactLogger.logJS(level: .info, message: "Configuring Session...")
|
|
||||||
isReady = false
|
|
||||||
|
|
||||||
#if targetEnvironment(simulator)
|
|
||||||
return invokeOnError(.device(.notAvailableOnSimulator))
|
|
||||||
#endif
|
|
||||||
|
|
||||||
guard cameraId != nil else {
|
|
||||||
return invokeOnError(.device(.noDevice))
|
|
||||||
}
|
|
||||||
let cameraId = self.cameraId! as String
|
|
||||||
|
|
||||||
ReactLogger.log(level: .info, message: "Initializing Camera with device \(cameraId)...")
|
|
||||||
captureSession.beginConfiguration()
|
|
||||||
defer {
|
|
||||||
captureSession.commitConfiguration()
|
|
||||||
}
|
|
||||||
|
|
||||||
if let preset = self.preset {
|
|
||||||
var sessionPreset: AVCaptureSession.Preset?
|
|
||||||
do {
|
|
||||||
sessionPreset = try AVCaptureSession.Preset(withString: preset)
|
|
||||||
} catch let EnumParserError.unsupportedOS(supportedOnOS: os) {
|
|
||||||
return invokeOnError(.parameter(.unsupportedOS(unionName: "Preset", receivedValue: preset, supportedOnOs: os)))
|
|
||||||
} catch {
|
|
||||||
return invokeOnError(.parameter(.invalid(unionName: "Preset", receivedValue: preset)))
|
|
||||||
}
|
|
||||||
if sessionPreset != nil {
|
|
||||||
if captureSession.canSetSessionPreset(sessionPreset!) {
|
|
||||||
captureSession.sessionPreset = sessionPreset!
|
|
||||||
} else {
|
|
||||||
// non-fatal error, so continue with configuration
|
|
||||||
invokeOnError(.format(.invalidPreset(preset: preset)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// INPUTS
|
|
||||||
// Video Input
|
|
||||||
do {
|
|
||||||
if let videoDeviceInput = self.videoDeviceInput {
|
|
||||||
captureSession.removeInput(videoDeviceInput)
|
|
||||||
}
|
|
||||||
guard let videoDevice = AVCaptureDevice(uniqueID: cameraId) else {
|
|
||||||
return invokeOnError(.device(.invalid))
|
|
||||||
}
|
|
||||||
zoom = NSNumber(value: Double(videoDevice.neutralZoomPercent))
|
|
||||||
videoDeviceInput = try AVCaptureDeviceInput(device: videoDevice)
|
|
||||||
guard captureSession.canAddInput(videoDeviceInput!) else {
|
|
||||||
return invokeOnError(.parameter(.unsupportedInput(inputDescriptor: "video-input")))
|
|
||||||
}
|
|
||||||
captureSession.addInput(videoDeviceInput!)
|
|
||||||
} catch {
|
|
||||||
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
|
|
||||||
if let photoOutput = self.photoOutput {
|
|
||||||
captureSession.removeOutput(photoOutput)
|
|
||||||
}
|
|
||||||
// Photo Output
|
|
||||||
photoOutput = AVCapturePhotoOutput()
|
|
||||||
photoOutput!.isDepthDataDeliveryEnabled = photoOutput!.isDepthDataDeliverySupported && enableDepthData
|
|
||||||
if let enableHighResolutionCapture = self.enableHighResolutionCapture?.boolValue {
|
|
||||||
photoOutput!.isHighResolutionCaptureEnabled = enableHighResolutionCapture
|
|
||||||
}
|
|
||||||
if #available(iOS 12.0, *) {
|
|
||||||
photoOutput!.isPortraitEffectsMatteDeliveryEnabled = photoOutput!.isPortraitEffectsMatteDeliverySupported && self.enablePortraitEffectsMatteDelivery
|
|
||||||
}
|
|
||||||
guard captureSession.canAddOutput(photoOutput!) else {
|
|
||||||
return invokeOnError(.parameter(.unsupportedOutput(outputDescriptor: "photo-output")))
|
|
||||||
}
|
|
||||||
captureSession.addOutput(photoOutput!)
|
|
||||||
if videoDeviceInput!.device.position == .front {
|
|
||||||
photoOutput!.mirror()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Video Output
|
|
||||||
if let movieOutput = self.movieOutput {
|
|
||||||
captureSession.removeOutput(movieOutput)
|
|
||||||
}
|
|
||||||
movieOutput = AVCaptureMovieFileOutput()
|
|
||||||
guard captureSession.canAddOutput(movieOutput!) else {
|
|
||||||
return invokeOnError(.parameter(.unsupportedOutput(outputDescriptor: "movie-output")))
|
|
||||||
}
|
|
||||||
captureSession.addOutput(movieOutput!)
|
|
||||||
if videoDeviceInput!.device.position == .front {
|
|
||||||
movieOutput!.mirror()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Barcode Scanning
|
|
||||||
if let metadataOutput = self.metadataOutput {
|
|
||||||
captureSession.removeOutput(metadataOutput)
|
|
||||||
}
|
|
||||||
if let scannableCodes = self.scannableCodes {
|
|
||||||
// scannableCodes prop is not nil, so enable barcode scanning.
|
|
||||||
guard onCodeScanned != nil else {
|
|
||||||
return invokeOnError(.parameter(.invalidCombination(provided: "scannableCodes", missing: "onCodeScanned")))
|
|
||||||
}
|
|
||||||
metadataOutput = AVCaptureMetadataOutput()
|
|
||||||
guard captureSession.canAddOutput(metadataOutput!) else {
|
|
||||||
return invokeOnError(.parameter(.unsupportedOutput(outputDescriptor: "metadata-output")))
|
|
||||||
}
|
|
||||||
captureSession.addOutput(metadataOutput!)
|
|
||||||
metadataOutput!.setMetadataObjectsDelegate(self, queue: queue)
|
|
||||||
var objectTypes: [AVMetadataObject.ObjectType] = []
|
|
||||||
scannableCodes.forEach { code in
|
|
||||||
do {
|
|
||||||
objectTypes.append(try AVMetadataObject.ObjectType(withString: code))
|
|
||||||
} catch let EnumParserError.unsupportedOS(supportedOnOS: os) {
|
|
||||||
invokeOnError(.parameter(.unsupportedOS(unionName: "CodeType", receivedValue: code, supportedOnOs: os)))
|
|
||||||
} catch {
|
|
||||||
invokeOnError(.parameter(.invalid(unionName: "CodeType", receivedValue: code)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
metadataOutput!.metadataObjectTypes = objectTypes
|
|
||||||
}
|
|
||||||
|
|
||||||
invokeOnInitialized()
|
|
||||||
isReady = true
|
|
||||||
ReactLogger.logJS(level: .info, message: "Session successfully configured!")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Configures the Video Device to find the best matching Format.
|
|
||||||
*/
|
|
||||||
private final func configureFormat() {
|
|
||||||
ReactLogger.logJS(level: .info, message: "Configuring Format...")
|
|
||||||
guard let filter = self.format else {
|
|
||||||
// Format Filter was null. Ignore it.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard let device = videoDeviceInput?.device else {
|
|
||||||
return invokeOnError(.session(.cameraNotReady))
|
|
||||||
}
|
|
||||||
|
|
||||||
if device.activeFormat.matchesFilter(filter) {
|
|
||||||
ReactLogger.log(level: .info, message: "Active format already matches filter.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// get matching format
|
|
||||||
let matchingFormats = device.formats.filter { $0.matchesFilter(filter) }.sorted { $0.isBetterThan($1) }
|
|
||||||
guard let format = matchingFormats.first else {
|
|
||||||
return invokeOnError(.format(.invalidFormat))
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
try device.lockForConfiguration()
|
|
||||||
device.activeFormat = format
|
|
||||||
device.unlockForConfiguration()
|
|
||||||
ReactLogger.logJS(level: .info, message: "Format successfully configured!")
|
|
||||||
} catch let error as NSError {
|
|
||||||
return invokeOnError(.device(.configureError), cause: error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Configures the Video Device with the given FPS, HDR and ColorSpace.
|
|
||||||
*/
|
|
||||||
private final func configureDevice() {
|
|
||||||
ReactLogger.logJS(level: .info, message: "Configuring Device...")
|
|
||||||
guard let device = videoDeviceInput?.device else {
|
|
||||||
return invokeOnError(.session(.cameraNotReady))
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
try device.lockForConfiguration()
|
|
||||||
|
|
||||||
if let fps = self.fps?.int32Value {
|
|
||||||
let duration = CMTimeMake(value: 1, timescale: fps)
|
|
||||||
device.activeVideoMinFrameDuration = duration
|
|
||||||
device.activeVideoMaxFrameDuration = duration
|
|
||||||
} else {
|
|
||||||
device.activeVideoMinFrameDuration = CMTime.invalid
|
|
||||||
device.activeVideoMaxFrameDuration = CMTime.invalid
|
|
||||||
}
|
|
||||||
if hdr != nil {
|
|
||||||
if hdr == true && !device.activeFormat.isVideoHDRSupported {
|
|
||||||
return invokeOnError(.format(.invalidHdr))
|
|
||||||
}
|
|
||||||
if !device.automaticallyAdjustsVideoHDREnabled {
|
|
||||||
if device.isVideoHDREnabled != hdr!.boolValue {
|
|
||||||
device.isVideoHDREnabled = hdr!.boolValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if lowLightBoost != nil {
|
|
||||||
if lowLightBoost == true && !device.isLowLightBoostSupported {
|
|
||||||
return invokeOnError(.device(.lowLightBoostNotSupported))
|
|
||||||
}
|
|
||||||
if device.automaticallyEnablesLowLightBoostWhenAvailable != lowLightBoost!.boolValue {
|
|
||||||
device.automaticallyEnablesLowLightBoostWhenAvailable = lowLightBoost!.boolValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if colorSpace != nil, let avColorSpace = try? AVCaptureColorSpace(string: String(colorSpace!)) {
|
|
||||||
device.activeColorSpace = avColorSpace
|
|
||||||
}
|
|
||||||
|
|
||||||
device.unlockForConfiguration()
|
|
||||||
ReactLogger.logJS(level: .info, message: "Device successfully configured!")
|
|
||||||
} catch let error as NSError {
|
|
||||||
return invokeOnError(.device(.configureError), cause: error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,8 @@ extension AVAudioSession {
|
|||||||
*/
|
*/
|
||||||
func trySetAllowHaptics(_ allowHaptics: Bool) {
|
func trySetAllowHaptics(_ allowHaptics: Bool) {
|
||||||
if #available(iOS 13.0, *) {
|
if #available(iOS 13.0, *) {
|
||||||
if !audioSession.allowHapticsAndSystemSoundsDuringRecording {
|
if !self.allowHapticsAndSystemSoundsDuringRecording {
|
||||||
try? audioSession.setAllowHapticsAndSystemSoundsDuringRecording(true)
|
try? self.setAllowHapticsAndSystemSoundsDuringRecording(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,8 +27,8 @@ class PhotoCaptureDelegate: NSObject, AVCapturePhotoCaptureDelegate {
|
|||||||
defer {
|
defer {
|
||||||
delegatesReferences.removeAll(where: { $0 == self })
|
delegatesReferences.removeAll(where: { $0 == self })
|
||||||
}
|
}
|
||||||
if let error = error {
|
if let error = error as NSError? {
|
||||||
return promise.reject(error: .capture(.unknown(message: error.description)), cause: error as NSError)
|
return promise.reject(error: .capture(.unknown(message: error.description)), cause: error)
|
||||||
}
|
}
|
||||||
|
|
||||||
let error = ErrorPointer(nilLiteral: ())
|
let error = ErrorPointer(nilLiteral: ())
|
||||||
@ -66,8 +66,8 @@ class PhotoCaptureDelegate: NSObject, AVCapturePhotoCaptureDelegate {
|
|||||||
defer {
|
defer {
|
||||||
delegatesReferences.removeAll(where: { $0 == self })
|
delegatesReferences.removeAll(where: { $0 == self })
|
||||||
}
|
}
|
||||||
if let error = error {
|
if let error = error as NSError? {
|
||||||
return promise.reject(error: .capture(.unknown(message: error.description)), cause: error as NSError)
|
return promise.reject(error: .capture(.unknown(message: error.description)), cause: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,8 +33,8 @@ class RecordingDelegateWithCallback: NSObject, AVCaptureFileOutputRecordingDeleg
|
|||||||
self.resetTorchMode()
|
self.resetTorchMode()
|
||||||
delegateReferences.removeAll(where: { $0 == self })
|
delegateReferences.removeAll(where: { $0 == self })
|
||||||
}
|
}
|
||||||
if let error = error {
|
if let error = error as NSError? {
|
||||||
return callback([NSNull(), makeReactError(.capture(.unknown(message: error.description)), cause: error as NSError)])
|
return callback([NSNull(), makeReactError(.capture(.unknown(message: error.description)), cause: error)])
|
||||||
}
|
}
|
||||||
|
|
||||||
let seconds = CMTimeGetSeconds(output.recordedDuration)
|
let seconds = CMTimeGetSeconds(output.recordedDuration)
|
||||||
|
@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
B86DC971260E2D5200FB17B2 /* AVAudioSession+trySetAllowHaptics.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86DC970260E2D5200FB17B2 /* AVAudioSession+trySetAllowHaptics.swift */; };
|
B86DC971260E2D5200FB17B2 /* AVAudioSession+trySetAllowHaptics.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86DC970260E2D5200FB17B2 /* AVAudioSession+trySetAllowHaptics.swift */; };
|
||||||
|
B86DC974260E310600FB17B2 /* CameraView+AVAudioSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86DC973260E310600FB17B2 /* CameraView+AVAudioSession.swift */; };
|
||||||
|
B86DC977260E315100FB17B2 /* CameraView+AVCaptureSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86DC976260E315100FB17B2 /* CameraView+AVCaptureSession.swift */; };
|
||||||
B887518525E0102000DB86D6 /* PhotoCaptureDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B887515C25E0102000DB86D6 /* PhotoCaptureDelegate.swift */; };
|
B887518525E0102000DB86D6 /* PhotoCaptureDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B887515C25E0102000DB86D6 /* PhotoCaptureDelegate.swift */; };
|
||||||
B887518625E0102000DB86D6 /* CameraView+RecordVideo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B887515D25E0102000DB86D6 /* CameraView+RecordVideo.swift */; };
|
B887518625E0102000DB86D6 /* CameraView+RecordVideo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B887515D25E0102000DB86D6 /* CameraView+RecordVideo.swift */; };
|
||||||
B887518725E0102000DB86D6 /* CameraViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = B887515F25E0102000DB86D6 /* CameraViewManager.m */; };
|
B887518725E0102000DB86D6 /* CameraViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = B887515F25E0102000DB86D6 /* CameraViewManager.m */; };
|
||||||
@ -62,6 +64,8 @@
|
|||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
134814201AA4EA6300B7C361 /* libVisionCamera.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libVisionCamera.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
134814201AA4EA6300B7C361 /* libVisionCamera.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libVisionCamera.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
B86DC970260E2D5200FB17B2 /* AVAudioSession+trySetAllowHaptics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVAudioSession+trySetAllowHaptics.swift"; sourceTree = "<group>"; };
|
B86DC970260E2D5200FB17B2 /* AVAudioSession+trySetAllowHaptics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVAudioSession+trySetAllowHaptics.swift"; sourceTree = "<group>"; };
|
||||||
|
B86DC973260E310600FB17B2 /* CameraView+AVAudioSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CameraView+AVAudioSession.swift"; sourceTree = "<group>"; };
|
||||||
|
B86DC976260E315100FB17B2 /* CameraView+AVCaptureSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CameraView+AVCaptureSession.swift"; sourceTree = "<group>"; };
|
||||||
B887515C25E0102000DB86D6 /* PhotoCaptureDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoCaptureDelegate.swift; sourceTree = "<group>"; };
|
B887515C25E0102000DB86D6 /* PhotoCaptureDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoCaptureDelegate.swift; sourceTree = "<group>"; };
|
||||||
B887515D25E0102000DB86D6 /* CameraView+RecordVideo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CameraView+RecordVideo.swift"; sourceTree = "<group>"; };
|
B887515D25E0102000DB86D6 /* CameraView+RecordVideo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CameraView+RecordVideo.swift"; sourceTree = "<group>"; };
|
||||||
B887515E25E0102000DB86D6 /* CameraBridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CameraBridge.h; sourceTree = "<group>"; };
|
B887515E25E0102000DB86D6 /* CameraBridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CameraBridge.h; sourceTree = "<group>"; };
|
||||||
@ -127,6 +131,8 @@
|
|||||||
B887515E25E0102000DB86D6 /* CameraBridge.h */,
|
B887515E25E0102000DB86D6 /* CameraBridge.h */,
|
||||||
B887518325E0102000DB86D6 /* CameraError.swift */,
|
B887518325E0102000DB86D6 /* CameraError.swift */,
|
||||||
B887518425E0102000DB86D6 /* CameraView.swift */,
|
B887518425E0102000DB86D6 /* CameraView.swift */,
|
||||||
|
B86DC976260E315100FB17B2 /* CameraView+AVCaptureSession.swift */,
|
||||||
|
B86DC973260E310600FB17B2 /* CameraView+AVAudioSession.swift */,
|
||||||
B887516C25E0102000DB86D6 /* CameraView+CodeScanning.swift */,
|
B887516C25E0102000DB86D6 /* CameraView+CodeScanning.swift */,
|
||||||
B887518025E0102000DB86D6 /* CameraView+Focus.swift */,
|
B887518025E0102000DB86D6 /* CameraView+Focus.swift */,
|
||||||
B887515D25E0102000DB86D6 /* CameraView+RecordVideo.swift */,
|
B887515D25E0102000DB86D6 /* CameraView+RecordVideo.swift */,
|
||||||
@ -289,6 +295,7 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
B86DC974260E310600FB17B2 /* CameraView+AVAudioSession.swift in Sources */,
|
||||||
B887518625E0102000DB86D6 /* CameraView+RecordVideo.swift in Sources */,
|
B887518625E0102000DB86D6 /* CameraView+RecordVideo.swift in Sources */,
|
||||||
B88751A225E0102000DB86D6 /* AVCaptureColorSpace+descriptor.swift in Sources */,
|
B88751A225E0102000DB86D6 /* AVCaptureColorSpace+descriptor.swift in Sources */,
|
||||||
B887518925E0102000DB86D6 /* Collection+safe.swift in Sources */,
|
B887518925E0102000DB86D6 /* Collection+safe.swift in Sources */,
|
||||||
@ -310,6 +317,7 @@
|
|||||||
B887518B25E0102000DB86D6 /* AVCaptureDevice.Format+isBetterThan.swift in Sources */,
|
B887518B25E0102000DB86D6 /* AVCaptureDevice.Format+isBetterThan.swift in Sources */,
|
||||||
B887518E25E0102000DB86D6 /* AVFrameRateRange+includes.swift in Sources */,
|
B887518E25E0102000DB86D6 /* AVFrameRateRange+includes.swift in Sources */,
|
||||||
B88751A125E0102000DB86D6 /* AVCaptureDevice.Position+descriptor.swift in Sources */,
|
B88751A125E0102000DB86D6 /* AVCaptureDevice.Position+descriptor.swift in Sources */,
|
||||||
|
B86DC977260E315100FB17B2 /* CameraView+AVCaptureSession.swift in Sources */,
|
||||||
B887518A25E0102000DB86D6 /* AVCaptureDevice+neutralZoom.swift in Sources */,
|
B887518A25E0102000DB86D6 /* AVCaptureDevice+neutralZoom.swift in Sources */,
|
||||||
B88751A325E0102000DB86D6 /* AVCaptureDevice.FlashMode+descriptor.swift in Sources */,
|
B88751A325E0102000DB86D6 /* AVCaptureDevice.FlashMode+descriptor.swift in Sources */,
|
||||||
B887519A25E0102000DB86D6 /* AVVideoCodecType+descriptor.swift in Sources */,
|
B887519A25E0102000DB86D6 /* AVVideoCodecType+descriptor.swift in Sources */,
|
||||||
|
Loading…
Reference in New Issue
Block a user