diff --git a/Sources/VideoIO/Camera.swift b/Sources/VideoIO/Camera.swift index f33e1fa..29a0051 100644 --- a/Sources/VideoIO/Camera.swift +++ b/Sources/VideoIO/Camera.swift @@ -49,11 +49,17 @@ public class Camera { private let configurator: Configurator + private let defaultCameraUniqueId: String? + private let defaultCameraPosition: AVCaptureDevice.Position private let defaultCameraDeviceTypes: [AVCaptureDevice.DeviceType] - public init(captureSessionPreset: AVCaptureSession.Preset, defaultCameraPosition: AVCaptureDevice.Position = .back, defaultCameraDeviceTypes: [AVCaptureDevice.DeviceType] = [], configurator: Configurator = Configurator()) { + private let defaultAudioDeviceUniqueId: String? + + private let defaultAudioDeviceTypes: [AVCaptureDevice.DeviceType] + + public init(captureSessionPreset: AVCaptureSession.Preset, defaultCameraUniqueId: String? = nil, defaultCameraPosition: AVCaptureDevice.Position = .back, defaultCameraDeviceTypes: [AVCaptureDevice.DeviceType] = [], defaultAudioDeviceUniqueId: String? = nil, defaultAudioDeviceTypes: [AVCaptureDevice.DeviceType] = [], configurator: Configurator = Configurator()) { let captureSession = AVCaptureSession() assert(captureSession.canSetSessionPreset(captureSessionPreset)) captureSession.beginConfiguration() @@ -68,8 +74,11 @@ public class Camera { captureSession.commitConfiguration() self.captureSession = captureSession self.configurator = configurator + self.defaultCameraUniqueId = defaultCameraUniqueId self.defaultCameraPosition = defaultCameraPosition self.defaultCameraDeviceTypes = defaultCameraDeviceTypes + self.defaultAudioDeviceUniqueId = defaultAudioDeviceUniqueId + self.defaultAudioDeviceTypes = defaultAudioDeviceTypes } public var captureSessionIsRunning: Bool { @@ -100,7 +109,7 @@ public class Camera { return self.audioDeviceInput?.device } - public func switchToVideoCaptureDevice(with position: AVCaptureDevice.Position, preferredDeviceTypes: [AVCaptureDevice.DeviceType] = []) throws { + public func switchToVideoCaptureDevice(with position: AVCaptureDevice.Position, preferredDeviceTypes: [AVCaptureDevice.DeviceType] = [], preferredCameraUniqueId: String? = nil) throws { let deviceTypes: [AVCaptureDevice.DeviceType] if preferredDeviceTypes.count == 0 { if defaultCameraDeviceTypes.count == 0 { @@ -124,31 +133,37 @@ public class Camera { deviceTypes = preferredDeviceTypes } let discoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: deviceTypes, mediaType: .video, position: position) - if let device = discoverySession.devices.first { - let newVideoDeviceInput = try AVCaptureDeviceInput(device: device) - self.captureSession.beginConfiguration() - if let currentVideoDeviceInput = self.videoDeviceInput { - self.captureSession.removeInput(currentVideoDeviceInput) - } - if self.captureSession.canAddInput(newVideoDeviceInput) { - self.captureSession.addInput(newVideoDeviceInput) - self.videoDeviceInput = newVideoDeviceInput - } else { - self.captureSession.commitConfiguration() - throw Error.cannotAddInput - } - - if let connection = self.videoCaptureConnection { - self.configurator.videoConnectionConfigurator(self, connection) - } - self.captureSession.commitConfiguration() - - try device.lockForConfiguration() - self.configurator.videoDeviceConfigurator(self, device) - device.unlockForConfiguration() + + let device: AVCaptureDevice + if let preferredCamera = discoverySession.devices.first(where: { $0.uniqueID == preferredCameraUniqueId }) { + device = preferredCamera + } else if let discoveredDevice = discoverySession.devices.first { + device = discoveredDevice } else { throw Error.noDeviceFound } + + let newVideoDeviceInput = try AVCaptureDeviceInput(device: device) + self.captureSession.beginConfiguration() + if let currentVideoDeviceInput = self.videoDeviceInput { + self.captureSession.removeInput(currentVideoDeviceInput) + } + if self.captureSession.canAddInput(newVideoDeviceInput) { + self.captureSession.addInput(newVideoDeviceInput) + self.videoDeviceInput = newVideoDeviceInput + } else { + self.captureSession.commitConfiguration() + throw Error.cannotAddInput + } + + if let connection = self.videoCaptureConnection { + self.configurator.videoConnectionConfigurator(self, connection) + } + self.captureSession.commitConfiguration() + + try device.lockForConfiguration() + self.configurator.videoDeviceConfigurator(self, device) + device.unlockForConfiguration() } public var videoCaptureConnection: AVCaptureConnection? { @@ -160,7 +175,7 @@ public class Camera { public func enableVideoDataOutput(on queue: DispatchQueue = .main, delegate: AVCaptureVideoDataOutputSampleBufferDelegate) throws { assert(self.videoDataOutput == nil) if self.videoDevice == nil { - try self.switchToVideoCaptureDevice(with: self.defaultCameraPosition) + try self.switchToVideoCaptureDevice(with: self.defaultCameraPosition, preferredCameraUniqueId: self.defaultCameraUniqueId) } self.captureSession.beginConfiguration() defer { @@ -193,6 +208,62 @@ public class Camera { self.captureSession.commitConfiguration() } + public func switchToAudioCaptureDevice(preferredDeviceTypes: [AVCaptureDevice.DeviceType] = [], preferredAudioDeviceId: String? = nil) throws { + let deviceTypes: [AVCaptureDevice.DeviceType] + if preferredDeviceTypes.count == 0 { + if defaultAudioDeviceTypes.count == 0 { + #if os(macOS) + deviceTypes = [.builtInMicrophone, .externalUnknown] + #elseif os(tvOS) + deviceTypes = [] + #else + deviceTypes = [.builtInMicrophone] + #endif + } else { + deviceTypes = defaultAudioDeviceTypes + } + } else { + deviceTypes = preferredDeviceTypes + } + let discoverySession = AVCaptureDevice.DiscoverySession(deviceTypes: deviceTypes, mediaType: .audio, position: .unspecified) + + let device: AVCaptureDevice + if let preferredAudioDevice = discoverySession.devices.first(where: { $0.uniqueID == preferredAudioDeviceId }) { + device = preferredAudioDevice + } else if let discoveredDevice = AVCaptureDevice.default(for: .audio) { + device = discoveredDevice + } else { + throw Error.noDeviceFound + } + + let newAudioDeviceInput = try AVCaptureDeviceInput(device: device) + self.captureSession.beginConfiguration() + + if let currentAudioDeviceInput = self.audioDeviceInput { + self.captureSession.removeInput(currentAudioDeviceInput) + } + if self.captureSession.canAddInput(newAudioDeviceInput) { + self.captureSession.addInput(newAudioDeviceInput) + self.audioDeviceInput = newAudioDeviceInput + } else { + self.captureSession.commitConfiguration() + throw Error.cannotAddInput + } + + self.captureSession.commitConfiguration() + } + + public func removeAudioCaptureDevice() { + self.captureSession.beginConfiguration() + + if let currentAudioDeviceInput = self.audioDeviceInput { + self.captureSession.removeInput(currentAudioDeviceInput) + self.audioDeviceInput = nil + } + + self.captureSession.commitConfiguration() + } + public var audioCaptureConnection: AVCaptureConnection? { return self.audioDataOutput?.connection(with: .audio) } @@ -200,23 +271,14 @@ public class Camera { public private(set) var audioDataOutput: AVCaptureAudioDataOutput? public func enableAudioDataOutput(on queue: DispatchQueue = .main, delegate: AVCaptureAudioDataOutputSampleBufferDelegate) throws { + assert(self.audioDataOutput == nil) + if self.audioDevice == nil { + try self.switchToAudioCaptureDevice(preferredAudioDeviceId: self.defaultAudioDeviceUniqueId) + } self.captureSession.beginConfiguration() defer { self.captureSession.commitConfiguration() } - if self.audioDeviceInput == nil { - if let device = AVCaptureDevice.default(for: .audio), let audioDeviceInput = try? AVCaptureDeviceInput(device: device) { - if self.captureSession.canAddInput(audioDeviceInput) { - self.captureSession.addInput(audioDeviceInput) - self.audioDeviceInput = audioDeviceInput - } else { - throw Error.cannotAddInput - } - } else { - throw Error.cannotAddInput - } - } - assert(self.audioDataOutput == nil) if let audioOutput = self.audioDataOutput { self.captureSession.removeOutput(audioOutput) } @@ -236,11 +298,6 @@ public class Camera { self.captureSession.removeOutput(output) } self.audioDataOutput = nil - - if let input = self.audioDeviceInput { - self.captureSession.removeInput(input) - } - self.audioDeviceInput = nil self.captureSession.commitConfiguration() }