import Foundation import AVFoundation import SwiftSignalKit private let defaultFPS: Double = 30.0 final class CameraDevice { public private(set) var videoDevice: AVCaptureDevice? = nil public private(set) var audioDevice: AVCaptureDevice? = nil private var videoDevicePromise = Promise() init() { } var position: Camera.Position = .back func configure(for session: AVCaptureSession, position: Camera.Position) { self.position = position if #available(iOSApplicationExtension 10.0, iOS 10.0, *) { self.videoDevice = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInDuoCamera, .builtInWideAngleCamera, .builtInTelephotoCamera], mediaType: .video, position: position).devices.first } else { self.videoDevice = AVCaptureDevice.devices(for: .video).filter { $0.position == position }.first } if let videoDevice = self.videoDevice { self.videoDevicePromise.set(.single(videoDevice)) } self.audioDevice = AVCaptureDevice.default(for: .audio) } func transaction(_ device: AVCaptureDevice, update: (AVCaptureDevice) -> Void) { if let _ = try? device.lockForConfiguration() { update(device) device.unlockForConfiguration() } } private func subscribeForChanges() { NotificationCenter.default.addObserver(self, selector: #selector(self.subjectAreaChanged), name: Notification.Name.AVCaptureDeviceSubjectAreaDidChange, object: self.videoDevice) } private func unsubscribeFromChanges() { NotificationCenter.default.removeObserver(self, name: Notification.Name.AVCaptureDeviceSubjectAreaDidChange, object: self.videoDevice) } @objc private func subjectAreaChanged() { } var fps: Double = defaultFPS { didSet { guard let device = self.videoDevice, let targetFPS = device.actualFPS(Double(self.fps)) else { return } self.fps = targetFPS.fps self.transaction(device) { device in device.activeVideoMinFrameDuration = targetFPS.duration device.activeVideoMaxFrameDuration = targetFPS.duration } } } var isFlashActive: Signal { return self.videoDevicePromise.get() |> mapToSignal { device -> Signal in return Signal { subscriber in subscriber.putNext(device.isFlashActive) let observer = device.observe(\.isFlashActive, options: [.new], changeHandler: { device, _ in subscriber.putNext(device.isFlashActive) }) return ActionDisposable { observer.invalidate() } } |> distinctUntilChanged } } var isFlashAvailable: Signal { return self.videoDevicePromise.get() |> mapToSignal { device -> Signal in return Signal { subscriber in subscriber.putNext(device.isFlashAvailable) let observer = device.observe(\.isFlashAvailable, options: [.new], changeHandler: { device, _ in subscriber.putNext(device.isFlashAvailable) }) return ActionDisposable { observer.invalidate() } } |> distinctUntilChanged } } var isAdjustingFocus: Signal { return self.videoDevicePromise.get() |> mapToSignal { device -> Signal in return Signal { subscriber in subscriber.putNext(device.isAdjustingFocus) let observer = device.observe(\.isAdjustingFocus, options: [.new], changeHandler: { device, _ in subscriber.putNext(device.isAdjustingFocus) }) return ActionDisposable { observer.invalidate() } } |> distinctUntilChanged } } func setFocusPoint(_ point: CGPoint, focusMode: Camera.FocusMode, exposureMode: Camera.ExposureMode, monitorSubjectAreaChange: Bool) { guard let device = self.videoDevice else { return } self.transaction(device) { device in if device.isExposurePointOfInterestSupported && device.isExposureModeSupported(exposureMode) { device.exposurePointOfInterest = point device.exposureMode = exposureMode } if device.isFocusPointOfInterestSupported && device.isFocusModeSupported(focusMode) { device.focusPointOfInterest = point device.focusMode = focusMode } } } func setExposureTargetBias(_ bias: Float) { guard let device = self.videoDevice else { return } self.transaction(device) { device in let extremum = (bias >= 0) ? device.maxExposureTargetBias : device.minExposureTargetBias; let value = abs(bias) * extremum * 0.85 device.setExposureTargetBias(value, completionHandler: nil) } } }