Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 100 additions & 43 deletions Sources/VideoIO/Camera.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -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? {
Expand All @@ -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 {
Expand Down Expand Up @@ -193,30 +208,77 @@ 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)
}

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)
}
Expand All @@ -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()
}

Expand Down